yangyuxuan 2 月之前
父節點
當前提交
757d9ffdf5

+ 207 - 0
package-lock.json

@@ -51,6 +51,7 @@
         "vue-chartjs": "^5.3.1",
         "vue-cropper": "1.1.1",
         "vue-i18n": "9.10.2",
+        "vue-pdf-embed": "^2.1.2",
         "vue-router": "4.3.2",
         "vue-types": "5.1.1",
         "vue3-print-nb": "^0.1.4",
@@ -1526,6 +1527,188 @@
       "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
       "peer": true
     },
+    "node_modules/@napi-rs/canvas": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas/-/canvas-0.1.67.tgz",
+      "integrity": "sha512-VA4Khm/5Kg2bQGx3jXotTC4MloOG8b1Ung80exafUK0k5u6yJmIz3Q2iXeeWZs5weV+LQOEB+CPKsYwEYaGAjw==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 10"
+      },
+      "optionalDependencies": {
+        "@napi-rs/canvas-android-arm64": "0.1.67",
+        "@napi-rs/canvas-darwin-arm64": "0.1.67",
+        "@napi-rs/canvas-darwin-x64": "0.1.67",
+        "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.67",
+        "@napi-rs/canvas-linux-arm64-gnu": "0.1.67",
+        "@napi-rs/canvas-linux-arm64-musl": "0.1.67",
+        "@napi-rs/canvas-linux-riscv64-gnu": "0.1.67",
+        "@napi-rs/canvas-linux-x64-gnu": "0.1.67",
+        "@napi-rs/canvas-linux-x64-musl": "0.1.67",
+        "@napi-rs/canvas-win32-x64-msvc": "0.1.67"
+      }
+    },
+    "node_modules/@napi-rs/canvas-android-arm64": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.67.tgz",
+      "integrity": "sha512-W+3DFG5h0WU8Vqqb3W5fNmm5/TPH5ECZRinQDK4CAKFSUkc4iZcDwrmyFG9sB4KdHazf1mFVHCpEeVMO6Mk6Zg==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-darwin-arm64": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.67.tgz",
+      "integrity": "sha512-xzrv7QboI47yhIHR5P5u/9KGswokuOKLiKSukr1Ku03RRJxP6lGuVtrAZAgdRg7F9FsuF2REf2yK53YVb6pMlA==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-darwin-x64": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.67.tgz",
+      "integrity": "sha512-SNk9lYBr84N0gW8MZ2IrjygFtbFBILr3SEqMdHzHHuph20SQmssFvJGPZwSSCMEyKAvyqhogbmlew0te5Z4w9Q==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.67.tgz",
+      "integrity": "sha512-qmBlSvUpl567bzH8tNXi82u5FrL4d0qINqd6K9O7GWGGGFmKMJdrgi2/SW3wwCTxqHBasIDdVWc4KSJfwyaoDQ==",
+      "cpu": [
+        "arm"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-arm64-gnu": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.67.tgz",
+      "integrity": "sha512-k3nAPQefkMeFuJ65Rqdnx92KX1JXQhEKjjWeKsCJB+7sIBgQUWtHo9c3etfVLv5pkWJJDFi/Zc2soNkH3E8dRA==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-arm64-musl": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.67.tgz",
+      "integrity": "sha512-lZwHWR1cCP408l86n3Qbs3X1oFeAYMjJIQvQl1VMZh6wo5PfI+jaZSKBUOd8x44TnVllX9yhLY9unNRztk/sUQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.67.tgz",
+      "integrity": "sha512-PdBC9p6bLHA1W3OdA0vTHj701SB/kioGQ1uCFBRMs5KBCaMLb/H4aNi8uaIUIEvBWnxeAjoNcLU7//q0FxEosw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-x64-gnu": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.67.tgz",
+      "integrity": "sha512-kJJX6eWzjipL/LdKOWCJctc88e5yzuXri8+s0V/lN06OwuLGW62TWS3lvi8qlUrGMOfRGabSWWlB4omhASSB8w==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-x64-musl": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.67.tgz",
+      "integrity": "sha512-jLKiPWGeN6ZzhnaLG7ex7eexsiHJ1mdtPK1qKvETIcu45dApMXyUIHvdL6XWB5gFFtj5ScHzLUxv1vkfPZsoxA==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-win32-x64-msvc": {
+      "version": "0.1.67",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.67.tgz",
+      "integrity": "sha512-K/JmkOFbc4iRZYUqJhj0jwqfHA/wNQEmTiGNsgZ6d59yF/IBNp5T0D5eg3B8ghjI8GxDYCiSJ6DNX8mC3Oh2EQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -10394,6 +10577,18 @@
         "pbf": "bin/pbf"
       }
     },
+    "node_modules/pdfjs-dist": {
+      "version": "4.10.38",
+      "resolved": "https://registry.npmmirror.com/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz",
+      "integrity": "sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=20"
+      },
+      "optionalDependencies": {
+        "@napi-rs/canvas": "^0.1.65"
+      }
+    },
     "node_modules/perfect-debounce": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -13496,6 +13691,18 @@
         "url": "https://github.com/sponsors/kazupon"
       }
     },
+    "node_modules/vue-pdf-embed": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/vue-pdf-embed/-/vue-pdf-embed-2.1.2.tgz",
+      "integrity": "sha512-/j++oknFBY9x/MgEFBo9tSuOXS0Z9COlywwLhMREhiGfmuQqpnGy5T+SwVIXxR1tmdzM/lHog8JL7HOAgXT1aw==",
+      "license": "MIT",
+      "dependencies": {
+        "pdfjs-dist": "^4.10.38"
+      },
+      "peerDependencies": {
+        "vue": "^3.3.0"
+      }
+    },
     "node_modules/vue-router": {
       "version": "4.3.2",
       "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.3.2.tgz",

+ 1 - 0
package.json

@@ -59,6 +59,7 @@
     "vue-chartjs": "^5.3.1",
     "vue-cropper": "1.1.1",
     "vue-i18n": "9.10.2",
+    "vue-pdf-embed": "^2.1.2",
     "vue-router": "4.3.2",
     "vue-types": "5.1.1",
     "vue3-print-nb": "^0.1.4",

+ 197 - 0
src/components/PdfView/index.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="yaxq-bottom-jcxx yawd">
+    <div class="page-tool">
+      <div class="page-tool-item" @click="zoomOut">缩小</div>
+      <div class="page-tool-item" @click="lastPage">上一页</div>
+      <div class="page-tool-item">{{ state.pageNum }}/{{ state.numPages }}</div>
+      <div class="page-tool-item" @click="nextPage">下一页</div>
+      <div class="page-tool-item" @click="zoomIn">放大</div>
+    </div>
+    <div class="pdf-preview"
+         v-loading="pdfLoading"
+         @mousedown="startDrag"
+         @mousemove="doDrag"
+         @mouseup="endDrag"
+         @mouseleave="endDrag">
+      <div class="pdf-container" :style="containerStyle">
+        <vue-pdf-embed
+          v-if="show"
+          :source="state.source"
+          class="vue-pdf-embed"
+          :style="{ transform: `scale(${state.scale})` }"
+          :page="state.pageNum"
+          @loaded="handleDocument" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="PdfView">
+import { ref, reactive, computed, onMounted } from 'vue';
+import VuePdfEmbed from 'vue-pdf-embed';
+
+const props = defineProps({
+  url: String
+});
+
+// PDF状态
+const state = reactive({
+  source: '' as string | undefined,
+  pageNum: 1,
+  scale: 1,
+  numPages: 0,
+});
+
+// 拖拽状态
+const dragState = reactive({
+  isDragging: false,
+  startX: 0,
+  startY: 0,
+  translateX: 0,
+  translateY: 0,
+  lastTranslateX: 0,
+  lastTranslateY: 0
+});
+
+// 加载状态
+const pdfLoading = ref(false);
+const show = ref(false);
+
+// 容器样式计算
+const containerStyle = computed(() => ({
+  transform: `translate(${dragState.translateX}px, ${dragState.translateY}px)`,
+  cursor: dragState.isDragging ? 'grabbing' : 'grab'
+}));
+
+// 拖拽处理
+const startDrag = (e: MouseEvent) => {
+  dragState.isDragging = true;
+  dragState.startX = e.clientX - dragState.translateX;
+  dragState.startY = e.clientY - dragState.translateY;
+};
+
+const doDrag = (e: MouseEvent) => {
+  if (!dragState.isDragging) return;
+  dragState.translateX = e.clientX - dragState.startX;
+  dragState.translateY = e.clientY - dragState.startY;
+};
+
+const endDrag = () => {
+  if (!dragState.isDragging) return;
+  dragState.isDragging = false;
+  dragState.lastTranslateX = dragState.translateX;
+  dragState.lastTranslateY = dragState.translateY;
+};
+
+// 缩放功能
+const zoomOut = () => {
+  state.scale = Math.max(0.5, state.scale - 0.1);
+  resetPosition();
+};
+
+const zoomIn = () => {
+  state.scale = Math.min(3, state.scale + 0.1);
+  resetPosition();
+};
+
+// 重置位置
+const resetPosition = () => {
+  dragState.translateX = 0;
+  dragState.translateY = 0;
+  dragState.lastTranslateX = 0;
+  dragState.lastTranslateY = 0;
+};
+
+// 翻页功能
+const lastPage = () => {
+  if (state.pageNum > 1) {
+    state.pageNum -= 1;
+    resetPosition();
+  }
+};
+
+const nextPage = () => {
+  if (state.pageNum < state.numPages) {
+    state.pageNum += 1;
+    resetPosition();
+  }
+};
+
+// PDF加载完成
+const handleDocument = (pdf: any) => {
+  if (pdf.numPages) {
+    pdfLoading.value = false;
+    state.numPages = pdf.numPages;
+  }
+};
+
+// 初始化
+onMounted(() => {
+  if (props.url) {
+    state.source = props.url;
+    pdfLoading.value = true;
+    show.value = true;
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.yawd {
+  display: flex;
+  justify-content: center;
+  position: relative;
+  height: 100%;
+
+  .page-tool {
+    position: fixed;
+    bottom: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+    padding: 5px 15px;
+    display: flex;
+    align-items: center;
+    background: #424242;
+    color: white;
+    border-radius: 5px;
+    z-index: 999;
+    cursor: pointer;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+    &-item {
+      font-size: 14px;
+      padding: 5px 15px;
+      transition: background 0.3s;
+      border-radius: 3px;
+
+      &:hover {
+        background: rgba(255, 255, 255, 0.1);
+      }
+
+      &:not(:last-child) {
+        border-right: 1px solid rgba(255, 255, 255, 0.2);
+      }
+    }
+  }
+
+  .pdf-preview {
+    width: 100%;
+    height: calc(100% - 60px);
+    overflow: hidden;
+    position: relative;
+    background: #f0f0f0;
+
+    //.pdf-container {
+    //  transition: transform 0.3s ease;
+    //  will-change: transform;
+    //  padding: 20px;
+    //}
+
+    .vue-pdf-embed {
+      transform-origin: 0 0;
+      user-select: none;
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+      background: white !important;
+    }
+  }
+}
+</style>

+ 5 - 0
src/types/components.d.ts

@@ -75,6 +75,8 @@ declare module 'vue' {
     NearbyVideos: typeof import('./../components/NearbyVideos/index.vue')['default']
     Pagination: typeof import('./../components/Pagination/index.vue')['default']
     ParentView: typeof import('./../components/ParentView/index.vue')['default']
+    PdfView: typeof import('./../components/PdfView/index.vue')['default']
+    PdfViewer: typeof import('./../components/PdfView/PdfViewer.vue')['default']
     QuickZoom: typeof import('./../components/Map/quickZoom.vue')['default']
     Render: typeof import('./../components/BuildCode/render.vue')['default']
     RightTool: typeof import('./../components/Map/rightTool.vue')['default']
@@ -99,4 +101,7 @@ declare module 'vue' {
     YMap: typeof import('./../components/Map/YMap.vue')['default']
     YztMap: typeof import('./../components/Map/YztMap/index.vue')['default']
   }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
 }

+ 50 - 10
src/views/routineCommandMap/RightSection/EventReport/EventDetail.vue

@@ -101,29 +101,45 @@
       </div>
     </div>
     <div class="common-title-box">匹配预案</div>
-    <div v-if="!!detailData.plan_name" class="info-content">
-      <div class="list">
-        <div class="list-item">
-          <i class="img" />
-          <div class="item-title">{{ detailData.plan_name }}</div>
+    <div v-if="!!planFiles && planFiles.length > 0" class="common-info-content">
+      <div class="list2">
+        <div v-for="(item, index) in planFiles" :key="index" class="list-item" style="margin-bottom: 10px">
+          <div class="link" @click="previewSummaryFile2(item)">{{ item.name }}</div>
+          <div style="margin-left: 40px" @click="previewSummaryFile2(item)">查看</div>
+          <div style="margin-left: 20px; display: flex; align-items: center" @click="downloadSummaryFile2(item)">
+            <span>下载</span>
+            <el-icon class="icon" style="margin-left: 0px"><Download /></el-icon>
+          </div>
         </div>
       </div>
     </div>
     <div class="common-title-box">事件总结报告</div>
     <div v-if="!!summaryFiles && summaryFiles.length > 0" class="info-content">
       <div class="list2">
-        <div v-for="(item, index) in summaryFiles" :key="index" class="list-item" @click="downloadSummaryFile(item.url)">
-          <div class="link">{{ item.file_name }}</div>
-          <el-icon class="icon"><Download /></el-icon>
+        <div v-for="(item, index) in summaryFiles" :key="index" class="list-item" style="margin-bottom: 10px">
+          <div class="link" @click="previewSummaryFile(item)">{{ item.file_name }}</div>
+          <div style="margin-left: 40px" @click="previewSummaryFile(item)">查看</div>
+          <div style="margin-left: 20px; display: flex; align-items: center" @click="downloadSummaryFile(item)">
+            <span>下载</span>
+            <el-icon class="icon" style="margin-left: 0px"><Download /></el-icon>
+          </div>
         </div>
       </div>
     </div>
     <Dialog v-model="showDeleteDialog" class="tip" type="xs" title="提示">确认删除事件吗</Dialog>
   </Dialog>
+  <Dialog type="xs" v-model="dialogTableVisible" title="文件预览" height="1800px">
+    <div>
+      <div style="margin-top: 80px">{{ detailInfo.file_name }}</div>
+      <pdf-viewer v-if="dialogTableVisible" :url="baseUrl + '/file/download/' + detailInfo.url" />
+    </div>
+  </Dialog>
 </template>
 
 <script lang="ts" setup name="EventDetail">
 import { getEventDetail } from '@/api/duty/eventing';
+import { download2 } from '@/utils/request';
+import PdfViewer from '@/components/PdfView/index.vue';
 
 const props = defineProps({
   modelValue: {
@@ -190,9 +206,32 @@ let eventLevelState = reactive({
 });
 // 下载总结报告
 let summaryFiles = ref([]);
-const downloadSummaryFile = (url) => {
-  window.open(url);
+let planFiles = ref([]);
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+const downloadSummaryFile = (file: any) => {
+  // window.open(url);
+  download2(baseUrl + '/file/download/' + file.url, file.file_name);
+};
+const detailInfo = ref({});
+const detailInfo2 = ref({});
+const dialogTableVisible = ref(false);
+const dialogTableVisible2 = ref(false);
+const previewSummaryFile = (file) => {
+  detailInfo.value = file;
+  if (file) {
+    dialogTableVisible.value = true;
+  }
 };
+const previewSummaryFile2 = (file) => {
+  detailInfo2.value = file;
+  if (file) {
+    dialogTableVisible2.value = true;
+  }
+};
+const downloadSummaryFile2 = (file: any) => {
+  download2(baseUrl + '/file/download/' + file.url, file.name);
+};
+
 const handleClose = () => {
   emits('update:modelValue', false);
 };
@@ -220,6 +259,7 @@ const initData = () => {
     eventTrackState = res.data.event_status_tracks;
     eventLevelState.data = res.data.event_level_tracks;
     summaryFiles.value = res.data.summary_file;
+    planFiles.value = res.data.plan_files;
   });
 };