|
|
@@ -48,7 +48,7 @@
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="编辑" placement="top">
|
|
|
- <span class="action-icon" @click="editSelected">
|
|
|
+ <span class="action-icon" @click="editSelected('tabulation')">
|
|
|
<el-icon><Edit /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
@@ -95,7 +95,7 @@
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="编辑" placement="top">
|
|
|
- <span class="action-icon" @click="editSelected">
|
|
|
+ <span class="action-icon" @click="editSelected('overview')">
|
|
|
<el-icon><Edit /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
@@ -111,13 +111,8 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <!-- 地图选择弹窗 -->
|
|
|
- <CreatMap
|
|
|
- v-model="showMapDialog"
|
|
|
- :caseId="caseId"
|
|
|
- @confirm="handleMapConfirm"
|
|
|
- />
|
|
|
- </template>
|
|
|
+
|
|
|
+ </template>
|
|
|
|
|
|
<!-- 勘验笔录:列表 -->
|
|
|
<template v-else-if="activeTab === 'inspection'">
|
|
|
@@ -140,17 +135,17 @@
|
|
|
<span class="name">{{ rec.title }}</span>
|
|
|
<div class="header-actions" v-if="editOrShow === 'edit'">
|
|
|
<el-tooltip content="下载" placement="top">
|
|
|
- <span class="action-icon" @click="renameSelected">
|
|
|
+ <span class="action-icon" @click="downloadSelected">
|
|
|
<el-icon><Download /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="编辑" placement="top">
|
|
|
- <span class="action-icon" @click="editSelected">
|
|
|
+ <span class="action-icon" @click="onEditInspection">
|
|
|
<el-icon><EditPen /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="删除" placement="top">
|
|
|
- <span class="action-icon" @click="deleteSelected">
|
|
|
+ <span class="action-icon" @click="deleteItem">
|
|
|
<el-icon><CircleClose /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
@@ -168,8 +163,8 @@
|
|
|
<div class="block-header">
|
|
|
<!-- <div class="title">提取清单</div> -->
|
|
|
<div class="actions" v-if="editOrShow === 'edit'">
|
|
|
- <div class="action-item" @click="onFillInspection"><el-icon size="16"><Edit /></el-icon>填写</div>
|
|
|
- <div class="action-item" @click="onUploadInspection"><el-icon size="16"><Upload /></el-icon>上传</div>
|
|
|
+ <div class="action-item" @click="onFillExtraction"><el-icon size="16"><Edit /></el-icon>填写</div>
|
|
|
+ <div class="action-item" @click="onUploadExtraction"><el-icon size="16"><Upload /></el-icon>上传</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="block-body">
|
|
|
@@ -183,17 +178,17 @@
|
|
|
<span class="name">{{ rec.title }}</span>
|
|
|
<div class="header-actions" v-if="editOrShow === 'edit'">
|
|
|
<el-tooltip content="下载" placement="top">
|
|
|
- <span class="action-icon" @click="renameSelected">
|
|
|
+ <span class="action-icon" @click="downloadSelected">
|
|
|
<el-icon><Download /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="编辑" placement="top">
|
|
|
- <span class="action-icon" @click="editSelected">
|
|
|
+ <span class="action-icon" @click="onEditExtraction">
|
|
|
<el-icon><EditPen /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="删除" placement="top">
|
|
|
- <span class="action-icon" @click="deleteSelected">
|
|
|
+ <span class="action-icon" @click="deleteItem">
|
|
|
<el-icon><CircleClose /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
@@ -209,9 +204,9 @@
|
|
|
<template v-else-if="activeTab === 'album'">
|
|
|
<div class="content-block">
|
|
|
<div class="block-header">
|
|
|
- <div class="title">照片卷</div>
|
|
|
+ <!-- <div class="title">照片卷</div> -->
|
|
|
<div class="actions" v-if="editOrShow === 'edit'">
|
|
|
- <div class="action-item" @click="onFillInspection"><el-icon size="16"><Edit /></el-icon>制卷</div>
|
|
|
+ <div class="action-item" @click="openPhotoEdit()"><el-icon size="16"><Edit /></el-icon>制卷</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="block-body">
|
|
|
@@ -225,17 +220,17 @@
|
|
|
<span class="name">{{ alb.title }}</span>
|
|
|
<div class="header-actions" v-if="editOrShow === 'edit'">
|
|
|
<el-tooltip content="下载" placement="top">
|
|
|
- <span class="action-icon" @click="renameSelected">
|
|
|
+ <span class="action-icon" @click="downloadSelected">
|
|
|
<el-icon><Download /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="编辑" placement="top">
|
|
|
- <span class="action-icon" @click="editSelected">
|
|
|
+ <span class="action-icon" @click="openPhotoEdit(alb.id)">
|
|
|
<el-icon><EditPen /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
<el-tooltip content="删除" placement="top">
|
|
|
- <span class="action-icon" @click="deleteSelected">
|
|
|
+ <span class="action-icon" @click="deleteItem">
|
|
|
<el-icon><CircleClose /></el-icon>
|
|
|
</span>
|
|
|
</el-tooltip>
|
|
|
@@ -286,18 +281,10 @@
|
|
|
<div v-else class="viewer-placeholder">暂无提取清单</div>
|
|
|
</template>
|
|
|
|
|
|
- <!-- 照片卷右侧:图片网格,可点击放大 -->
|
|
|
+ <!-- 照片卷右侧:单图预览,可点击放大 -->
|
|
|
<template v-else-if="activeTab === 'album'">
|
|
|
- <div v-if="currentAlbum" class="album-grid">
|
|
|
- <div
|
|
|
- class="album-image"
|
|
|
- v-for="(img, idx) in currentAlbum.images"
|
|
|
- :key="img.url + idx"
|
|
|
- @click="openAlbumViewer(idx)"
|
|
|
- >
|
|
|
- <el-image :src="img.url" fit="cover" />
|
|
|
- <div class="caption">{{ img.name || ('照片 ' + (idx + 1)) }}</div>
|
|
|
- </div>
|
|
|
+ <div v-if="currentAlbum" class="scene-image" @click="openAlbumViewer(0)">
|
|
|
+ <el-image :src="currentAlbum.images[0].url" fit="contain" class="inline-image" />
|
|
|
</div>
|
|
|
<div v-else class="viewer-placeholder">暂无照片卷</div>
|
|
|
<el-image-viewer
|
|
|
@@ -312,15 +299,17 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, watch, onMounted } from 'vue';
|
|
|
+import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
-import CreatMap from '@/view/case/drawMap/creatMap.vue';
|
|
|
+// 弹窗已移除,直接跳转到绘制页面
|
|
|
import { getCaseFiles, CaseFile, BoardType, setCaseFile, delCaseFile } from '@/store/caseFile';
|
|
|
import { FileDrawType } from '@/constant/caseFile';
|
|
|
import { addCaseFile as addCaseFileDialog } from '@/view/case/quisk';
|
|
|
import { router, RouteName } from '@/router';
|
|
|
import { user } from '@/store/user';
|
|
|
-import { getCaseInquestInfo, getCaseDetailInfo } from '@/store/case';
|
|
|
+import { getCaseInquestInfo, getCaseDetailInfo, getFfmpegImageList, exportCaseInquestInfo, exportCaseDetailInfo, caseDel } from '@/store/case';
|
|
|
+import { axios } from '@/request';
|
|
|
+import { saveAs } from '@/util/file-serve';
|
|
|
|
|
|
const appId = import.meta.env.VITE_APP_APP || 'fire';
|
|
|
const url = import.meta.env.VITE_DRAW_URL || 'http://mix3d.4dkankan.com';
|
|
|
@@ -441,28 +430,20 @@ const renameSelected = async () => {
|
|
|
};
|
|
|
|
|
|
// 编辑当前选中(根据类型跳转)
|
|
|
-const editSelected = () => {
|
|
|
+const editSelected = (type) => {
|
|
|
const file = currentSelectedFile.value as any;
|
|
|
if (!file) {
|
|
|
ElMessage.warning('请先在左侧列表选择一个文件');
|
|
|
return;
|
|
|
}
|
|
|
+ console.log('file', type, file);
|
|
|
// 新版绘图:tabulation/overview 分开处理;旧数据走 RouteName.drawCaseFile
|
|
|
- if (file.type === 'tabulation' && file.tabulationId) {
|
|
|
- if (appId === 'fire') {
|
|
|
- window.open(`${url}/draw/fire/index.html#/tabulation?caseId=${caseId.value}&tabulationId=${file.tabulationId}&token=${user.value.token}`, '_blank');
|
|
|
- } else if (appId === 'criminal') {
|
|
|
- window.open(`${url}/draw/criminal/index.html#/tabulation?caseId=${caseId.value}&tabulationId=${file.tabulationId}&token=${user.value.token}`, '_blank');
|
|
|
- } else if (appId === 'cjzfire') {
|
|
|
- window.open(`${url}/draw/cjzfire/index.html#/tabulation?caseId=${caseId.value}&tabulationId=${file.tabulationId}&token=${user.value.token}`, '_blank');
|
|
|
- } else if (appId === 'xmfire') {
|
|
|
- window.open(`${url}/draw/xmfire/index.html#/tabulation?caseId=${caseId.value}&tabulationId=${file.tabulationId}&token=${user.value.token}`, '_blank');
|
|
|
- } else {
|
|
|
- window.open(`${url}/draw/fire/index.html#/tabulation?caseId=${caseId.value}&tabulationId=${file.tabulationId}&token=${user.value.token}`, '_blank');
|
|
|
- }
|
|
|
+ if (type === 'tabulation' && file.tabulationId) {
|
|
|
+ console.log(1111)
|
|
|
+ openTabulation(file.tabulationId);
|
|
|
return;
|
|
|
}
|
|
|
- if (file.type === 'overview' && file.overviewId) {
|
|
|
+ if (type === 'overview' && file.overviewId) {
|
|
|
if (appId === 'fire') {
|
|
|
window.open(`${url}/draw/fire/index.html#/overview?caseId=${caseId.value!}&overviewId=${file.overviewId}&token=${user.value.token}`, '_blank');
|
|
|
} else if (appId === 'criminal') {
|
|
|
@@ -525,12 +506,56 @@ const inquestData = ref<any>({
|
|
|
],
|
|
|
});
|
|
|
|
|
|
-// 左侧列表使用的摘要
|
|
|
+// 左侧列表使用的摘要 + 原始数据
|
|
|
const inspectionList = ref<{ title: string; content: string }[]>([]);
|
|
|
+const inquestRawList = ref<any[]>([]);
|
|
|
const selectedInspectionIndex = ref(0);
|
|
|
const currentInspection = computed(() => inspectionList.value[selectedInspectionIndex.value]);
|
|
|
-const selectInspection = (idx: number) => (selectedInspectionIndex.value = idx);
|
|
|
-
|
|
|
+const selectInspection = (idx: number) => {
|
|
|
+ selectedInspectionIndex.value = idx;
|
|
|
+ // 同步当前原始数据,供编辑页预填
|
|
|
+ inquestData.value = inquestRawList.value[idx] || {};
|
|
|
+};
|
|
|
+// 勘验笔录:拉取并生成预览文本
|
|
|
+const loadInspection = async () => {
|
|
|
+ if (!caseId.value) return;
|
|
|
+ try {
|
|
|
+ const res: any = await getCaseInquestInfo(caseId.value!);
|
|
|
+ const payload = res?.data ?? [];
|
|
|
+ if (Array.isArray(payload)) {
|
|
|
+ // 新格式:数组,每条记录生成一个列表项
|
|
|
+ inquestRawList.value = payload;
|
|
|
+ inspectionList.value = payload.map((item: any, idx: number) => ({
|
|
|
+ title: item?.count ? `勘验笔录(第${item.count}次)` : `勘验笔录 ${idx + 1}`,
|
|
|
+ content: formatInquest(item),
|
|
|
+ }));
|
|
|
+ // 默认选中第一条,并同步右侧与预填原始数据
|
|
|
+ if (inspectionList.value.length) {
|
|
|
+ selectedInspectionIndex.value = 0;
|
|
|
+ inquestData.value = inquestRawList.value[0] || {};
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 兼容旧格式:单对象
|
|
|
+ const content = formatInquest(payload || {});
|
|
|
+ inquestRawList.value = [payload || {}];
|
|
|
+ if (!inspectionList.value.length) {
|
|
|
+ inspectionList.value = [{ title: '勘验笔录', content }];
|
|
|
+ selectedInspectionIndex.value = 0;
|
|
|
+ inquestData.value = inquestRawList.value[0];
|
|
|
+ } else {
|
|
|
+ inspectionList.value[selectedInspectionIndex.value] = {
|
|
|
+ title: inspectionList.value[selectedInspectionIndex.value]?.title || '勘验笔录',
|
|
|
+ content,
|
|
|
+ };
|
|
|
+ inquestData.value = inquestRawList.value[selectedInspectionIndex.value] || {};
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取勘验笔录失败:', e);
|
|
|
+ inspectionList.value = [];
|
|
|
+ inquestRawList.value = [];
|
|
|
+ }
|
|
|
+};
|
|
|
// 将接口数据格式化为预览文本
|
|
|
const formatInquest = (d: any) => {
|
|
|
const s = d.startTime || {}; const e = d.endTime || {};
|
|
|
@@ -554,25 +579,46 @@ const formatInquest = (d: any) => {
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-// 调取接口并填充列表
|
|
|
-const loadInspection = async () => {
|
|
|
+// 调取接口并填充列表(提取清单)
|
|
|
+const loadExtraction = async () => {
|
|
|
if (!caseId.value) return;
|
|
|
try {
|
|
|
- const res: any = await getCaseInquestInfo(caseId.value!);
|
|
|
- const payload = res?.data || {};
|
|
|
- // 将返回数据写入到 inquestData
|
|
|
- const target = inquestData.value;
|
|
|
- for (const k in target) {
|
|
|
- if (Object.prototype.hasOwnProperty.call(payload, k)) {
|
|
|
- (target as any)[k] = payload[k];
|
|
|
+ const res: any = await getCaseDetailInfo(caseId.value!);
|
|
|
+ const payload = res?.data ?? [];
|
|
|
+ if (Array.isArray(payload)) {
|
|
|
+ // 新格式:数组
|
|
|
+ extractionRawList.value = payload;
|
|
|
+ extractionList.value = payload.map((item: any, idx: number) => ({
|
|
|
+ title: item?.address ? `提取清单(${item.address})` : `提取清单 ${idx + 1}`,
|
|
|
+ content: formatExtraction(item),
|
|
|
+ }));
|
|
|
+ if (extractionList.value.length) {
|
|
|
+ selectedExtractionIndex.value = 0;
|
|
|
+ extractionData.value = extractionRawList.value[0] || {};
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 旧格式:单对象
|
|
|
+ const target = extractionData.value;
|
|
|
+ for (const k in target) {
|
|
|
+ if (Object.prototype.hasOwnProperty.call(payload, k)) {
|
|
|
+ (target as any)[k] = (payload as any)[k];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const content = formatExtraction(target);
|
|
|
+ if (!extractionList.value.length) {
|
|
|
+ extractionList.value = [{ title: '提取清单', content }];
|
|
|
+ selectedExtractionIndex.value = 0;
|
|
|
+ } else {
|
|
|
+ extractionList.value[selectedExtractionIndex.value] = {
|
|
|
+ title: extractionList.value[selectedExtractionIndex.value]?.title || '提取清单',
|
|
|
+ content,
|
|
|
+ };
|
|
|
}
|
|
|
}
|
|
|
- const content = formatInquest(target);
|
|
|
- inspectionList.value = [{ title: '勘验笔录', content }];
|
|
|
- selectedInspectionIndex.value = 0;
|
|
|
} catch (e) {
|
|
|
- console.error('获取勘验笔录失败:', e);
|
|
|
- inspectionList.value = [];
|
|
|
+ console.error('获取提取清单失败:', e);
|
|
|
+ extractionList.value = [];
|
|
|
+ extractionRawList.value = [];
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -629,61 +675,23 @@ const formatExtraction = (d: any) => {
|
|
|
};
|
|
|
|
|
|
const extractionList = ref<{ title: string; content: string }[]>([]);
|
|
|
+const extractionRawList = ref<any[]>([]);
|
|
|
const selectedExtractionIndex = ref(0);
|
|
|
const currentExtraction = computed(() => extractionList.value[selectedExtractionIndex.value]);
|
|
|
const selectExtraction = (idx: number) => (selectedExtractionIndex.value = idx);
|
|
|
|
|
|
-// 复原接口逻辑:拉取并写入 extractionData,然后生成预览文本
|
|
|
-const loadExtraction = async () => {
|
|
|
- if (!caseId.value) return;
|
|
|
- try {
|
|
|
- const res: any = await getCaseDetailInfo(caseId.value!);
|
|
|
- const payload = res?.data || {};
|
|
|
- const target = extractionData.value;
|
|
|
- for (const k in target) {
|
|
|
- if (Object.prototype.hasOwnProperty.call(payload, k)) {
|
|
|
- (target as any)[k] = payload[k];
|
|
|
- }
|
|
|
- }
|
|
|
- const content = formatExtraction(target);
|
|
|
- // 左侧列表:如果为空则初始化一个;否则更新当前项内容
|
|
|
- if (!extractionList.value.length) {
|
|
|
- extractionList.value = [{ title: '提取清单', content }];
|
|
|
- selectedExtractionIndex.value = 0;
|
|
|
- } else {
|
|
|
- extractionList.value[selectedExtractionIndex.value] = {
|
|
|
- title: extractionList.value[selectedExtractionIndex.value]?.title || '提取清单',
|
|
|
- content,
|
|
|
- };
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.error('获取提取清单失败:', e);
|
|
|
- if (!extractionList.value.length) extractionList.value = [];
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// 切换多个提取清单项时,重新调用接口更新内容
|
|
|
+// 切换多个提取清单项时,同步当前原始数据用于编辑预填
|
|
|
watch(selectedExtractionIndex, () => {
|
|
|
if (activeTab.value === 'extraction') {
|
|
|
- loadExtraction();
|
|
|
+ extractionData.value = extractionRawList.value[selectedExtractionIndex.value] || {};
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 照片卷数据与交互
|
|
|
-const albumList = ref<{ title: string; images: PicItem[] }[]>([
|
|
|
- {
|
|
|
- title: '照片卷 1',
|
|
|
- images: [
|
|
|
- { url: '/jmlogo.png', name: '现场照片 1' },
|
|
|
- { url: '/favicon.ico', name: '现场照片 2' },
|
|
|
- { url: '/fire.ico', name: '现场照片 3' },
|
|
|
- { url: '/police.ico', name: '现场照片 4' },
|
|
|
- { url: '/logo_big.ico', name: '现场照片 5' },
|
|
|
- ],
|
|
|
- },
|
|
|
-]);
|
|
|
+const albumList = ref<{ id?: number; title: string; images: PicItem[] }[]>([]);
|
|
|
const selectedAlbumIndex = ref(0);
|
|
|
const currentAlbum = computed(() => albumList.value[selectedAlbumIndex.value]);
|
|
|
+console.log(currentAlbum.value, 999)
|
|
|
const selectAlbum = (idx: number) => (selectedAlbumIndex.value = idx);
|
|
|
const openAlbumViewer = (index: number) => {
|
|
|
const list = currentAlbum.value?.images || [];
|
|
|
@@ -692,6 +700,94 @@ const openAlbumViewer = (index: number) => {
|
|
|
showViewer.value = true;
|
|
|
};
|
|
|
|
|
|
+// 照片制卷列表:改用 getFfmpegImageList 接口获取
|
|
|
+const loadAlbum = async () => {
|
|
|
+ if (!caseId.value) return;
|
|
|
+ try {
|
|
|
+ const res: any = await getFfmpegImageList(caseId.value!);
|
|
|
+ const list: any[] = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.list) ? res.data.list : [];
|
|
|
+ albumList.value = list.map((item: any, idx: number) => {
|
|
|
+ const url = item.imgUrl || item.filesUrl || item.url;
|
|
|
+ const name = item.imgInfo || item.filesTitle || item.name || `照片卷 ${idx + 1}`;
|
|
|
+ return { id: item.id, title: name, images: url ? [{ url, name }] : [] };
|
|
|
+ });
|
|
|
+ // 若无数据,置空;否则默认选中第一项
|
|
|
+ selectedAlbumIndex.value = albumList.value.length ? 0 : 0;
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取照片卷失败:', e);
|
|
|
+ albumList.value = [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 统一下载:按当前 tab 类型以 blob 方式下载
|
|
|
+const downloadSelected = async () => {
|
|
|
+ try {
|
|
|
+ if (activeTab.value === 'inspection') {
|
|
|
+ if (!caseId.value) return;
|
|
|
+ const title = (inspectionList.value[selectedInspectionIndex.value]?.title || '勘验笔录') + '.docx';
|
|
|
+ const blob: Blob = await (exportCaseInquestInfo(caseId.value!) as any);
|
|
|
+ await saveAs(blob, title);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (activeTab.value === 'extraction') {
|
|
|
+ if (!caseId.value) return;
|
|
|
+ const title = (extractionList.value[selectedExtractionIndex.value]?.title || '提取清单') + '.docx';
|
|
|
+ const blob: Blob = await (exportCaseDetailInfo(caseId.value!) as any);
|
|
|
+ await saveAs(blob, title);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (activeTab.value === 'album') {
|
|
|
+ const img = currentAlbum.value?.images?.[0];
|
|
|
+ if (!img?.url) {
|
|
|
+ ElMessage.warning('当前照片卷暂无图片可下载');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const resp: any = await axios.get(img.url, { responseType: 'blob', params: { ingoreRes: true } });
|
|
|
+ await saveAs(resp as Blob, (currentAlbum.value?.title || '照片卷') + '.jpg');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (activeTab.value === 'scene') {
|
|
|
+ const url = currentSceneImageUrl.value;
|
|
|
+ if (!url) {
|
|
|
+ ElMessage.warning('请先选择一张现场图');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const resp: any = await axios.get(url, { responseType: 'blob', params: { ingoreRes: true } });
|
|
|
+ await saveAs(resp as Blob, '现场图.jpg');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('下载失败:', e);
|
|
|
+ ElMessage.error('下载失败,请稍后重试');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 统一删除:照片卷调用 caseDel,其他沿用原逻辑或不支持
|
|
|
+const deleteItem = async () => {
|
|
|
+ try {
|
|
|
+ if (activeTab.value === 'album') {
|
|
|
+ const alb = currentAlbum.value;
|
|
|
+ if (!alb?.id) {
|
|
|
+ ElMessage.warning('未选中照片卷或缺少ID');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await ElMessageBox.confirm('确定删除当前照片卷?删除后不可恢复。', '删除确认', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning' });
|
|
|
+ await caseDel(alb.id);
|
|
|
+ ElMessage.success('已删除');
|
|
|
+ await loadAlbum();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (activeTab.value === 'scene') {
|
|
|
+ // 现场图删除沿用原有逻辑
|
|
|
+ await deleteSelected();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ElMessage.info('当前列表暂不支持删除');
|
|
|
+ } catch (e) {
|
|
|
+ // 取消或失败不提示错误
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// Tab 切换时默认选中第一个项,并关闭任何图片查看器
|
|
|
watch(activeTab, (val) => {
|
|
|
if (val === 'scene') {
|
|
|
@@ -714,6 +810,8 @@ watch(activeTab, (val) => {
|
|
|
showViewer.value = false;
|
|
|
if (val === 'inspection' && inspectionList.value.length) {
|
|
|
selectedInspectionIndex.value = 0;
|
|
|
+ // 同步当前原始数据
|
|
|
+ inquestData.value = inquestRawList.value[0] || {};
|
|
|
} else if (val === 'extraction' && extractionList.value.length) {
|
|
|
selectedExtractionIndex.value = 0;
|
|
|
} else if (val === 'album' && albumList.value.length) {
|
|
|
@@ -722,10 +820,21 @@ watch(activeTab, (val) => {
|
|
|
}
|
|
|
}, { immediate: true });
|
|
|
|
|
|
-// 地图选择弹窗与新增逻辑
|
|
|
-const showMapDialog = ref(false);
|
|
|
-const openMapDialog = () => {
|
|
|
- showMapDialog.value = true;
|
|
|
+// 直接打开方位图绘制页面(tabulation),支持传入已选项的 tabulationId
|
|
|
+const openTabulation = (tabulationId?: string | number) => {
|
|
|
+ if (!caseId.value) return;
|
|
|
+ const extra = tabulationId ? `&tabulationId=${tabulationId}` : '';
|
|
|
+ if (appId === 'fire') {
|
|
|
+ window.open(`${url}/draw/fire/index.html#/tabulation?caseId=${caseId.value!}&token=${user.value.token}${extra}`, '_blank');
|
|
|
+ } else if (appId === 'criminal') {
|
|
|
+ window.open(`${url}/draw/criminal/index.html#/tabulation?caseId=${caseId.value!}&token=${user.value.token}${extra}`, '_blank');
|
|
|
+ } else if (appId === 'cjzfire') {
|
|
|
+ window.open(`${url}/draw/cjzfire/index.html#/tabulation?caseId=${caseId.value!}&token=${user.value.token}${extra}`, '_blank');
|
|
|
+ } else if (appId === 'xmfire') {
|
|
|
+ window.open(`${url}/draw/xmfire/index.html#/tabulation?caseId=${caseId.value!}&token=${user.value.token}${extra}`, '_blank');
|
|
|
+ } else {
|
|
|
+ window.open(`${url}/draw/fire/index.html#/tabulation?caseId=${caseId.value!}&token=${user.value.token}${extra}`, '_blank');
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
// 新增平面图(overview)
|
|
|
@@ -762,7 +871,7 @@ const onAdd = (type: 'plane' | 'orientation') => {
|
|
|
if (type === 'plane') {
|
|
|
openOverView();
|
|
|
} else {
|
|
|
- openMapDialog();
|
|
|
+ openTabulation();
|
|
|
}
|
|
|
};
|
|
|
const onUpload = async (_type: 'plane' | 'orientation') => {
|
|
|
@@ -771,19 +880,107 @@ const onUpload = async (_type: 'plane' | 'orientation') => {
|
|
|
await refreshSceneFiles();
|
|
|
};
|
|
|
|
|
|
-const onFillInspection = () => ElMessage.info('勘验笔录填写功能未接入');
|
|
|
+// 打开覆盖式编辑页(编辑当前项):勘验笔录/提取清单
|
|
|
+const openEditOverlay = (type: 'inquest' | 'extraction') => {
|
|
|
+ const current = router.currentRoute.value;
|
|
|
+ // 勘验笔录仍走原有 records 编辑页;提取清单改为新的 editInspection 页
|
|
|
+ const editSub = type === 'extraction' ? 'editInspection' : 'records';
|
|
|
+ const query: any = { ...current.query, editSub, type };
|
|
|
+ if (type === 'extraction') {
|
|
|
+ // 使用 sessionStorage 传递预填数据,避免 URL 过长
|
|
|
+ try {
|
|
|
+ const key = `extractionPreset:${caseId.value!}`;
|
|
|
+ const currentItem = extractionData.value || {};
|
|
|
+ sessionStorage.setItem(key, JSON.stringify(currentItem));
|
|
|
+ query.presetKey = String(caseId.value!);
|
|
|
+ if (currentItem && currentItem.id !== undefined) {
|
|
|
+ query.id = String(currentItem.id);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('保存预填数据失败', e);
|
|
|
+ }
|
|
|
+ } else if (type === 'inquest') {
|
|
|
+ // 勘验笔录:传递当前选中项内容与ID,用于编辑页预填与更新
|
|
|
+ try {
|
|
|
+ const key = `inquestPreset:${caseId.value!}`;
|
|
|
+ const currentItem = inquestData.value || {};
|
|
|
+ sessionStorage.setItem(key, JSON.stringify(currentItem));
|
|
|
+ query.presetKey = String(caseId.value!);
|
|
|
+ if (currentItem && currentItem.id !== undefined) {
|
|
|
+ query.id = String(currentItem.id);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('保存勘验预填数据失败', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ router.push({
|
|
|
+ name: current.name || RouteName.fireDetails,
|
|
|
+ params: current.params,
|
|
|
+ query,
|
|
|
+ });
|
|
|
+};
|
|
|
+// 打开覆盖式编辑页(新增空白):不携带任何信息
|
|
|
+const openAddOverlay = (type: 'inquest' | 'extraction') => {
|
|
|
+ const current = router.currentRoute.value;
|
|
|
+ const editSub = type === 'extraction' ? 'editInspection' : 'records';
|
|
|
+ // 基于当前查询构建,但显式清理会导致回显的参数
|
|
|
+ const query: any = { ...current.query, editSub, type };
|
|
|
+ // 清理上一次编辑可能遗留的预填参数
|
|
|
+ if ('presetKey' in query) delete query.presetKey;
|
|
|
+ if ('id' in query) delete query.id;
|
|
|
+ // 不设置 presetKey 或 id,编辑页将保持空白
|
|
|
+ router.push({
|
|
|
+ name: current.name || RouteName.fireDetails,
|
|
|
+ params: current.params,
|
|
|
+ query,
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const onFillInspection = () => openAddOverlay('inquest');
|
|
|
const onUploadInspection = () => ElMessage.info('勘验笔录上传功能未接入');
|
|
|
-const onFillExtraction = () => ElMessage.info('提取清单填写功能未接入');
|
|
|
+const onFillExtraction = () => openAddOverlay('extraction');
|
|
|
+const onEditInspection = () => openEditOverlay('inquest');
|
|
|
+const onEditExtraction = () => openEditOverlay('extraction');
|
|
|
const onUploadExtraction = () => ElMessage.info('提取清单上传功能未接入');
|
|
|
-const onEditAlbum = () => ElMessage.info('照片卷编纂功能未接入');
|
|
|
+const onEditAlbum = () => openPhotoEdit();
|
|
|
const onUploadAlbum = () => ElMessage.info('照片卷上传功能未接入');
|
|
|
|
|
|
+// 打开照片卷编纂页(photoEdit.vue),支持传递 parentId
|
|
|
+const openPhotoEdit = (imgId?: number) => {
|
|
|
+ const current = router.currentRoute.value;
|
|
|
+ const query: any = { ...current.query, editSub: 'photoEdit' };
|
|
|
+ // 编辑按钮:携带 imgId 与 parentId;制卷按钮:parentId 为空
|
|
|
+ if (typeof imgId === 'number' && Number.isFinite(imgId)) {
|
|
|
+ query.imgId = String(imgId);
|
|
|
+ query.parentId = String(imgId);
|
|
|
+ } else {
|
|
|
+ delete query.imgId;
|
|
|
+ delete query.parentId;
|
|
|
+ }
|
|
|
+ router.push({
|
|
|
+ name: current.name || RouteName.fireDetails,
|
|
|
+ params: current.params,
|
|
|
+ query,
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
refreshSceneFiles();
|
|
|
loadInspection();
|
|
|
loadExtraction();
|
|
|
+ loadAlbum();
|
|
|
+ // 监听返回刷新事件,仅刷新勘验笔录、提取清单与照片卷
|
|
|
+ const refreshListsHandler = () => {
|
|
|
+ loadInspection();
|
|
|
+ loadExtraction();
|
|
|
+ loadAlbum();
|
|
|
+ };
|
|
|
+ window.addEventListener('fireDetails:refreshLists', refreshListsHandler as any);
|
|
|
+ onUnmounted(() => {
|
|
|
+ window.removeEventListener('fireDetails:refreshLists', refreshListsHandler as any);
|
|
|
+ });
|
|
|
});
|
|
|
-watch(caseId, () => { refreshSceneFiles(); loadInspection(); loadExtraction(); });
|
|
|
+watch(caseId, () => { refreshSceneFiles(); loadInspection(); loadExtraction(); loadAlbum(); });
|
|
|
</script>
|
|
|
<style lang="scss" scoped>
|
|
|
.scene-3dmix {
|
|
|
@@ -998,16 +1195,14 @@ watch(caseId, () => { refreshSceneFiles(); loadInspection(); loadExtraction(); }
|
|
|
}
|
|
|
}
|
|
|
.album-grid {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(4, 1fr);
|
|
|
- gap: 12px;
|
|
|
- padding: 16px;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: #F5F5F5;
|
|
|
}
|
|
|
.album-image {
|
|
|
- border: 1px solid #f0f0f0;
|
|
|
+ height: 100%;
|
|
|
border-radius: 4px;
|
|
|
overflow: hidden;
|
|
|
- background: #fafafa;
|
|
|
cursor: pointer;
|
|
|
.caption {
|
|
|
padding: 6px 8px;
|
|
|
@@ -1015,6 +1210,11 @@ watch(caseId, () => { refreshSceneFiles(); loadInspection(); loadExtraction(); }
|
|
|
color: rgba(0,0,0,.65);
|
|
|
text-align: center;
|
|
|
}
|
|
|
+ :deep(img){
|
|
|
+ height: 100%;
|
|
|
+ width: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|