wangfumin 1 місяць тому
батько
коміт
3530ded652
4 змінених файлів з 636 додано та 6 видалено
  1. 7 0
      index.html
  2. 314 0
      src/view/case/drawMap/creatMap.vue
  3. 314 6
      src/view/case/newCaseFile.vue
  4. 1 0
      vite.config.ts

+ 7 - 0
index.html

@@ -9,6 +9,13 @@
 </head>
 
 <body>
+  <script type="text/javascript">
+    window._AMapSecurityConfig = {
+      securityJsCode: "d5165cfa21e0f81e630062ff330a2ddd",
+    };
+  </script>
+  <script type="text/javascript" src='//webapi.amap.com/maps?v=2.0&key=2ae5a7713612a8d5a65cfd54c989c969'></script>
+  <script src="//webapi.amap.com/ui/1.1/main.js?v=1.1.1"></script>
   <div id="app"></div>
   <script type="module" src="/src/main.ts"></script>
 </body>

+ 314 - 0
src/view/case/drawMap/creatMap.vue

@@ -0,0 +1,314 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="选择地图位置"
+    width="80%"
+    :before-close="handleClose"
+    destroy-on-close
+    class="map-fire-dialog"
+  >
+    <div class="map-dialog-content">
+      <!-- 搜索框 -->
+      <div id="panel" class="scrollbar1">
+            <div id="searchBar">
+                <input id="searchInput" @input="handleSearch" placeholder="输入关键字搜素POI" />
+            </div>
+            <div id="searchResults">暂无数据</div>
+        </div>
+
+      <!-- 地图容器 -->
+      <div class="map-container">
+        <div id="container" class="map" style="width: 100%; height: 100%" tabindex="0"></div>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleClose">取消</el-button>
+        <el-button type="primary" @click="handleConfirm" :disabled="!selectedLocation">
+          确定
+        </el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, nextTick, onUnmounted, onMounted } from 'vue'
+import { ElDialog, ElInput, ElButton, ElDescriptions, ElDescriptionsItem, ElIcon, ElMessage } from 'element-plus'
+import { Search } from '@element-plus/icons-vue'
+import AMapLoader from '@amap/amap-jsapi-loader'
+
+// 定义组件props和emits
+interface Props {
+  modelValue: boolean
+}
+
+interface LocationInfo {
+  name: string
+  address: string
+  lat: number
+  lng: number
+  adcode?: string
+  citycode?: string
+  district?: string
+}
+
+const props = defineProps<Props>()
+const mapInput = ref('')
+const emit = defineEmits<{
+  'update:modelValue': [value: boolean]
+  'confirm': [location: LocationInfo]
+}>()
+
+// 响应式变量
+const visible = ref(false)
+const keyword = ref('')
+const selectedLocation = ref<LocationInfo | null>(null)
+const mapContainer = ref<HTMLDivElement>()
+
+// 高德地图相关变量
+let map: any = null
+let placeSearch: any = null
+let geocoder: any = null
+let currentMarker: any = null
+let poiPicker: any = null
+
+// 高德地图配置
+const AMAP_KEY = '2ae5a7713612a8d5a65cfd54c989c969'
+
+// 监听visible变化
+watch(() => props.modelValue, (newVal) => {
+  visible.value = newVal
+  if (newVal) {
+    nextTick(() => {
+      initMap()
+    })
+  }
+}, { immediate: true })
+
+watch(visible, (newVal) => {
+  emit('update:modelValue', newVal)
+})
+// 初始化地图
+const initMap = async () => {
+    map = new AMap.Map('container', {
+        zoom: 10,
+        key: AMAP_KEY, // 替换为你的API密钥
+        center: [116.35, 39.86],
+    });
+
+    AMapUI.loadUI(['misc/PoiPicker'], function(PoiPicker) {
+
+    poiPicker = new PoiPicker({
+        input: 'searchInput',
+        placeSearchOptions: {
+            map: map,
+            pageSize: 7
+        },
+        searchResultsContainer: 'searchResults'
+    });
+    poiPicker.on('poiPicked', function(poiResult) {
+
+        poiPicker.hideSearchResults();
+        map.clearMap();
+        var source = poiResult.source, poi = poiResult.item;
+        if (source !== 'search') {
+            //suggest来源的,同样调用搜索
+            poiPicker.searchByKeyword(poi.name);
+
+        } else {
+            addMarker(poi.location.lng, poi.location.lat)
+            // console.log(poi);
+        }
+    });
+
+    poiPicker.onCityReady(function() {
+        poiPicker.searchByKeyword('');
+    });
+});
+
+}
+const handleSearch = (e) => {
+    // console.log('handleSearch', e.target.value)
+    let mapData = e.target.value
+    poiPicker.searchByKeyword( e.target.value)
+    // setTimeout(() => {
+    //     map.clearMap();
+    // }, 100)
+}
+
+
+// 添加标记
+const addMarker = (lng: number, lat: number) => {
+
+  // 添加新标记
+  var marker = new AMap.Marker({
+    position: new AMap.LngLat(lng, lat), 
+    content: '<div class="amap_lib_placeSearch_poi"></div>',
+    offset: new AMap.Pixel(-10, -31)
+  });
+  map.add(marker)
+}
+
+
+// 处理弹窗关闭
+const handleClose = () => {
+  visible.value = false
+  selectedLocation.value = null
+  keyword.value = ''
+  
+  // 清理地图
+  if (map) {
+    map.destroy()
+    map = null
+  }
+}
+
+// 处理确认选择
+const handleConfirm = () => {
+  if (selectedLocation.value) {
+    emit('confirm', selectedLocation.value)
+    handleClose()
+  }
+}
+
+// 组件卸载时清理
+onUnmounted(() => {
+  if (map) {
+    map.destroy()
+  }
+})
+</script>
+
+<style lang="scss">
+.map-fire-dialog{
+    .el-dialog__header{
+        border-bottom: 1px solid #dcdfe6;
+        margin-bottom: 16px;
+    }
+    // https://a.amap.com/jsapi/static/image/plugin/marker_red.png
+    // .amap-marker{
+    //     display: none!important;
+    // }
+}
+</style>
+<style scoped lang="scss">
+.map-dialog-content {
+  height: 600px;
+  display: flex;
+//   flex-direction: column;
+}
+#panel {
+    // position: absolute;
+    // top: 24px;
+    // left: 24px;
+    height: 100%;
+    overflow: auto;
+    width: 400px;
+    padding-right: 16px;
+    // z-index: 999;
+    background: #fff;
+    :deep(.amap-ui-poi-picker-sugg-container) {
+        display: none;
+    }
+    #searchBar{
+        width: 300px;
+        text-align: left;
+        margin-bottom: 16px;
+    }
+    :deep(#searchInput) {
+        width: 378px;
+        height: 40px;
+        border-radius: 4px;
+        border: 1px solid #dcdfe6;
+        padding: 0 10px;
+        font-size: 14px;
+        color: #303133;
+        background: #fff;
+        outline: none;
+    }
+}
+#searchResults {
+    width: 100%;
+    height: 540px;
+    overflow: auto;
+}
+.search-section {
+  position: relative;
+  margin-bottom: 16px;
+  z-index: 1000;
+}
+
+.search-results {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  background: white;
+  border: 1px solid #dcdfe6;
+  border-top: none;
+  border-radius: 0 0 4px 4px;
+  max-height: 200px;
+  overflow-y: auto;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.search-item {
+  padding: 12px 16px;
+  cursor: pointer;
+  border-bottom: 1px solid #f0f0f0;
+  transition: background-color 0.2s;
+
+  &:hover {
+    background-color: #f5f7fa;
+  }
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+
+.item-name {
+  font-weight: 500;
+  color: #303133;
+  margin-bottom: 4px;
+}
+
+.item-address {
+  font-size: 12px;
+  color: #909399;
+}
+
+.map-container {
+  flex: 1;
+  position: relative;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.amap-wrapper {
+  width: 100%;
+  height: 100%;
+}
+
+.location-info {
+  margin-top: 16px;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+// 覆盖高德地图控件样式
+:deep(.amap-logo) {
+  display: none !important;
+}
+
+:deep(.amap-copyright) {
+  display: none !important;
+}
+</style>

+ 314 - 6
src/view/case/newCaseFile.vue

@@ -18,10 +18,13 @@
     </template>
     <template v-else>
       <div class="body-head details-head">
-        <h3 style="visibility: hidden">场景管理</h3>
+        <h3 style="visibility: hidden">绘图管理</h3>
         <div>
           <template v-if="isDraw">
-            <el-button type="primary" @click="gotoDraw(BoardType.map, -1)">
+            <!-- <el-button type="primary" @click="gotoDraw(BoardType.map, -1)">
+              创建{{ BoardTypeDesc[BoardType.map] }}
+            </el-button> -->
+            <el-button type="primary" @click="openMapDialog">
               创建{{ BoardTypeDesc[BoardType.map] }}
             </el-button>
             <el-button type="primary" @click="gotoDraw(BoardType.scene, -1)">
@@ -33,8 +36,7 @@
           </el-button>
         </div>
       </div>
-
-      <el-table
+      <!-- <el-table
         :data="files"
         class="table-list"
         tooltip-effect="dark"
@@ -87,7 +89,113 @@
           </span>
           <span class="oper-span delBtn" @click="del(row)"> 删除 </span>
         </el-table-column>
-      </el-table>
+      </el-table> -->
+
+      <!-- 卡片网格布局 -->
+      <div class="file-grid-container">
+        <div 
+          class="file-card" 
+          v-for="(file, index) in files" 
+          :key="file.filesId"
+        >
+          <!-- 卡片图片容器 -->
+          <div class="card-image-container">
+            <el-image
+              ref="imageRef"
+              :src="file.filesUrl"
+              :preview-src-list="srcList"
+              :initial-index="index"
+              fit="cover"
+              class="card-image"
+              :hide-on-click-modal="true"
+              :preview-teleported="true"
+            >
+              <template #error>
+                <div class="image-error">
+                  <el-icon size="40" color="#c0c4cc">
+                    <Document />
+                  </el-icon>
+                </div>
+              </template>
+            </el-image>
+            
+            <!-- 悬浮操作按钮 -->
+            <div class="card-overlay">
+              <div class="card-actions">
+                <el-button 
+                  class="card-overlay-btn"
+                  size="default" 
+                  circle
+                  @click.stop="previewImage(index)"
+                  title="查看"
+                >
+                  <el-icon :size="20"><View /></el-icon>
+                </el-button>
+                <el-button 
+                  class="card-overlay-btn"
+                  size="default" 
+                  circle
+                  @click.stop="gotoDraw(file.imgType!, file.filesId)"
+                  v-if="file.imgType !== null"
+                  title="编辑"
+                >
+                  <el-icon :size="20"><Edit /></el-icon>
+                </el-button>
+                <el-button 
+                  class="card-overlay-btn"
+                  size="default" 
+                  circle
+                  @click.stop="del(file)"
+                  title="删除"
+                >
+                  <el-icon :size="20"><Delete /></el-icon>
+                </el-button>
+              </div>
+            </div>
+          </div>
+          
+          <!-- 卡片底部:文件名 -->
+          <div class="card-footer">
+            <div class="file-title">
+              <span v-if="!inputCaseTitles.includes(file)" class="title-text">
+                {{ file.filesTitle }}
+                <el-icon class="edit-title" @click="inputCaseTitles.push(file)">
+                  <EditPen />
+                </el-icon>
+              </span>
+              <template v-else>
+                <ElInput
+                  v-model="file.filesTitle"
+                  placeholder="请输入文件名"
+                  focus
+                  :maxlength="50"
+                  size="default"
+                  class="edit-input"
+                >
+                  <template #append>
+                    <el-button type="primary" plain @click="updateFileTitle(file)">
+                      确定
+                    </el-button>
+                  </template>
+                </ElInput>
+              </template>
+            </div>
+          </div>
+          <el-image-viewer
+            v-if="showPreview"
+            :url-list="srcList"
+            show-progress
+            :initial-index="0"
+            @close="showPreview = false"
+          />
+        </div>
+      </div>
+
+      <!-- 地图选择弹窗 -->
+      <CreatMap 
+        v-model="showMapDialog" 
+        @confirm="handleMapConfirm"
+      />
     </template>
   </div>
 </template>
@@ -110,10 +218,12 @@ import {
 } from "@/store/caseFile";
 import { getCaseInfo, updateCaseInfo } from "@/store/case";
 import { appConstant } from "@/app";
-import { ElIcon, ElInput, ElMessage } from "element-plus";
+import { ElIcon, ElInput, ElMessage, ElImage, ElButton, ImageInstance } from "element-plus";
+import { EditPen, Document, View, Edit, Delete } from "@element-plus/icons-vue";
 import Photos from "./photos/index.vue";
 import Records from "./records/index.vue";
 import Manifest from "./records/manifest.vue";
+import CreatMap from "./drawMap/creatMap.vue";
 
 const props = defineProps<{
   caseId?: number;
@@ -169,6 +279,41 @@ watchEffect(() => {
 const isDraw = computed(() => currentTypeId.value === FileDrawType);
 
 const files = ref<CaseFile[]>([]);
+
+// 计算预览图片列表
+const srcList = computed(() => {
+  return files.value.map(file => file.filesUrl);
+});
+
+// 预览图片方法
+const imageRef = ref<ImageInstance>()
+const showPreview = ref(false)
+const previewImage = (index: number) => {
+  const file = files.value[index];
+  const ext = file.filesUrl
+    .substring(file.filesUrl.lastIndexOf("."))
+    .toLocaleLowerCase();
+  
+  // 如果是图片文件,让 el-image 的预览功能自动处理
+  const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
+  console.log(imageRef.value, 999)
+  if (imageExts.includes(ext)) {
+    showPreview.value = true
+    return
+  }
+  
+  // 如果是其他文件类型,使用原来的查看逻辑
+  const appId = import.meta.env.VITE_APP_APP || 'fire';
+  if ([".raw", ".dcm"].includes(ext)) {
+    window.open(
+      `/${appId}/xfile-viewer/index.html?file=${file.filesUrl}&name=${file.filesTitle}&time=` +
+        Date.now()
+    );
+  } else {
+    window.open(file.filesUrl + "?time=" + Date.now());
+  }
+};
+
 const refresh = async () => {
   files.value = await getCaseFiles({
     caseId: caseId.value!,
@@ -209,6 +354,36 @@ const gotoDraw = (type: BoardType, id: number) => {
     params: { caseId: caseId.value!, type, id },
   });
 };
+// 地图弹窗相关
+const showMapDialog = ref(false)
+
+// 打开地图选择弹窗,新版本地图选择
+const openMapDialog = () => {
+  showMapDialog.value = true
+}
+
+// 处理地图选择确认
+const handleMapConfirm = async (location: any) => {
+  console.log('选择的地图位置:', location)
+  // 这里可以将位置信息保存到案件中,或者创建地图绘图
+  try {
+    // 可以调用相关API保存位置信息
+    // 或者直接跳转到绘图页面
+    await router.push({
+      name: RouteName.drawCaseFile,
+      params: { 
+        caseId: caseId.value!, 
+        type: BoardType.map, 
+        id: -1 
+      },
+      query: {
+        location: JSON.stringify(location)
+      }
+    })
+  } catch (error) {
+    console.error('处理地图位置失败:', error)
+  }
+}
 
 onMounted(async () => {
   try {
@@ -282,9 +457,142 @@ onUnmounted(() => {
     align-items: flex-start;
   }
  }
+
 .edit-title {
   cursor: pointer;
   margin-left: 10px;
 }
+
+// 卡片网格布局样式
+.file-grid-container {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, 387px);
+  gap: 16px;
+  padding: 16px 0;
+}
+
+.file-card {
+  background: #ffffff;
+  // transition: all 0.3s ease;
+  width: 387px;
+  height: 303px;
+  display: flex;
+  flex-direction: column;
+
+  &:hover {
+    // box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+    // transform: translateY(-2px);
+    
+    .card-overlay {
+      opacity: 1;
+    }
+  }
+}
+
+.card-image-container {
+  position: relative;
+  flex: 1;
+  overflow: hidden;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+}
+
+.card-image {
+  width: 100%;
+  height: 100%;
+  cursor: pointer;
+}
+
+.image-error {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f5f7fa;
+}
+
+.card-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.4);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0;
+  transition: opacity 0.3s ease;
+}
+
+.card-actions {
+  display: flex;
+  gap: 12px;
+  .card-overlay-btn{
+    background: transparent;
+    border: none;
+    color: #F2F2F2;
+    padding: 0;
+  }
+}
+
+.card-footer {
+  padding: 12px 0;
+  background: #ffffff;
+}
+
+.file-title {
+  .title-text {
+    font-size: 14px;
+    font-weight: 500;
+    color: #303133;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    word-break: break-all;
+    line-height: 1.4;
+  }
+  
+  .edit-input {
+    :deep(.el-input__wrapper) {
+      height: 30px;
+    }
+    
+    :deep(.el-input-group__append) {
+      .el-button {
+        height: 30px;
+      }
+    }
+  }
+}
+
+.file-time {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 4px;
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .file-grid-container {
+    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .file-grid-container {
+    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+    gap: 12px;
+  }
+  
+  .file-card {
+    height: 240px;
+  }
+  
+  .card-footer {
+    padding: 8px 0;
+  }
+}
 </style>
 

+ 1 - 0
vite.config.ts

@@ -48,6 +48,7 @@ export default ({ mode }: any) => {
     server: {
       port: 5173,
       host: "0.0.0.0",
+      
       proxy: {
         "/api": {
           secure: false,