vue2-client 1.22.15 → 1.22.16
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/src/base-client/components/common/FileImage/FileImageGroup.vue +14 -1
- package/src/base-client/components/common/ImagePreviewModal/ImagePreviewModal.vue +16 -4
- package/src/base-client/components/common/XInspectionDetailDrawer/components/AiDeviceStatus.vue +53 -6
- package/src/base-client/components/common/XInspectionDetailDrawer/components/CheckItems.vue +28 -35
- package/src/base-client/components/common/XInspectionDetailDrawer/components/DeviceStatus.vue +62 -4
- package/src/base-client/components/common/XInspectionDetailDrawer/components/HazardPhotos.vue +49 -1
- package/src/base-client/components/common/XInspectionDetailDrawer/index.vue +191 -4
package/package.json
CHANGED
|
@@ -99,7 +99,9 @@ export default {
|
|
|
99
99
|
emptyText: { type: String, default: '暂无图片' },
|
|
100
100
|
columns: { type: Number, default: 3, validator: v => v >= 1 && v <= 6 },
|
|
101
101
|
imageSize: { type: [String, Number], default: 90 },
|
|
102
|
-
gap: { type: [String, Number], default: 10 }
|
|
102
|
+
gap: { type: [String, Number], default: 10 },
|
|
103
|
+
/** 为 true 时禁止内部预览弹窗;点击图片触发 image-click 事件,由父组件接管预览逻辑 */
|
|
104
|
+
noPreview: { type: Boolean, default: false }
|
|
103
105
|
},
|
|
104
106
|
data () {
|
|
105
107
|
return {
|
|
@@ -186,7 +188,13 @@ export default {
|
|
|
186
188
|
this.galleryVisible = false
|
|
187
189
|
},
|
|
188
190
|
|
|
191
|
+
/** 点击图片:noPreview 模式下仅抛事件,由父组件处理预览;否则打开内部画廊 */
|
|
189
192
|
handleImageClick (index) {
|
|
193
|
+
if (this.noPreview) {
|
|
194
|
+
const allUrls = this.fileIds.map((fd, i) => this.urlsMap[i] || (fd && typeof fd === 'object' && fd.url) || '')
|
|
195
|
+
this.$emit('image-click', { index, url: allUrls[index] || '', fileData: this.fileIds[index], allUrls })
|
|
196
|
+
return
|
|
197
|
+
}
|
|
190
198
|
this.currentIndex = index
|
|
191
199
|
this.galleryVisible = true
|
|
192
200
|
},
|
|
@@ -201,6 +209,11 @@ export default {
|
|
|
201
209
|
|
|
202
210
|
nextImage () {
|
|
203
211
|
if (this.currentIndex < this.fileIds.length - 1) this.currentIndex++
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
/** 返回当前所有已解析的图片 URL 数组(外部通过此方法拉取,而非事件推) */
|
|
215
|
+
getAllResolvedUrls () {
|
|
216
|
+
return this.fileIds.map((fd, i) => this.urlsMap[i] || (fd && typeof fd === 'object' && fd.url) || '')
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
}
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
<!-- 底部工具栏 -->
|
|
41
41
|
<div class="preview-toolbar bottom-toolbar" @wheel.prevent.stop>
|
|
42
42
|
<div class="toolbar-content">
|
|
43
|
+
<slot name="toolbarPrepend" />
|
|
43
44
|
<a-tooltip title="放大">
|
|
44
45
|
<a-button type="primary" icon="zoom-in" @click="zoomIn" size="small" class="toolbar-btn" />
|
|
45
46
|
</a-tooltip>
|
|
@@ -71,6 +72,9 @@
|
|
|
71
72
|
</a-tooltip>
|
|
72
73
|
</div>
|
|
73
74
|
</div>
|
|
75
|
+
<div class="bottom-slot">
|
|
76
|
+
<slot name="thumbnailStrip" />
|
|
77
|
+
</div>
|
|
74
78
|
</div>
|
|
75
79
|
</template>
|
|
76
80
|
|
|
@@ -305,10 +309,10 @@ export default {
|
|
|
305
309
|
// 鼠标滚轮缩放(居中缩放,与按钮行为一致)
|
|
306
310
|
handleWheel(event) {
|
|
307
311
|
event.preventDefault()
|
|
308
|
-
|
|
312
|
+
|
|
309
313
|
const delta = event.deltaY > 0 ? -ZOOM_CONFIG.STEP : ZOOM_CONFIG.STEP
|
|
310
314
|
const newScale = Math.min(Math.max(ZOOM_CONFIG.MIN, this.scale + delta), ZOOM_CONFIG.MAX)
|
|
311
|
-
|
|
315
|
+
|
|
312
316
|
// 如果缩放值没有变化,直接返回
|
|
313
317
|
if (newScale === this.scale) return
|
|
314
318
|
|
|
@@ -486,11 +490,19 @@ export default {
|
|
|
486
490
|
margin-bottom: 8px;
|
|
487
491
|
}
|
|
488
492
|
}
|
|
489
|
-
|
|
493
|
+
.bottom-slot{
|
|
494
|
+
position: fixed;
|
|
495
|
+
bottom: 10px;
|
|
496
|
+
left: 0;
|
|
497
|
+
right: 0;
|
|
498
|
+
z-index: 1011;
|
|
499
|
+
display: flex;
|
|
500
|
+
justify-content: center;
|
|
501
|
+
}
|
|
490
502
|
// 底部工具栏样式
|
|
491
503
|
.bottom-toolbar {
|
|
492
504
|
position: fixed;
|
|
493
|
-
bottom:
|
|
505
|
+
bottom: 60px;
|
|
494
506
|
left: 0;
|
|
495
507
|
right: 0;
|
|
496
508
|
z-index: 1011;
|
package/src/base-client/components/common/XInspectionDetailDrawer/components/AiDeviceStatus.vue
CHANGED
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
:columns="2"
|
|
55
55
|
:image-size="60"
|
|
56
56
|
:gap="8"
|
|
57
|
+
:no-preview="true"
|
|
58
|
+
@image-click="handleFileImageClick"
|
|
59
|
+
:ref="el => registerFig(el)"
|
|
57
60
|
/>
|
|
58
61
|
</div>
|
|
59
62
|
<div v-else class="violation-images-empty"></div>
|
|
@@ -102,6 +105,9 @@
|
|
|
102
105
|
:columns="2"
|
|
103
106
|
:image-size="60"
|
|
104
107
|
:gap="8"
|
|
108
|
+
:no-preview="true"
|
|
109
|
+
@image-click="handleFileImageClick"
|
|
110
|
+
:ref="el => registerFig(el)"
|
|
105
111
|
/>
|
|
106
112
|
<span v-else></span></div>
|
|
107
113
|
<div class="inconsistent-item">
|
|
@@ -164,6 +170,9 @@
|
|
|
164
170
|
:columns="2"
|
|
165
171
|
:image-size="60"
|
|
166
172
|
:gap="8"
|
|
173
|
+
:no-preview="true"
|
|
174
|
+
@image-click="handleFileImageClick"
|
|
175
|
+
:ref="el => registerFig(el)"
|
|
167
176
|
/>
|
|
168
177
|
<span v-else></span></div>
|
|
169
178
|
<div class="danger-item">{{ item.aiVal }}</div>
|
|
@@ -222,6 +231,20 @@ export default {
|
|
|
222
231
|
return unmatched + add
|
|
223
232
|
},
|
|
224
233
|
},
|
|
234
|
+
data () {
|
|
235
|
+
return {
|
|
236
|
+
/** 储存本组件内所有 FileImageGroup 实例(通过函数 ref 注册) */
|
|
237
|
+
figInstances: []
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
watch: {
|
|
241
|
+
/** 设备列表变化时清空实例缓存,重新渲染时会重新注册 */
|
|
242
|
+
deviceList: {
|
|
243
|
+
handler () { this.figInstances = [] },
|
|
244
|
+
deep: true,
|
|
245
|
+
immediate: true
|
|
246
|
+
}
|
|
247
|
+
},
|
|
225
248
|
methods: {
|
|
226
249
|
|
|
227
250
|
/**
|
|
@@ -287,12 +310,36 @@ export default {
|
|
|
287
310
|
: []
|
|
288
311
|
},
|
|
289
312
|
|
|
290
|
-
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
313
|
+
/** 通过函数 ref 收集 FileImageGroup 实例 */
|
|
314
|
+
registerFig (fig) {
|
|
315
|
+
if (fig && this.figInstances.indexOf(fig) === -1) {
|
|
316
|
+
this.figInstances.push(fig)
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
/** 收集本组件内所有 FileImageGroup 的已解析图片 URL,去重后返回 */
|
|
321
|
+
_collectAllUrls () {
|
|
322
|
+
const urls = []
|
|
323
|
+
this.figInstances.forEach(fig => {
|
|
324
|
+
if (fig && fig.getAllResolvedUrls) {
|
|
325
|
+
fig.getAllResolvedUrls().forEach(u => { if (u && urls.indexOf(u) === -1) urls.push(u) })
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
return urls
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
/** 父组件通过 ref 调用的公共方法:获取本组件全部图片 URL */
|
|
332
|
+
getAllPhotoUrls () {
|
|
333
|
+
return this._collectAllUrls()
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
/** 点击 FileImageGroup 图片:收集全组件 URL 抛给父组件统一预览 */
|
|
337
|
+
handleFileImageClick({ index, url }) {
|
|
338
|
+
const urls = this._collectAllUrls()
|
|
339
|
+
if (urls.length > 0) {
|
|
340
|
+
const globalIdx = url ? urls.indexOf(url) : Math.min(index, urls.length - 1)
|
|
341
|
+
this.$emit('preview-photos', urls, globalIdx >= 0 ? globalIdx : 0)
|
|
342
|
+
}
|
|
296
343
|
}
|
|
297
344
|
}
|
|
298
345
|
}
|
|
@@ -123,27 +123,14 @@
|
|
|
123
123
|
</div>
|
|
124
124
|
</a-card>
|
|
125
125
|
|
|
126
|
-
<!-- 图片全屏预览 -->
|
|
127
|
-
<image-preview-modal
|
|
128
|
-
v-if="previewVisible"
|
|
129
|
-
:visible="previewVisible"
|
|
130
|
-
:image-url="currentPreviewUrl"
|
|
131
|
-
:file-id="currentPreviewUrl ? null : currentPreviewFileId"
|
|
132
|
-
:service-name="currentPreviewUrl ? '' : injectedServiceName"
|
|
133
|
-
:image-name="`照片${previewIndex + 1}`"
|
|
134
|
-
:append-to-body="true"
|
|
135
|
-
@close="previewVisible = false"
|
|
136
|
-
/>
|
|
137
126
|
</div>
|
|
138
127
|
</template>
|
|
139
128
|
|
|
140
129
|
<script>
|
|
141
|
-
import ImagePreviewModal from '@vue2-client/base-client/components/common/ImagePreviewModal/ImagePreviewModal.vue'
|
|
142
130
|
import { createFileImageService } from '@vue2-client/utils/fileImageService'
|
|
143
131
|
|
|
144
132
|
export default {
|
|
145
133
|
name: 'CheckItems',
|
|
146
|
-
components: { ImagePreviewModal },
|
|
147
134
|
inject: {
|
|
148
135
|
serviceName: { default: '' }
|
|
149
136
|
},
|
|
@@ -159,9 +146,6 @@ export default {
|
|
|
159
146
|
},
|
|
160
147
|
data () {
|
|
161
148
|
return {
|
|
162
|
-
previewVisible: false,
|
|
163
|
-
previewIndex: 0,
|
|
164
|
-
previewPhotos: [],
|
|
165
149
|
thumbUrlMap: {},
|
|
166
150
|
thumbStateMap: {}
|
|
167
151
|
}
|
|
@@ -169,22 +153,6 @@ export default {
|
|
|
169
153
|
computed: {
|
|
170
154
|
injectedServiceName () {
|
|
171
155
|
return this.serviceName || ''
|
|
172
|
-
},
|
|
173
|
-
currentPhoto () {
|
|
174
|
-
return this.previewPhotos[this.previewIndex] || null
|
|
175
|
-
},
|
|
176
|
-
currentPreviewUrl () {
|
|
177
|
-
if (!this.currentPhoto) return ''
|
|
178
|
-
if (this.currentPhoto.url) return this.currentPhoto.url
|
|
179
|
-
if (this.currentPhoto.fileId) {
|
|
180
|
-
const key = this.thumbCacheKey(this.currentPhoto)
|
|
181
|
-
return this.thumbUrlMap[key] || ''
|
|
182
|
-
}
|
|
183
|
-
return ''
|
|
184
|
-
},
|
|
185
|
-
currentPreviewFileId () {
|
|
186
|
-
if (!this.currentPhoto) return null
|
|
187
|
-
return this.currentPhoto.fileId || null
|
|
188
156
|
}
|
|
189
157
|
},
|
|
190
158
|
watch: {
|
|
@@ -213,6 +181,30 @@ export default {
|
|
|
213
181
|
})
|
|
214
182
|
},
|
|
215
183
|
|
|
184
|
+
/** 父组件通过 ref 调用的公共方法:获取本组件全部图片 URL(含已解析的 fileId 缩略图) */
|
|
185
|
+
getAllPhotoUrls () {
|
|
186
|
+
const urls = []
|
|
187
|
+
if (!this.checkItems) return urls
|
|
188
|
+
this.checkItems.forEach(category => {
|
|
189
|
+
if (!category.items) return
|
|
190
|
+
category.items.forEach(item => {
|
|
191
|
+
if (item.photos) {
|
|
192
|
+
item.photos.forEach(p => {
|
|
193
|
+
const u = p.url || this.thumbUrlMap[this.thumbCacheKey(p)]
|
|
194
|
+
if (u && urls.indexOf(u) === -1) urls.push(u)
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
if (this.isResultPhotos(item.result)) {
|
|
198
|
+
this.parseResultPhotos(item.result).forEach(p => {
|
|
199
|
+
const u = p.url
|
|
200
|
+
if (u && urls.indexOf(u) === -1) urls.push(u)
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
return urls
|
|
206
|
+
},
|
|
207
|
+
|
|
216
208
|
/**
|
|
217
209
|
* 缩略图缓存必须用字符串键。若用对象作 fileId,JS 会统一变成 "[object Object]",导致所有缩略图共用一张图。
|
|
218
210
|
* 优先使用业务侧已生成的 photo.id,其次从 fileId 上解析 id。
|
|
@@ -281,9 +273,10 @@ export default {
|
|
|
281
273
|
* 打开预览
|
|
282
274
|
*/
|
|
283
275
|
openPreview (photos, index) {
|
|
284
|
-
this.
|
|
285
|
-
|
|
286
|
-
|
|
276
|
+
const urls = photos.map(p => p.url || this.thumbUrlMap[this.thumbCacheKey(p)] || '').filter(Boolean)
|
|
277
|
+
if (urls.length > 0) {
|
|
278
|
+
this.$emit('preview-photos', urls, Math.min(index, urls.length - 1))
|
|
279
|
+
}
|
|
287
280
|
},
|
|
288
281
|
|
|
289
282
|
/**
|
package/src/base-client/components/common/XInspectionDetailDrawer/components/DeviceStatus.vue
CHANGED
|
@@ -59,6 +59,9 @@
|
|
|
59
59
|
:image-size="60"
|
|
60
60
|
:gap="8"
|
|
61
61
|
:alt-prefix="field.label + '图片'"
|
|
62
|
+
:no-preview="true"
|
|
63
|
+
@image-click="handleFileImageClick"
|
|
64
|
+
:ref="el => registerFig(el)"
|
|
62
65
|
/>
|
|
63
66
|
</div>
|
|
64
67
|
<span v-else class="tag-value">{{ formatFieldValue(field) }}</span>
|
|
@@ -93,10 +96,12 @@
|
|
|
93
96
|
:gap="8"
|
|
94
97
|
:alt-prefix="device.name + '照片'"
|
|
95
98
|
empty-text="暂无设备照片"
|
|
99
|
+
:no-preview="true"
|
|
100
|
+
@image-click="handleFileImageClick"
|
|
101
|
+
:ref="el => registerFig(el)"
|
|
96
102
|
/>
|
|
97
103
|
</div>
|
|
98
|
-
|
|
99
|
-
<!-- 隐患照片 -->
|
|
104
|
+
<!-- 隐患照片 -->
|
|
100
105
|
<div class="hazard-photos-section" v-if="device.hazardPhotos && device.hazardPhotos.length > 0">
|
|
101
106
|
<div class="section-title">隐患照片</div>
|
|
102
107
|
<!-- 隐患照片使用FileImageGroup -->
|
|
@@ -109,6 +114,9 @@
|
|
|
109
114
|
:gap="8"
|
|
110
115
|
:alt-prefix="device.name + '隐患'"
|
|
111
116
|
empty-text="暂无隐患照片"
|
|
117
|
+
:no-preview="true"
|
|
118
|
+
@image-click="handleFileImageClick"
|
|
119
|
+
:ref="el => registerFig(el)"
|
|
112
120
|
/>
|
|
113
121
|
<!-- 备用显示方式 -->
|
|
114
122
|
<div v-else class="hazard-photos-grid">
|
|
@@ -166,6 +174,20 @@ export default {
|
|
|
166
174
|
default: () => []
|
|
167
175
|
}
|
|
168
176
|
},
|
|
177
|
+
data () {
|
|
178
|
+
return {
|
|
179
|
+
/** 储存本组件内所有 FileImageGroup 实例(通过函数 ref 注册) */
|
|
180
|
+
figInstances: []
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
watch: {
|
|
184
|
+
/** 设备列表变化时清空实例缓存,下一帧重新渲染时会重新注册 */
|
|
185
|
+
deviceList: {
|
|
186
|
+
handler () { this.figInstances = [] },
|
|
187
|
+
deep: true,
|
|
188
|
+
immediate: true
|
|
189
|
+
}
|
|
190
|
+
},
|
|
169
191
|
methods: {
|
|
170
192
|
/**
|
|
171
193
|
* 根据设备信息获取图标类型
|
|
@@ -256,12 +278,48 @@ export default {
|
|
|
256
278
|
return new Date().toISOString().substring(0, 16).replace('T', ' ')
|
|
257
279
|
},
|
|
258
280
|
|
|
281
|
+
/** 通过函数 ref 收集 FileImageGroup 实例 */
|
|
282
|
+
registerFig (fig) {
|
|
283
|
+
if (fig && this.figInstances.indexOf(fig) === -1) {
|
|
284
|
+
this.figInstances.push(fig)
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
/** 收集本组件内所有 FileImageGroup 的已解析图片 URL,去重后返回 */
|
|
289
|
+
_collectAllUrls () {
|
|
290
|
+
const urls = []
|
|
291
|
+
this.figInstances.forEach(fig => {
|
|
292
|
+
if (fig && fig.getAllResolvedUrls) {
|
|
293
|
+
fig.getAllResolvedUrls().forEach(u => { if (u && urls.indexOf(u) === -1) urls.push(u) })
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
return urls
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
/** 父组件通过 ref 调用的公共方法:获取本组件全部图片 URL */
|
|
300
|
+
getAllPhotoUrls () {
|
|
301
|
+
return this._collectAllUrls()
|
|
302
|
+
},
|
|
303
|
+
|
|
259
304
|
/**
|
|
260
|
-
*
|
|
305
|
+
* 点击隐患照片(非 FileImageGroup 容灾展示):收集全组件 URL 抛给父组件
|
|
261
306
|
*/
|
|
262
307
|
handlePreviewHazardPhotos (hazardPhotos, currentIndex = 0) {
|
|
263
308
|
const photos = hazardPhotos.map(photo => photo.url)
|
|
264
|
-
this
|
|
309
|
+
const all = this._collectAllUrls()
|
|
310
|
+
const urls = all.length > 0 ? all : photos
|
|
311
|
+
if (urls.length > 0) {
|
|
312
|
+
this.$emit('preview-photos', urls, Math.min(currentIndex, urls.length - 1))
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
/** 点击 FileImageGroup 图片:收集全组件 URL 抛给父组件统一预览 */
|
|
317
|
+
handleFileImageClick ({ index, url }) {
|
|
318
|
+
const urls = this._collectAllUrls()
|
|
319
|
+
if (urls.length > 0) {
|
|
320
|
+
const globalIdx = url ? urls.indexOf(url) : Math.min(index, urls.length - 1)
|
|
321
|
+
this.$emit('preview-photos', urls, globalIdx >= 0 ? globalIdx : 0)
|
|
322
|
+
}
|
|
265
323
|
},
|
|
266
324
|
|
|
267
325
|
/**
|
package/src/base-client/components/common/XInspectionDetailDrawer/components/HazardPhotos.vue
CHANGED
|
@@ -68,6 +68,9 @@
|
|
|
68
68
|
:gap="10"
|
|
69
69
|
alt-prefix="隐患照片"
|
|
70
70
|
empty-text="暂无隐患照片"
|
|
71
|
+
:no-preview="true"
|
|
72
|
+
@image-click="handleImageClick"
|
|
73
|
+
:ref="el => registerFig(el)"
|
|
71
74
|
/>
|
|
72
75
|
</div>
|
|
73
76
|
|
|
@@ -91,6 +94,9 @@
|
|
|
91
94
|
:gap="10"
|
|
92
95
|
alt-prefix="现场处理照片"
|
|
93
96
|
empty-text="暂无现场处理照片"
|
|
97
|
+
:no-preview="true"
|
|
98
|
+
@image-click="handleImageClick"
|
|
99
|
+
:ref="el => registerFig(el)"
|
|
94
100
|
/>
|
|
95
101
|
</div>
|
|
96
102
|
</div>
|
|
@@ -130,7 +136,17 @@ export default {
|
|
|
130
136
|
},
|
|
131
137
|
data () {
|
|
132
138
|
return {
|
|
133
|
-
hazardLevelConfig: []
|
|
139
|
+
hazardLevelConfig: [],
|
|
140
|
+
/** 储存本组件内所有 FileImageGroup 实例(通过函数 ref 注册) */
|
|
141
|
+
figInstances: []
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
watch: {
|
|
145
|
+
/** 隐患列表数据变化时清空实例缓存,下一帧重新渲染时会重新注册 */
|
|
146
|
+
hazardList: {
|
|
147
|
+
handler () { this.figInstances = [] },
|
|
148
|
+
deep: true,
|
|
149
|
+
immediate: true
|
|
134
150
|
}
|
|
135
151
|
},
|
|
136
152
|
methods: {
|
|
@@ -206,6 +222,38 @@ export default {
|
|
|
206
222
|
minor: 'info-circle'
|
|
207
223
|
}
|
|
208
224
|
return iconMap[level] || 'warning'
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
/** 通过函数 ref 收集 FileImageGroup 实例(v-for 中无法使用静态 ref) */
|
|
228
|
+
registerFig (fig) {
|
|
229
|
+
if (fig && this.figInstances.indexOf(fig) === -1) {
|
|
230
|
+
this.figInstances.push(fig)
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/** 收集本组件内所有 FileImageGroup 的已解析图片 URL,去重后返回 */
|
|
235
|
+
_collectAllUrls () {
|
|
236
|
+
const urls = []
|
|
237
|
+
this.figInstances.forEach(fig => {
|
|
238
|
+
if (fig && fig.getAllResolvedUrls) {
|
|
239
|
+
fig.getAllResolvedUrls().forEach(u => { if (u && urls.indexOf(u) === -1) urls.push(u) })
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
return urls
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/** 父组件通过 ref 调用的公共方法:获取本组件全部图片 URL */
|
|
246
|
+
getAllPhotoUrls () {
|
|
247
|
+
return this._collectAllUrls()
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/** 点击图片:收集本组件全部 URL 抛给父组件统一预览 */
|
|
251
|
+
handleImageClick ({ index, url }) {
|
|
252
|
+
const urls = this._collectAllUrls()
|
|
253
|
+
if (urls.length > 0) {
|
|
254
|
+
const globalIdx = url ? urls.indexOf(url) : index
|
|
255
|
+
this.$emit('preview-photos', urls, globalIdx >= 0 ? globalIdx : 0)
|
|
256
|
+
}
|
|
209
257
|
}
|
|
210
258
|
},
|
|
211
259
|
mounted() {
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
</div>
|
|
75
75
|
<div class="device-section" v-show="showAiDeviceTab">
|
|
76
76
|
<ai-device-status
|
|
77
|
+
ref="aiDeviceStatus"
|
|
77
78
|
:device-list="aiDeviceItem"
|
|
78
79
|
:config="AICheckConfig?.XInspectionDetailDrawer?.AiDeviceStatus"
|
|
79
80
|
:check-total-count="checkTotalCount"
|
|
@@ -83,6 +84,7 @@
|
|
|
83
84
|
</div>
|
|
84
85
|
<div class="device-section" v-show="inspectionData.deviceList.length > 0">
|
|
85
86
|
<device-status
|
|
87
|
+
ref="deviceStatus"
|
|
86
88
|
:device-list="inspectionData.deviceList"
|
|
87
89
|
@preview-photos="(photos, idx) => handlePreviewPhotos(photos, idx)"
|
|
88
90
|
/>
|
|
@@ -90,14 +92,20 @@
|
|
|
90
92
|
|
|
91
93
|
<div class="check-items-section" v-show="inspectionData.checkItems.length > 0">
|
|
92
94
|
<check-items
|
|
95
|
+
ref="checkItems"
|
|
93
96
|
:check-items="inspectionData.checkItems"
|
|
97
|
+
@preview-photos="(photos, idx) => handlePreviewPhotos(photos, idx)"
|
|
94
98
|
/>
|
|
95
99
|
</div>
|
|
96
100
|
|
|
97
101
|
<div class="hazard-gallery-section" v-show="inspectionData.basicInfo.f_entry_status === '入户'">
|
|
98
102
|
<a-card class="section-card" :bordered="false">
|
|
99
103
|
<div class="section-grid">
|
|
100
|
-
<hazard-photos
|
|
104
|
+
<hazard-photos
|
|
105
|
+
ref="hazardPhotos"
|
|
106
|
+
:hazard-list="inspectionData.hazardList"
|
|
107
|
+
@preview-photos="(photos, idx) => handlePreviewPhotos(photos, idx)"
|
|
108
|
+
/>
|
|
101
109
|
<div class="overall-photos">
|
|
102
110
|
<div class="photos-header">
|
|
103
111
|
<div class="photos-title">
|
|
@@ -124,6 +132,9 @@
|
|
|
124
132
|
:gap="10"
|
|
125
133
|
:alt-prefix="field.name"
|
|
126
134
|
:empty-text="`暂无${field.name}`"
|
|
135
|
+
:no-preview="true"
|
|
136
|
+
@image-click="handleFormFileImageClick"
|
|
137
|
+
:ref="el => registerFormFig(el)"
|
|
127
138
|
/>
|
|
128
139
|
<span v-else class="empty-text">暂无</span>
|
|
129
140
|
</template>
|
|
@@ -138,6 +149,9 @@
|
|
|
138
149
|
:gap="10"
|
|
139
150
|
:alt-prefix="field.name"
|
|
140
151
|
:empty-text="`暂无${field.name}`"
|
|
152
|
+
:no-preview="true"
|
|
153
|
+
@image-click="handleFormFileImageClick"
|
|
154
|
+
:ref="el => registerFormFig(el)"
|
|
141
155
|
/>
|
|
142
156
|
<span v-else class="empty-text">暂无</span>
|
|
143
157
|
</template>
|
|
@@ -167,7 +181,45 @@
|
|
|
167
181
|
:image-name="`预览图片${previewModal.current + 1}`"
|
|
168
182
|
:append-to-body="true"
|
|
169
183
|
@close="previewModal.visible = false"
|
|
170
|
-
|
|
184
|
+
>
|
|
185
|
+
<template #toolbarPrepend v-if="allPhotoList.length > 0">
|
|
186
|
+
<a-tooltip title="上一张">
|
|
187
|
+
<a-button
|
|
188
|
+
type="primary"
|
|
189
|
+
icon="left"
|
|
190
|
+
@click="goPrevPhoto"
|
|
191
|
+
size="small"
|
|
192
|
+
:disabled="previewModal.current <= 0"
|
|
193
|
+
style="background:rgba(255,255,255,0.15);border-color:rgba(255,255,255,0.2);color:#fff;"
|
|
194
|
+
/>
|
|
195
|
+
</a-tooltip>
|
|
196
|
+
<span style="color:#fff;font-size:13px;min-width:50px;text-align:center;display:inline-block;">{{ previewModal.current + 1 }} / {{ allPhotoList.length }}</span>
|
|
197
|
+
<a-tooltip title="下一张">
|
|
198
|
+
<a-button
|
|
199
|
+
type="primary"
|
|
200
|
+
icon="right"
|
|
201
|
+
@click="goNextPhoto"
|
|
202
|
+
size="small"
|
|
203
|
+
:disabled="previewModal.current >= allPhotoList.length - 1"
|
|
204
|
+
style="background:rgba(255,255,255,0.15);border-color:rgba(255,255,255,0.2);color:#fff;"
|
|
205
|
+
/>
|
|
206
|
+
</a-tooltip>
|
|
207
|
+
<span style="width:1px;height:20px;background:rgba(255,255,255,0.2);margin:0 4px;display:inline-block;"></span>
|
|
208
|
+
</template>
|
|
209
|
+
<template #thumbnailStrip v-if="allPhotoList.length > 1">
|
|
210
|
+
<div class="thumbnail-strip">
|
|
211
|
+
<div
|
|
212
|
+
v-for="(thumb, i) in allPhotoList"
|
|
213
|
+
:key="i"
|
|
214
|
+
class="thumbnail-item"
|
|
215
|
+
:class="{ active: i === previewModal.current }"
|
|
216
|
+
@click="previewModal.current = i"
|
|
217
|
+
>
|
|
218
|
+
<img :src="thumb" :alt="`缩略图 ${i + 1}`" />
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</template>
|
|
222
|
+
</image-preview-modal>
|
|
171
223
|
|
|
172
224
|
<a-modal
|
|
173
225
|
v-if="approveModal"
|
|
@@ -271,6 +323,9 @@ export default {
|
|
|
271
323
|
}
|
|
272
324
|
},
|
|
273
325
|
previewModal: { visible: false, images: [], current: 0 },
|
|
326
|
+
allPhotoList: [],
|
|
327
|
+
/** 收集"拍照签名信息"区域中 FileImageGroup 实例(通过函数 ref 注册) */
|
|
328
|
+
formFigInstances: [],
|
|
274
329
|
approveModal: { visible: false, type: 'approve', form: { note: '' }, loading: false },
|
|
275
330
|
approveRules: {
|
|
276
331
|
note: [
|
|
@@ -366,6 +421,8 @@ export default {
|
|
|
366
421
|
hazardList: [],
|
|
367
422
|
attachments: { signature: null, recordings: [], filePhotos: [], imagePhotos: [] }
|
|
368
423
|
}
|
|
424
|
+
this.allPhotoList = []
|
|
425
|
+
this.formFigInstances = []
|
|
369
426
|
|
|
370
427
|
this.showHistoryPanel = !params?.id && !!params?.f_userinfo_id
|
|
371
428
|
|
|
@@ -406,10 +463,12 @@ export default {
|
|
|
406
463
|
console.error('getPcAiInspectionAssistResultLogic', e)
|
|
407
464
|
}
|
|
408
465
|
console.warn('datra', data)
|
|
466
|
+
this.formFigInstances = []
|
|
409
467
|
this.inspectionData = {
|
|
410
468
|
...data,
|
|
411
469
|
historyList: this.inspectionData.historyList || data.historyList || []
|
|
412
470
|
}
|
|
471
|
+
this.allPhotoList = this._rebuildAllPhotoList()
|
|
413
472
|
if (this.inspectionData.historyList && this.inspectionData.historyList.length > 0) {
|
|
414
473
|
const activeItem = this.inspectionData.historyList.find(item => item.isActive)
|
|
415
474
|
if (activeItem) this.currentHistoryId = activeItem.id
|
|
@@ -423,6 +482,7 @@ export default {
|
|
|
423
482
|
this.currentHistoryId = historyId
|
|
424
483
|
const data = await inspectionService.switchToHistoryDetail(historyId, this.currentId, this.f_userinfo_id)
|
|
425
484
|
this.inspectionData = { ...data, historyList: this.inspectionData.historyList || data.historyList || [] }
|
|
485
|
+
this.allPhotoList = this._rebuildAllPhotoList()
|
|
426
486
|
} catch (error) {
|
|
427
487
|
this.$message.error('切换历史记录失败')
|
|
428
488
|
} finally {
|
|
@@ -479,11 +539,99 @@ export default {
|
|
|
479
539
|
this.$message.info('录音播放功能待实现')
|
|
480
540
|
},
|
|
481
541
|
|
|
542
|
+
/** 打开图片预览:先按固定顺序重新构建全量列表,再定位点击的图片位置 */
|
|
482
543
|
handlePreviewPhotos (photos, current = 0) {
|
|
544
|
+
if (!Array.isArray(photos) || photos.length === 0) return
|
|
545
|
+
const urls = typeof photos[0] === 'string' ? photos : photos.map(p => p?.url || p?.imageUrl).filter(Boolean)
|
|
546
|
+
this.allPhotoList = this._rebuildAllPhotoList()
|
|
547
|
+
const merged = [...this.allPhotoList]
|
|
548
|
+
urls.forEach(url => {
|
|
549
|
+
if (url && merged.indexOf(url) === -1) merged.push(url)
|
|
550
|
+
})
|
|
551
|
+
this.allPhotoList = merged
|
|
552
|
+
const clickedUrl = urls[Math.min(current, urls.length - 1)]
|
|
553
|
+
const globalIdx = clickedUrl ? merged.indexOf(clickedUrl) : 0
|
|
483
554
|
this.previewModal = {
|
|
484
555
|
visible: true,
|
|
485
|
-
images:
|
|
486
|
-
current
|
|
556
|
+
images: merged,
|
|
557
|
+
current: globalIdx >= 0 ? globalIdx : 0
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
/** 点击"拍照签名信息"区域中的 FileImageGroup:合并到全量列表后打开预览 */
|
|
562
|
+
handleFormFileImageClick ({ index, url, fileData, allUrls }) {
|
|
563
|
+
const urls = allUrls.filter(Boolean)
|
|
564
|
+
if (urls.length > 0) {
|
|
565
|
+
this.handlePreviewPhotos(urls, Math.min(index, urls.length - 1))
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
/** 通过函数 ref 收集"拍照签名信息"区域的 FileImageGroup 实例 */
|
|
570
|
+
registerFormFig (fig) {
|
|
571
|
+
if (!this.formFigInstances) this.formFigInstances = []
|
|
572
|
+
if (fig && this.formFigInstances.indexOf(fig) === -1) {
|
|
573
|
+
this.formFigInstances.push(fig)
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
/** 收集"拍照签名信息"区域中所有 FileImageGroup 的已解析 URL + 表单字段原始 url */
|
|
578
|
+
_collectFormFigPhotoUrls () {
|
|
579
|
+
const urls = []
|
|
580
|
+
;(this.formFigInstances || []).forEach(fig => {
|
|
581
|
+
if (fig && fig.getAllResolvedUrls) {
|
|
582
|
+
fig.getAllResolvedUrls().forEach(u => { if (u && urls.indexOf(u) === -1) urls.push(u) })
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
// 兜底:直接取表单字段中的 url(未通过 FIG 解析的 fileId 在此处无法获取)
|
|
586
|
+
const form = this.inspectionData && this.inspectionData.signatureForm
|
|
587
|
+
if (form && form.formConfig && form.formConfig.formJson) {
|
|
588
|
+
form.formConfig.formJson.forEach(field => {
|
|
589
|
+
if (!this.shouldShowField || !this.shouldShowField(field)) return
|
|
590
|
+
const files = field.type === 'file'
|
|
591
|
+
? (this.getFieldFiles && this.getFieldFiles(field.model) || [])
|
|
592
|
+
: ((field.type === 'image' || field.type === 'signature')
|
|
593
|
+
? (this.getFieldImages && this.getFieldImages(field.model) || [])
|
|
594
|
+
: [])
|
|
595
|
+
files.forEach(f => {
|
|
596
|
+
const u = f && (f.url || f.imageUrl)
|
|
597
|
+
if (u && urls.indexOf(u) === -1) urls.push(u)
|
|
598
|
+
})
|
|
599
|
+
})
|
|
600
|
+
}
|
|
601
|
+
return urls
|
|
602
|
+
},
|
|
603
|
+
|
|
604
|
+
/** 按固定顺序构建全量图片列表:附件 → 子组件(隐患、设备、AI、检查项)→ 签名表单 */
|
|
605
|
+
_rebuildAllPhotoList () {
|
|
606
|
+
const list = []
|
|
607
|
+
const att = this.inspectionData.attachments
|
|
608
|
+
if (att) {
|
|
609
|
+
if (att.signature?.imageUrl && list.indexOf(att.signature.imageUrl) === -1) list.push(att.signature.imageUrl)
|
|
610
|
+
if (Array.isArray(att.refusephotos)) {
|
|
611
|
+
att.refusephotos.forEach(p => { if (p?.imageUrl && list.indexOf(p.imageUrl) === -1) list.push(p.imageUrl) })
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
;['hazardPhotos', 'deviceStatus', 'aiDeviceStatus', 'checkItems'].forEach(ref => {
|
|
615
|
+
const child = this.$refs[ref]
|
|
616
|
+
if (child && child.getAllPhotoUrls) {
|
|
617
|
+
const urls = child.getAllPhotoUrls()
|
|
618
|
+
urls.forEach(u => { if (u && list.indexOf(u) === -1) list.push(u) })
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
const formUrls = this._collectFormFigPhotoUrls()
|
|
622
|
+
formUrls.forEach(u => { if (u && list.indexOf(u) === -1) list.push(u) })
|
|
623
|
+
return list
|
|
624
|
+
},
|
|
625
|
+
|
|
626
|
+
goPrevPhoto () {
|
|
627
|
+
if (this.previewModal.current > 0) {
|
|
628
|
+
this.previewModal.current--
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
goNextPhoto () {
|
|
633
|
+
if (this.previewModal.current < this.allPhotoList.length - 1) {
|
|
634
|
+
this.previewModal.current++
|
|
487
635
|
}
|
|
488
636
|
},
|
|
489
637
|
|
|
@@ -619,6 +767,8 @@ export default {
|
|
|
619
767
|
hazardList: [],
|
|
620
768
|
attachments: { signature: null, recordings: [], filePhotos: [], imagePhotos: [] }
|
|
621
769
|
}
|
|
770
|
+
this.allPhotoList = []
|
|
771
|
+
this.formFigInstances = []
|
|
622
772
|
},
|
|
623
773
|
|
|
624
774
|
/**
|
|
@@ -832,4 +982,41 @@ export default {
|
|
|
832
982
|
}
|
|
833
983
|
}
|
|
834
984
|
}
|
|
985
|
+
|
|
986
|
+
/* 预览模态框底部缩略图条 */
|
|
987
|
+
.thumbnail-strip {
|
|
988
|
+
display: flex;
|
|
989
|
+
justify-content: center;
|
|
990
|
+
gap: 6px;
|
|
991
|
+
margin-top: 12px;
|
|
992
|
+
padding: 0 20px;
|
|
993
|
+
flex-wrap: nowrap;
|
|
994
|
+
overflow-x: auto;
|
|
995
|
+
max-width: 100vw;
|
|
996
|
+
|
|
997
|
+
&::-webkit-scrollbar { height: 4px; }
|
|
998
|
+
&::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.3); border-radius: 2px; }
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.thumbnail-item {
|
|
1002
|
+
flex-shrink: 0;
|
|
1003
|
+
width: 48px;
|
|
1004
|
+
height: 48px;
|
|
1005
|
+
border-radius: 4px;
|
|
1006
|
+
overflow: hidden;
|
|
1007
|
+
cursor: pointer;
|
|
1008
|
+
border: 2px solid transparent;
|
|
1009
|
+
opacity: 0.6;
|
|
1010
|
+
transition: all 0.2s ease;
|
|
1011
|
+
|
|
1012
|
+
&:hover { opacity: 0.9; border-color: rgba(255,255,255,0.4); }
|
|
1013
|
+
&.active { opacity: 1; border-color: #1890ff; }
|
|
1014
|
+
|
|
1015
|
+
img {
|
|
1016
|
+
width: 100%;
|
|
1017
|
+
height: 100%;
|
|
1018
|
+
object-fit: cover;
|
|
1019
|
+
display: block;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
835
1022
|
</style>
|