vue2-client 1.22.32 → 1.22.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/his/editor/mock/bind_data_with_images.html +78 -0
- package/public/his/editor/mock/blank.html +645 -0
- package/src/base-client/components/common/XInspectionDetailDrawer/components/ImageAnnotationShow.vue +1 -1
- package/src/base-client/components/his/XHisEditor/ImageReportDemo.vue +155 -0
- package/src/base-client/components/his/XHisEditor/XHisEditor.vue +148 -4
- package/src/base-client/components/his/XImageReport/ImagePreview.vue +698 -198
- package/src/base-client/components/his/XSidebar/XSidebar.vue +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="image-report-demo">
|
|
3
|
+
<a-row :gutter="16">
|
|
4
|
+
<a-col :span="18">
|
|
5
|
+
<a-card title="XHisEditor 图片报告演示" style="margin-bottom: 16px;">
|
|
6
|
+
<template #extra>
|
|
7
|
+
<a-space>
|
|
8
|
+
<a-button type="primary" @click="loadMedicalRecord">
|
|
9
|
+
加载真实病历
|
|
10
|
+
</a-button>
|
|
11
|
+
<a-button @click="refreshEditor">
|
|
12
|
+
刷新编辑器
|
|
13
|
+
</a-button>
|
|
14
|
+
</a-space>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<XHisEditor
|
|
18
|
+
ref="hisEditorRef"
|
|
19
|
+
:image-report-fields="imageReportFields"
|
|
20
|
+
:bind-object="editorBindObject"
|
|
21
|
+
:save-data-logic-name="saveDataLogicName"
|
|
22
|
+
:logic-extra-params="logicExtraParams"
|
|
23
|
+
:hide-editor-toolbar="false"
|
|
24
|
+
service-name="af-his"
|
|
25
|
+
:show-mode-choose="false"
|
|
26
|
+
mode-type="data"
|
|
27
|
+
@init="onEditorInit"
|
|
28
|
+
@saveafter="onSaveAfter"
|
|
29
|
+
/>
|
|
30
|
+
</a-card>
|
|
31
|
+
</a-col>
|
|
32
|
+
|
|
33
|
+
<a-col :span="6">
|
|
34
|
+
<a-card title="说明" style="margin-bottom: 16px;">
|
|
35
|
+
<p>1. 点击“加载真实病历”后会调用 <code>getFileInformation</code>。</p>
|
|
36
|
+
<p>2. 接口返回的 <code>bindObject</code> 若带 <code>ctImages</code>,会自动渲染。</p>
|
|
37
|
+
<p>3. 保存逻辑为 <code>saveMedicalRecordFile</code>。</p>
|
|
38
|
+
</a-card>
|
|
39
|
+
|
|
40
|
+
<a-card title="当前文书信息" style="margin-bottom: 16px;">
|
|
41
|
+
<pre>{{ JSON.stringify(editorInfo, null, 2) }}</pre>
|
|
42
|
+
</a-card>
|
|
43
|
+
|
|
44
|
+
<a-card title="当前 bindObject">
|
|
45
|
+
<pre>{{ JSON.stringify(editorBindObject, null, 2) }}</pre>
|
|
46
|
+
</a-card>
|
|
47
|
+
</a-col>
|
|
48
|
+
</a-row>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script>
|
|
53
|
+
import XHisEditor from '@vue2-client/base-client/components/his/XHisEditor/XHisEditor.vue'
|
|
54
|
+
import { runLogic } from '@vue2-client/services/api/common'
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
name: 'ImageReportDemo',
|
|
58
|
+
components: {
|
|
59
|
+
XHisEditor
|
|
60
|
+
},
|
|
61
|
+
data () {
|
|
62
|
+
return {
|
|
63
|
+
editorBindObject: {},
|
|
64
|
+
imageReportFields: ['ctImages'],
|
|
65
|
+
saveDataLogicName: 'saveMedicalRecordFile',
|
|
66
|
+
editorInfo: {
|
|
67
|
+
loaded: false,
|
|
68
|
+
fileId: null,
|
|
69
|
+
templateId: 670,
|
|
70
|
+
registrationNumber: 1
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
computed: {
|
|
75
|
+
logicExtraParams () {
|
|
76
|
+
const user = this.$store.state.account?.user || {}
|
|
77
|
+
return {
|
|
78
|
+
description: '保存需要传递的参数',
|
|
79
|
+
admissionId: this.editorBindObject.id,
|
|
80
|
+
templateId: 670,
|
|
81
|
+
userInfo: user,
|
|
82
|
+
checkNum: this.editorInfo.registrationNumber
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
methods: {
|
|
87
|
+
onEditorInit (main) {
|
|
88
|
+
console.log('[ImageReportDemo] editor init', main)
|
|
89
|
+
},
|
|
90
|
+
onSaveAfter (dataObject) {
|
|
91
|
+
console.log('[ImageReportDemo] saveafter', dataObject)
|
|
92
|
+
},
|
|
93
|
+
async loadMedicalRecord () {
|
|
94
|
+
try {
|
|
95
|
+
const registrationNumber = '1'
|
|
96
|
+
const res = await runLogic('getFileInformation', {
|
|
97
|
+
RegistrationNumber: registrationNumber,
|
|
98
|
+
f_template_id: 670
|
|
99
|
+
}, 'af-his')
|
|
100
|
+
|
|
101
|
+
console.info('返回的电子病历数据:', res)
|
|
102
|
+
|
|
103
|
+
this.editorInfo = {
|
|
104
|
+
loaded: true,
|
|
105
|
+
fileId: res.id,
|
|
106
|
+
templateId: 670,
|
|
107
|
+
registrationNumber
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const bindObject = res.bindObject || {}
|
|
111
|
+
this.editorBindObject = bindObject
|
|
112
|
+
|
|
113
|
+
const editorInstance = this.$refs.hisEditorRef
|
|
114
|
+
if (editorInstance && editorInstance.init) {
|
|
115
|
+
editorInstance.init({
|
|
116
|
+
fileUrl: res.url,
|
|
117
|
+
resId: res.id,
|
|
118
|
+
currResData: bindObject,
|
|
119
|
+
bindObject: bindObject,
|
|
120
|
+
saveDataLogicName: 'saveMedicalRecordFile',
|
|
121
|
+
serviceName: 'af-his',
|
|
122
|
+
hideToolbar: true,
|
|
123
|
+
treeQueryParams: { description: '查询树结构的参数' },
|
|
124
|
+
docQueryParams: { description: '切换文档的查询条件' },
|
|
125
|
+
logicExtraParams: {
|
|
126
|
+
description: '保存需要传递的参数',
|
|
127
|
+
admissionId: res.bindObject?.id,
|
|
128
|
+
templateId: 670,
|
|
129
|
+
userInfo: this.$store.state.account?.user || {},
|
|
130
|
+
checkNum: registrationNumber
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.$message.success(res.id == null ? '新建文书成功' : '加载文书完成')
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('加载失败:', error)
|
|
138
|
+
this.$message.error('加载失败: ' + (error.message || error))
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
refreshEditor () {
|
|
142
|
+
const editorInstance = this.$refs.hisEditorRef
|
|
143
|
+
if (editorInstance && editorInstance.refresh) {
|
|
144
|
+
editorInstance.refresh()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<style scoped lang="less">
|
|
152
|
+
.image-report-demo {
|
|
153
|
+
padding: 20px;
|
|
154
|
+
}
|
|
155
|
+
</style>
|
|
@@ -103,7 +103,20 @@
|
|
|
103
103
|
<a-input v-model="modifyResModel.f_file_name" placeholder="请输入文档名"/>
|
|
104
104
|
</a-form-model-item>
|
|
105
105
|
</a-form-model>
|
|
106
|
-
</a-modal
|
|
106
|
+
</a-modal>
|
|
107
|
+
|
|
108
|
+
<!-- 编辑器内部图片预览 -->
|
|
109
|
+
<ImagePreview
|
|
110
|
+
:visible="imagePreview.visible"
|
|
111
|
+
:imgList="imagePreview.imgList"
|
|
112
|
+
:compareList="imagePreview.compareList"
|
|
113
|
+
:initialIndex="imagePreview.index"
|
|
114
|
+
:currentLeftItem="imagePreview.currentLeftItem"
|
|
115
|
+
:currentRightItem="imagePreview.currentRightItem"
|
|
116
|
+
@close="imagePreview.visible = false"
|
|
117
|
+
@pacs-sync-view="handlePacsSync"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
107
120
|
</template>
|
|
108
121
|
|
|
109
122
|
<script>
|
|
@@ -111,16 +124,22 @@
|
|
|
111
124
|
import { runLogic } from '@vue2-client/services/api/common'
|
|
112
125
|
import { initDiagnosisDropdown } from './diagnosisAutocomplete'
|
|
113
126
|
import XDocTree from './XDocTree.vue'
|
|
127
|
+
import ImagePreview from '@vue2-client/base-client/components/his/XImageReport/ImagePreview.vue'
|
|
114
128
|
|
|
115
129
|
export default {
|
|
116
130
|
name: 'XHisEditor',
|
|
117
131
|
components: {
|
|
118
|
-
XDocTree
|
|
132
|
+
XDocTree,
|
|
133
|
+
ImagePreview
|
|
119
134
|
},
|
|
120
135
|
props: {
|
|
121
136
|
hideEditorToolbar: {
|
|
122
137
|
type: Boolean,
|
|
123
138
|
default: true
|
|
139
|
+
},
|
|
140
|
+
imageReportFields: {
|
|
141
|
+
type: Array,
|
|
142
|
+
default: () => []
|
|
124
143
|
}
|
|
125
144
|
},
|
|
126
145
|
computed: {
|
|
@@ -182,6 +201,8 @@ export default {
|
|
|
182
201
|
loading: false,
|
|
183
202
|
resDataModalMode: 'modify',
|
|
184
203
|
resDataModalVisible: false,
|
|
204
|
+
// imageReportFields 支持通过 init() 参数动态覆盖
|
|
205
|
+
mergedImageReportFields: this.imageReportFields || [],
|
|
185
206
|
// 使用按钮显隐控制
|
|
186
207
|
// userTemplateVisible: false,
|
|
187
208
|
objectOfAttrs: {
|
|
@@ -248,11 +269,18 @@ export default {
|
|
|
248
269
|
fieldFilter: undefined,
|
|
249
270
|
debounceTimer: null,
|
|
250
271
|
inputListenerSetup: false,
|
|
251
|
-
isComposing: false // 标记是否正在输入中文(拼音输入中)
|
|
272
|
+
isComposing: false, // 标记是否正在输入中文(拼音输入中)
|
|
273
|
+
// 编辑器内部图片预览
|
|
274
|
+
imagePreview: {
|
|
275
|
+
visible: false,
|
|
276
|
+
compareList: [],
|
|
277
|
+
imgList: [],
|
|
278
|
+
index: 0
|
|
279
|
+
}
|
|
252
280
|
}
|
|
253
281
|
},
|
|
254
282
|
// 初始化 保存后 加载文件后 诊断选择
|
|
255
|
-
emits: ['init', 'saveafter', 'afterLoadFile', 'selected', 'inputEnd'],
|
|
283
|
+
emits: ['init', 'saveafter', 'afterLoadFile', 'selected', 'inputEnd', 'pacs-sync-view'],
|
|
256
284
|
methods: {
|
|
257
285
|
// 获取编辑器绑定数据 - 辅助方法
|
|
258
286
|
getEditorData () {
|
|
@@ -275,6 +303,8 @@ export default {
|
|
|
275
303
|
// 更新本地数据
|
|
276
304
|
this.bindObject = bindObject
|
|
277
305
|
this.codeData = {}
|
|
306
|
+
// 重新渲染图片报告
|
|
307
|
+
this.renderImageReport()
|
|
278
308
|
return true
|
|
279
309
|
}
|
|
280
310
|
return false
|
|
@@ -306,6 +336,8 @@ export default {
|
|
|
306
336
|
this.editorRef.setBindObject(currentData)
|
|
307
337
|
// 更新本地数据
|
|
308
338
|
this.bindObject = currentData
|
|
339
|
+
// 重新渲染图片报告
|
|
340
|
+
this.renderImageReport()
|
|
309
341
|
|
|
310
342
|
return true
|
|
311
343
|
},
|
|
@@ -433,6 +465,7 @@ export default {
|
|
|
433
465
|
docQueryParams,
|
|
434
466
|
storedInitParams, // 新增
|
|
435
467
|
mergeStrategy = 'specify', // 模板数据合并策略
|
|
468
|
+
imageReportFields,
|
|
436
469
|
} = params
|
|
437
470
|
|
|
438
471
|
this.resId = resId
|
|
@@ -455,6 +488,9 @@ export default {
|
|
|
455
488
|
this.modeType = (modeType === "readonly!" ? "readonly" : modeType)
|
|
456
489
|
this.readonly = (modeType !== "readonly!")
|
|
457
490
|
this.fieldFilter = fieldFilter || this.$attrs.fieldFilter
|
|
491
|
+
if (imageReportFields !== undefined) {
|
|
492
|
+
this.mergedImageReportFields = imageReportFields
|
|
493
|
+
}
|
|
458
494
|
|
|
459
495
|
if (loadResList) {
|
|
460
496
|
this.treeQueryParams = treeQueryParams ? { ...treeQueryParams } : (logicExtraParams ? { ...logicExtraParams } : {})
|
|
@@ -514,12 +550,19 @@ export default {
|
|
|
514
550
|
this.editorRef.loadUrl(fileUrl).then(() => {
|
|
515
551
|
if (bindObject) {
|
|
516
552
|
this.editorRef.setBindObject(bindObject)
|
|
553
|
+
this.bindObject = bindObject // 同步 this.bindObject,供 renderImageReport 使用
|
|
517
554
|
if (bindObject.template) {
|
|
518
555
|
for (const key of Object.keys(bindObject.template)) {
|
|
519
556
|
this.editorRef.bindDataList(key, bindObject.template[key])
|
|
520
557
|
}
|
|
521
558
|
}
|
|
522
559
|
}
|
|
560
|
+
|
|
561
|
+
// 渲染编辑器内部图片报告
|
|
562
|
+
// 等 DOM 渲染完成后再执行
|
|
563
|
+
this.$nextTick(() => {
|
|
564
|
+
this.renderImageReport()
|
|
565
|
+
})
|
|
523
566
|
// 拿取多于模板中的数据
|
|
524
567
|
const diff = this.bindObject? Object.fromEntries(
|
|
525
568
|
Object.entries(this.bindObject)
|
|
@@ -1043,6 +1086,14 @@ export default {
|
|
|
1043
1086
|
close () {
|
|
1044
1087
|
this.ready = false
|
|
1045
1088
|
},
|
|
1089
|
+
handlePacsSync (payload) {
|
|
1090
|
+
const eventName = 'pacs-sync-view'
|
|
1091
|
+
if (this.$listeners[eventName]) {
|
|
1092
|
+
this.$emit(eventName, payload)
|
|
1093
|
+
} else {
|
|
1094
|
+
this.$message?.warn?.(`未实现事件函数 [${eventName}]`)
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1046
1097
|
// 刷新文档
|
|
1047
1098
|
refresh () {
|
|
1048
1099
|
this.loadFile(this.fileUrl, this.bindObject, this.currResData)
|
|
@@ -1079,6 +1130,99 @@ export default {
|
|
|
1079
1130
|
} catch (error) {
|
|
1080
1131
|
// 静默失败
|
|
1081
1132
|
}
|
|
1133
|
+
},
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* 动态图片使用格式,在将设计好的文书导出html格式,在html种加入图片渲染锚点,id作为后端返回则字段名称:<div id="ctImages" class="x-image-report-root"></div><br>
|
|
1137
|
+
* init的时候增加属性imageReportFields: ['ctImages'],告诉组件这个字段需要渲染为图片 可以是数组字符串或者数组对象 如果是对象则{url:"图片地址",f_upload_date:“对比模式下时间字段”}
|
|
1138
|
+
*
|
|
1139
|
+
* 渲染编辑器内部的图片报告
|
|
1140
|
+
* 根据 imageReportFields 配置,在编辑器 iframe 中查找对应 ID 的容器并注入图片缩略图
|
|
1141
|
+
* 字段名即为容器 ID,模板设计师可在模板 HTML 中预留容器位置
|
|
1142
|
+
*/
|
|
1143
|
+
renderImageReport () {
|
|
1144
|
+
if (!this.mergedImageReportFields || !this.mergedImageReportFields.length) {
|
|
1145
|
+
console.log('[XHisEditor] renderImageReport: 没有配置 imageReportFields,跳过')
|
|
1146
|
+
return
|
|
1147
|
+
}
|
|
1148
|
+
if (!this.editorRef) {
|
|
1149
|
+
console.log('[XHisEditor] renderImageReport: editorRef 未就绪,等待...')
|
|
1150
|
+
setTimeout(() => this.renderImageReport(), 200)
|
|
1151
|
+
return
|
|
1152
|
+
}
|
|
1153
|
+
if (!this.editorRef.document) {
|
|
1154
|
+
console.log('[XHisEditor] renderImageReport: iframe document 未就绪,等待...')
|
|
1155
|
+
setTimeout(() => this.renderImageReport(), 200)
|
|
1156
|
+
return
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const doc = this.editorRef.document
|
|
1160
|
+
const bindObject = this.bindObject || {}
|
|
1161
|
+
|
|
1162
|
+
console.log('[XHisEditor] renderImageReport 开始:', { imageReportFields: this.mergedImageReportFields, bindObject })
|
|
1163
|
+
|
|
1164
|
+
this.mergedImageReportFields.forEach(field => {
|
|
1165
|
+
const imageList = bindObject[field]
|
|
1166
|
+
const compareList = bindObject[`compare_${field}`]
|
|
1167
|
+
console.log(`[XHisEditor] 字段 ${field}:`, imageList)
|
|
1168
|
+
|
|
1169
|
+
let container = doc.getElementById(field)
|
|
1170
|
+
if (!container) {
|
|
1171
|
+
container = doc.createElement('div')
|
|
1172
|
+
container.id = field
|
|
1173
|
+
if (doc.body) {
|
|
1174
|
+
doc.body.appendChild(container)
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
container.innerHTML = ''
|
|
1178
|
+
|
|
1179
|
+
if (!Array.isArray(imageList) || imageList.length === 0) {
|
|
1180
|
+
const empty = doc.createElement('div')
|
|
1181
|
+
empty.textContent = '暂无图片'
|
|
1182
|
+
container.appendChild(empty)
|
|
1183
|
+
return
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const grid = doc.createElement('div')
|
|
1187
|
+
grid.style.cssText = 'display:grid;grid-template-columns:repeat(3,1fr);gap:8px;'
|
|
1188
|
+
|
|
1189
|
+
imageList.forEach((item, index) => {
|
|
1190
|
+
const url = typeof item === 'string' ? item : item?.url || ''
|
|
1191
|
+
const el = doc.createElement('div')
|
|
1192
|
+
el.style.cssText = 'aspect-ratio:1;border-radius:4px;overflow:hidden;cursor:pointer;border:2px solid transparent;transition:all 0.3s;'
|
|
1193
|
+
|
|
1194
|
+
const img = doc.createElement('img')
|
|
1195
|
+
img.src = url
|
|
1196
|
+
img.style.cssText = 'width:100%;height:100%;object-fit:cover;'
|
|
1197
|
+
img.alt = `${field} ${index + 1}`
|
|
1198
|
+
|
|
1199
|
+
el.appendChild(img)
|
|
1200
|
+
grid.appendChild(el)
|
|
1201
|
+
|
|
1202
|
+
el.addEventListener('click', () => {
|
|
1203
|
+
this.openEditorImagePreview(url, imageList, compareList, index, item)
|
|
1204
|
+
})
|
|
1205
|
+
})
|
|
1206
|
+
|
|
1207
|
+
container.appendChild(grid)
|
|
1208
|
+
})
|
|
1209
|
+
},
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* 打开编辑器内部图片预览
|
|
1213
|
+
* 直接更新 imagePreview 状态,显示 ImagePreview 组件
|
|
1214
|
+
*/
|
|
1215
|
+
openEditorImagePreview (currentUrl, imgList, compareList, index, currentItem) {
|
|
1216
|
+
const leftItem = typeof currentItem === 'object' ? currentItem : (imgList[index] || null)
|
|
1217
|
+
const rightItem = typeof currentItem === 'object' ? null : (compareList[index] || null)
|
|
1218
|
+
this.imagePreview = {
|
|
1219
|
+
visible: true,
|
|
1220
|
+
imgList,
|
|
1221
|
+
compareList,
|
|
1222
|
+
index,
|
|
1223
|
+
currentLeftItem: leftItem,
|
|
1224
|
+
currentRightItem: rightItem
|
|
1225
|
+
}
|
|
1082
1226
|
}
|
|
1083
1227
|
}
|
|
1084
1228
|
}
|