瀏覽代碼

fix: Merge branch 'xj' of http://192.168.0.115:3000/bill/public-fuse into xj

bill 1 年之前
父節點
當前提交
89613c911d

+ 2 - 1
package.json

@@ -13,7 +13,7 @@
     "@amap/amap-jsapi-loader": "^1.0.1",
     "@element-plus/icons-vue": "^2.1.0",
     "@types/qs": "^6.9.7",
-    "three": "^0.158.0",
+    "@vueuse/router": "^10.11.0",
     "axios": "^1.4.0",
     "echarts": "^5.4.3",
     "element-plus": "^2.3.8",
@@ -24,6 +24,7 @@
     "sass": "^1.64.2",
     "sortablejs": "^1.15.2",
     "swiper": "^11.1.4",
+    "three": "^0.158.0",
     "unplugin-element-plus": "^0.7.2",
     "unplugin-vue-define-options": "^1.3.12",
     "vue": "^3.3.4",

+ 117 - 97
src/app/map/App.vue

@@ -3,8 +3,16 @@
   <div class="tabbar">
     <div class="nav">
       <el-button-group class="ml-4">
-        <el-button :type="currentType(0) ? 'primary' : 'default'" @click="handleSelect(0)">地图</el-button>
-        <el-button :type="currentType(1) ? 'primary' : 'default'" @click="handleSelect(1)">卡片</el-button>
+        <el-button
+          :type="currentType(0) ? 'primary' : 'default'"
+          @click="handleSelect(0)"
+          >地图</el-button
+        >
+        <el-button
+          :type="currentType(1) ? 'primary' : 'default'"
+          @click="handleSelect(1)"
+          >卡片</el-button
+        >
       </el-button-group>
     </div>
     <el-form-item label="所属架构:" class="filter">
@@ -13,117 +21,128 @@
   </div>
   <div ref="mapEl" class="map-container" v-show="currentType(0)"></div>
   <div class="card-container" v-show="currentType(1)">
-
     <div class="card-list">
       <template v-for="item of list">
-        <el-card style="width: 480px" shadow="hover" @click="handCardClick(item.caseId)">
-          <img class="cover"
-            :src="item.cover ? item.cover : 'https://test-mix3d.4dkankan.com/code/assets/pic.d5781b0c.jpg'"
-            style="width: 100%" />
+        <el-card
+          style="width: 480px"
+          shadow="hover"
+          @click="handCardClick(item.caseId)"
+        >
+          <img
+            class="cover"
+            :src="
+              item.cover
+                ? item.cover
+                : 'https://test-mix3d.4dkankan.com/code/assets/pic.d5781b0c.jpg'
+            "
+            style="width: 100%"
+          />
           <div class="card">
             <span> 项目: {{ item.projectName }}</span>
             <span> 地址: {{ item.projectAddress }}</span>
             <span> 类别: {{ item.projectSite }}</span>
           </div>
-
         </el-card>
       </template>
     </div>
-
   </div>
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, computed } from "vue";
+import { onMounted, ref, computed, onBeforeMount } from "vue";
 import AMapLoader from "@amap/amap-jsapi-loader";
-import axios from 'axios';
+import axios from "axios";
 import { getFuseCodeLink } from "../../view/case/help";
 import comCompany from "./company-select/index.vue";
 import { reactive } from "vue";
 import { watch } from "vue";
-import { ElLoading } from 'element-plus'
-
+import { ElLoading } from "element-plus";
+import linkIco from "@/assets/image/fire.ico";
 
 const current = ref(0);
-const list = ref<any>([])
+const list = ref<any>([]);
 const state = reactive({
-  deptId: ''
-})
+  deptId: "",
+});
 
-const currentType = computed(() => (type: number) => current.value === type)
+const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
+link.setAttribute("href", linkIco);
+
+document.title = "案件显示";
+
+const currentType = computed(() => (type: number) => current.value === type);
 const handleSelect = (type: number) => {
-  current.value = type
-}
+  current.value = type;
+};
 
 const getQuery = (
   caseId: number,
   share: boolean = false,
   single: boolean = false
 ) =>
-  `${getFuseCodeLink(caseId, true)}${share ? "&share=1" : ""}${single ? "&single=1" : ""
+  `${getFuseCodeLink(caseId, true)}${share ? "&share=1" : ""}${
+    single ? "&single=1" : ""
   }#show/summary`;
 
 const request = axios.create({
-  baseURL: '',
+  baseURL: "",
   timeout: 1000,
   headers: {
     share: 1,
-    'Content-Type': 'application/json'
+    "Content-Type": "application/json",
   },
 });
 const mapEl = ref<HTMLDivElement>();
 
-
-
 const getDataQuest = () => {
   return new Promise(async (reslove, reject) => {
-
-    const res = await request.post('https://xj-mix3d.4dkankan.com/fusion-xj/web/fireProject/queryProject', {
-      pageNum: 1,
-      pageSize: 10000,
-      deptId: state.deptId
-    })
-    console.log('res.data', res)
+    const res = await request.post(
+      "https://xj-mix3d.4dkankan.com/fusion-xj/web/fireProject/queryProject",
+      {
+        pageNum: 1,
+        pageSize: 10000,
+        deptId: state.deptId,
+      }
+    );
+    console.log("res.data", res);
     if (res.status === 200 && res.data.code === 0) {
-      reslove(res.data.data.list)
+      reslove(res.data.data.list);
     } else {
-      reslove([])
+      reslove([]);
     }
-  })
-}
-
-
+  });
+};
 
 const refresh = async () => {
   const loading = ElLoading.service({
     lock: true,
-    text: 'Loading',
-    background: 'rgba(0, 0, 0, 0.7)',
-  })
-
-  const data = await getDataQuest() as any as any[];
-  console.log('data', data)
-  list.value = data as any[]
-  loading.close()
-
-}
+    text: "Loading",
+    background: "rgba(0, 0, 0, 0.7)",
+  });
 
-watch(() => state.deptId, () => {
+  const data = (await getDataQuest()) as any as any[];
+  console.log("data", data);
+  list.value = data as any[];
+  loading.close();
+};
 
-  refresh();
-}, {
-  immediate: true,
-  deep: true
-})
+watch(
+  () => state.deptId,
+  () => {
+    refresh();
+  },
+  {
+    immediate: true,
+    deep: true,
+  }
+);
 const loadMap = async () => {
-
   const AMap = await AMapLoader.load({
     plugins: ["AMap.PlaceSearch"],
     key: "e661b00bdf2c44cccf71ef6070ef41b8",
     version: "2.0",
   });
 
-
   const map = new AMap.Map(mapEl.value, {
     WebGLParams: {
       preserveDrawingBuffer: true,
@@ -132,74 +151,76 @@ const loadMap = async () => {
   });
 
   //添加插件
-  AMap.plugin(["AMap.ToolBar", "AMap.Scale", "AMap.HawkEye", 'AMap.MapType'], function () {
-    //异步同时加载多个插件
-    // map.addControl(new AMap.HawkEye()); //显示缩略图
-    map.addControl(new AMap.Scale()); //显示当前地图中心的比例尺
-    map.addControl(new AMap.MapType()); //显示当前地图中心的比例尺
-  });
-  console.log('map', map)
+  AMap.plugin(
+    ["AMap.ToolBar", "AMap.Scale", "AMap.HawkEye", "AMap.MapType"],
+    function () {
+      //异步同时加载多个插件
+      // map.addControl(new AMap.HawkEye()); //显示缩略图
+      map.addControl(new AMap.Scale()); //显示当前地图中心的比例尺
+      map.addControl(new AMap.MapType()); //显示当前地图中心的比例尺
+    }
+  );
+  console.log("map", map);
 
   const initMakers = async () => {
-    const data = await getDataQuest() as any as any[];
-    console.log('data', data)
-    const positions: any[] = []
-    list.value = data as any[]
+    const data = (await getDataQuest()) as any as any[];
+    console.log("data", data);
+    const positions: any[] = [];
+    list.value = data as any[];
     Array.from(data).forEach((item: any) => {
       // console.log(item)
-      const latlng = item.latlng
-      const coord = latlng.split(',')
+      const latlng = item.latlng;
+      const coord = latlng.split(",");
 
-      console.log('coord', coord, item.caseId)
-      const url = getQuery(item.caseId, true)
-      console.log('url', url)
+      console.log("coord", coord, item.caseId);
+      const url = getQuery(item.caseId, true);
+      console.log("url", url);
       const icon = new AMap.Icon({
-        image: "//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
-        size: new AMap.Size(22, 28),  //图标所处区域大小
-        imageSize: new AMap.Size(22, 28) //图标大小
+        image:
+          "//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
+        size: new AMap.Size(22, 28), //图标所处区域大小
+        imageSize: new AMap.Size(22, 28), //图标大小
       });
 
-      const pos = coord.reverse()
-      positions.push(pos)
+      const pos = coord.reverse();
+      positions.push(pos);
       const marker = new AMap.Marker({
         icon: icon,
         position: pos,
         title: item.title,
         label: item.title,
-        extData: { url: url, id: item.caseId }
+        extData: { url: url, id: item.caseId },
         // offset: new AMap.Pixel(-26, -54),
       });
 
       marker.setMap(map);
 
-      marker.on('click', () => {
-        const data = marker.getExtData()
-        window.open(data.url)
-        console.log('click', data)
-      })
-    })
+      marker.on("click", () => {
+        const data = marker.getExtData();
+        window.open(data.url);
+        console.log("click", data);
+      });
+    });
 
     var polygon = new AMap.Polygon({
       path: positions,
       map: map,
-      strokeOpacity: 0,//透明
-      fillOpacity: 0,//透明
-      bubble: true//事件穿透到地图
+      strokeOpacity: 0, //透明
+      fillOpacity: 0, //透明
+      bubble: true, //事件穿透到地图
     });
-    var overlaysList = map.getAllOverlays('polygon');//获取多边形图层
-    map.setFitView();//自适应显示
-
-  }
+    var overlaysList = map.getAllOverlays("polygon"); //获取多边形图层
+    map.setFitView(); //自适应显示
+  };
   initMakers();
-
 };
 
 onMounted(loadMap);
 const handCardClick = (id: number) => {
-  const url = getQuery(id, true)
-  console.log('url', url)
-  window.open(url)
-}
+  const url = getQuery(id, true);
+  console.log("url", url);
+  window.open(url);
+};
 </script>
 <style>
 body {
@@ -237,7 +258,6 @@ body {
 .card-container {
   width: 100%;
   padding-top: 100px;
-
 }
 
 .card-list {
@@ -269,4 +289,4 @@ body {
 .amap-ctrl-list-layer {
   z-index: 100000;
 }
-</style>
+</style>

+ 141 - 73
src/app/mirror/App.vue

@@ -2,7 +2,12 @@
   <div class="mirror-setting">
     <!-- 图片预览 -->
     <el-dialog v-model="dialogVisible">
-      <img style="width: 100%" w-full :src="dialogImageUrl" alt="Preview Image" />
+      <img
+        style="width: 100%"
+        w-full
+        :src="dialogImageUrl"
+        alt="Preview Image"
+      />
     </el-dialog>
     <!-- 分镜配置 -->
     <div class="project-title">
@@ -16,11 +21,12 @@
     </div>
     <div class="content">
       <el-table
+      :key="data.list.length"
         class="main-table"
         key="id"
         border
         v-dragable="dragOptions"
-        :data="data"
+        :data="data.list"
         header-row-class-name="t-head"
         header-cell-class-name="t-cell"
       >
@@ -61,14 +67,19 @@
               ref="upload"
               v-model:file-list="row.fileList"
               class="list-upload-style"
-              :class="{ activefileList: row.fileList && row.fileList.length == 1 }"
+              :class="{
+                activefileList: row.fileList && row.fileList.length == 1,
+              }"
               list-type="picture-card"
               :action="uploadFileUrl"
               :on-success="handleUploadSuccess"
               :limit="1"
             >
-              <div class="uploadImg" v-if="row.fileList && row.fileList.length == 0">
-                  <el-icon><Plus /></el-icon>
+              <div
+                class="uploadImg"
+                v-if="row.fileList && row.fileList.length == 0"
+              >
+                <el-icon><Plus /></el-icon>
               </div>
               <template #file="{ file }">
                 <div style="width: 100%">
@@ -110,14 +121,22 @@
         </el-table-column>
 
         <el-table-column prop="marks" label="备注">
-          <template v-slot="{ row }">
-            <el-input
-              class="gray"
-              type="textarea"
-              :autosize="{ minRows: 3 }"
-              v-model="row.marks"
-              placeholder="点击输入内容"
-            />
+          <template v-slot="{ row, $index }">
+            <div class="marksDiv">
+              <el-input
+                class="gray"
+                type="textarea"
+                :autosize="{ minRows: 3 }"
+                v-model="row.marks"
+                placeholder="点击输入内容"
+              />
+              <span
+                class="table-delete"
+                @click="handleTableRemove($index, row)"
+              >
+                <el-icon><Delete /></el-icon>
+              </span>
+            </div>
           </template>
         </el-table-column>
       </el-table>
@@ -142,7 +161,10 @@ import { ElMessage } from "element-plus";
 import { reactive, ref, onMounted } from "vue";
 import type { UploadFile } from "element-plus";
 import { uploadFile as uploadFileUrl } from "@/request";
-import { getCaseScriptInfo, CaseScriptSaveOrUpdate } from "@/app/mirror/store/script";
+import {
+  getCaseScriptInfo,
+  CaseScriptSaveOrUpdate,
+} from "@/app/mirror/store/script";
 
 const caseId = ref(null);
 const project = reactive({
@@ -153,34 +175,41 @@ const dialogVisible = ref(false);
 const disabled = ref(false);
 
 const addLine = ref(1);
-const active = ref(0);
+const active = ref(1);
 const dragOptions = [
-  {
-    selector: "thead tr", // add drag support for column
-    option: {
-      // sortablejs's option
-      animation: 150,
-      onEnd: (evt) => {
-        let oldCol: any = {};
-        Object.assign(oldCol, columns.value[evt.oldIndex]);
-        columns.value.splice(evt.oldIndex, 1); // 因为新增了数据,所以要移除原来的列的index要在原来的基础上
-        setTimeout(() => {
-          columns.value.splice(evt.newIndex, 0, oldCol); // 把原来的列数据添加到新的位置,然后再从原位置移除它,触发table的重绘
-        }, 30);
-        console.log(evt.oldIndex, evt.newIndex);
-      },
-    },
-  },
+  // {
+  //   selector: "thead tr", // add drag support for column
+  //   option: {
+  //     // sortablejs's option
+  //     animation: 150,
+  //     onEnd: (evt) => {
+  //       let oldCol: any = {};
+  //       Object.assign(oldCol, columns.value[evt.oldIndex]);
+  //       columns.value.splice(evt.oldIndex, 1); // 因为新增了数据,所以要移除原来的列的index要在原来的基础上
+  //       setTimeout(() => {
+  //         columns.value.splice(evt.newIndex, 0, oldCol); // 把原来的列数据添加到新的位置,然后再从原位置移除它,触发table的重绘
+  //       }, 30);
+  //       console.log(evt.oldIndex, evt.newIndex);
+  //     },
+  //   },
+  // },
   {
     selector: "tbody", // add drag support for row
     option: {
       // sortablejs's option
       animation: 150,
       onEnd: (evt: any) => {
-        // ElMessage.success(
-        //   `Drag the ${evt.oldIndex}th row to ${evt.newIndex}`
+        // let oldItem = sortList.value[evt.oldIndex];
+        // let sortLists = sortList.value.filter(
+        //   (_, index) => index !== evt.oldIndex
         // );
-        console.log(evt.oldIndex, evt.newIndex);
+        // sortLists.splice(evt.newIndex, 0, oldItem);
+        // sortList.value = sortLists;
+        let list = JSON.parse(JSON.stringify(data.newSortList));
+        const target = list.splice(evt.oldIndex, 1);
+        list.splice(evt.newIndex, 0, target[0]);
+        data.newSortList = list
+        console.log(evt.oldIndex, evt.newIndex,data.newSortList, data.list);
       },
     },
   },
@@ -195,60 +224,90 @@ const columns = ref([
   { prop: "marks", label: "备注" },
 ]);
 
-const data = reactive([
-  { id: 1, name: "", desc: "", fileList:[] },
-  { id: 2, name: "", desc: "", fileList:[] },
-  { id: 3, name: "", desc: "", fileList:[] },
-]);
-onMounted(()=> {
-  caseId.value = GetRequest('caseId');
+const data = reactive({
+  list: [{ id: 1, name: "", desc: "", fileList: [] }],
+  newSortList: [],
+});
+const sortList = ref([0]);
+onMounted(() => {
+  caseId.value = GetRequest("caseId");
+  getCaseScriptList();
+  console.log("caseId", caseId); //query传参
+});
+
+function getCaseScriptList() {
   getCaseScriptInfo(caseId.value).then((res) => {
-    console.log('getCaseScriptInfo', res)  //query传参
-  })
-  console.log('caseId', caseId)  //query传参
-})
+    project.title = res.name;
+    data.list = res.content
+    data.newSortList = res.content
+    const idList = data.list.map(ele => ele.id);
+    active.value =Math.max.apply(null,idList) || 1;
+    sortList.value = data.list.map((_, index) => index);
+  }).catch((err) => {
+    console.log(err);
+  });
+}
 function handleAdd() {
-  for(var i = 1; i <= addLine.value; i++) {
+  // let content = sortList.value.map((index) => data.list[index]);
+  // data.list.length = 0;
+  // Object.assign(data.list, content);
+  console.log("add", data.newSortList);
+  for (var i = 1; i <= addLine.value; i++) {
     console.log(i);
-    data.push({ id: data.length + 1, name: "", desc: "", fileList:[] });
+    data.newSortList.push({ id: active.value + 1, name: "", desc: "", fileList: [] });
   }
+  active.value ++;
+  data.list = data.newSortList
+  sortList.value = data.list.map((_, index) => index);
 }
 const handleRemove = (data) => {
-  data.fileList = []
+  data.fileList = [];
 };
-
+const handleTableRemove =(index, datas) => {
+  data.newSortList = data.newSortList.filter(ele => ele.id !== datas.id);
+  console.log("saveProject", data.newSortList);
+  data.list = data.newSortList
+  // let content = sortList.value.map((index) => data.list[index]);
+  // data.list.length = 0;
+  // content.splice(index, 1);
+  // Object.assign(data.list, content);
+  // sortList.value = content.map((_, index) => index);
+  console.log("saveProject", index, datas, data.list);
+}
 const handlePictureCardPreview = (file: UploadFile) => {
   dialogImageUrl.value = file.url!;
   dialogVisible.value = true;
 };
 
 const saveProject = () => {
+  // let content = sortList.value.map((index) => data.list[index]);
+  console.log("saveProject", data.list, data.newSortList);
   CaseScriptSaveOrUpdate({
-    caseId: caseId.value, 
+    caseId: caseId.value,
     name: project.title,
-    content: data,
+    content: data.newSortList,
   }).then((res) => {
-    console.log('saveProject');
-  })
+    console.log("saveProject");
+  });
 };
-function handleUploadSuccess(response: any, uploadFile: UploadFile ) {
+function handleUploadSuccess(response: any, uploadFile: UploadFile) {
   uploadFile.url = response.data;
   console.log(response, uploadFile);
 }
 
 function GetRequest(value) {
-    var url = decodeURI(window.location.search); //?id="123456"&name="www";
-    var object = {};
-    if (url.indexOf("?") != -1)//url中存在问号,也就说有参数。  
-    {
-      var str = url.substr(1);  //得到?后面的字符串
-      var strs = str.split("&");  //将得到的参数分隔成数组[id="123456",name="www"];
-      for (var i = 0; i < strs.length; i++) {
-        object[strs[i].split("=")[0]] = strs[i].split("=")[1];//得到{id:'123456',name:'www'}
-      }
+  var url = decodeURI(window.location.search); //?id="123456"&name="www";
+  var object = {};
+  if (url.indexOf("?") != -1) {
+    //url中存在问号,也就说有参数。
+    var str = url.substr(1); //得到?后面的字符串
+    var strs = str.split("&"); //将得到的参数分隔成数组[id="123456",name="www"];
+    for (var i = 0; i < strs.length; i++) {
+      object[strs[i].split("=")[0]] = strs[i].split("=")[1]; //得到{id:'123456',name:'www'}
     }
-    return object[value];
   }
+  return object[value];
+}
 </script>
 
 <style lang="scss">
@@ -343,35 +402,44 @@ tbody {
   color: white;
   text-align: center;
 }
-.activefileList{
-  .el-upload-list--picture-card{
-
-
+.activefileList {
+  .el-upload-list--picture-card {
   }
-  .el-upload--picture-card{
+  .el-upload--picture-card {
     display: none;
   }
 }
 .list-upload-style {
   width: 100%;
   text-align: center;
-  .el-upload-list, .el-upload--text {
+  .el-upload-list,
+  .el-upload--text {
     width: 100%;
   }
-  .el-upload-list__item-thumbnail, .el-upload--picture-card{
+  .el-upload-list__item-thumbnail,
+  .el-upload--picture-card {
     min-height: 73px;
     height: 73px;
     width: 100%;
   }
-  .uploadImg, .el-upload-list__item {
+  .uploadImg,
+  .el-upload-list__item {
     width: 100%;
     min-height: 73px;
     height: 73px;
     line-height: 73px;
-    .el-upload-list__item-thumbnail{
+    .el-upload-list__item-thumbnail {
       width: 100%;
       object-fit: cover;
     }
   }
 }
+.marksDiv{
+  position: relative;
+  .table-delete{
+    position: absolute;
+    right: -10px;
+    top: -3px;
+  }
+}
 </style>

+ 10 - 2
src/app/mirror/store/script.ts

@@ -58,8 +58,16 @@ export enum FirePaggingRoute {
   teached = 2,
 }
 
-export const getCaseScriptInfo = async (caseId) =>
-  (await axios.get(getCaseScriptInfoUrl, { params:{caseId, ingoreRes: true} }));
+export const getCaseScriptInfo = async (caseId) => {
+  let res = await axios.get(getCaseScriptInfoUrl, { params:{caseId, ingoreRes: true} });
+  console.log(res);
+  if(res && res.code === 0) {
+    return res.data;
+  }else{ 
+    // window.location.href = '/'
+    throw "用户未登录";
+  }
+}
 
 export const CaseScriptSaveOrUpdate = async (fire: Omit<Fire, "id">) =>
   axios.post(getCaseScriptSaveOrUpdateUrl, fire, {params: {ingoreRes: true}});

二進制
src/assets/image/arrow.png


+ 8 - 0
src/assets/image/arrow.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+	<title>
+		Artboard-12
+	</title>
+	<g id="Up-3" data-name="Up">
+		<path d="M19.707,9.293l-7-7a1,1,0,0,0-1.414,0l-7,7A1,1,0,0,0,5,11H8V21a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V11h3a1,1,0,0,0,.707-1.707Z" style="fill:#e44d54" />
+	</g>
+</svg>

+ 2 - 0
src/constant/caseFile.ts

@@ -2,6 +2,7 @@ import { BoardType } from "@/store/caseFile";
 
 export const FileDrawType = 1;
 export const DrawFormats = [".jpg", ".jpeg", ".png", ".raw", ".dcm"];
+export const photoFormats = [".jpg", ".jpeg", ".png"];
 export const OtherFormats = [".pdf", ".jpeg", ".doc", ".docx", ".jpg", ".png"];
 export const BoardTypeDesc = {
   [BoardType.scene]: "现场图",
@@ -9,5 +10,6 @@ export const BoardTypeDesc = {
 };
 export const DrawFormatDesc = "jpg、png、raw、dcm等格式的文件";
 export const OtherFormatDesc = "pdf、word、jpg、png等格式的文件";
+export const photoFormatDesc = "jpg、jpeg、png等格式的文件";
 
 export const maxFileSize = 100 * 1024 * 1024;

+ 32 - 7
src/core/Scene.js

@@ -2,17 +2,19 @@ import * as THREE from "three";
 import Stats from "three/examples/jsm/libs/stats.module.js";
 import Player from "./player/Player.js";
 import BoxManager from "./box/BoxManager.js";
-
+import { Mitt } from "./mitt.js";
+import testData from "./save.json";
 const stats = new Stats();
 
-export default class Scene {
+export default class Scene extends Mitt {
   constructor(domElement) {
+    super();
     this.domElement = domElement;
     this.scene = null;
     this.renderer = null;
     this.orthCamera = null;
     this.player = null;
-
+    this.sceneType = 1;
     this.width = 0;
     this.height = 0;
     this.inited = false;
@@ -47,6 +49,7 @@ export default class Scene {
       this.orthCamera.lookAt(0, 0, 0);
       // this.orthCamera.setViewOffset(this.width, this.height, 0, 0);
       this.orthCamera.updateProjectionMatrix();
+
       //player
       this.player = new Player(this);
 
@@ -63,17 +66,19 @@ export default class Scene {
     };
   }
 
-  load = (list, type) => {
+  load = (list, type, data) => {
     if (!list) return;
-    console.log("scene: ", list, type);
+    console.log("scene: ", list, type, data);
     //axesHeloer
     this.clearScene();
-    const axesHelper = new THREE.AxesHelper(1);
-    this.scene.add(axesHelper);
+    this.sceneType = type;
+    // const axesHelper = new THREE.AxesHelper(1);
+    // this.scene.add(axesHelper);
     this.boxManager = new BoxManager(this);
     this.boxManager.load(list, type);
     //light
     this.loadLight();
+    this.player.load(type, data || []);
   };
 
   clearScene() {
@@ -82,6 +87,18 @@ export default class Scene {
       this.scene.remove(obj);
     }
   }
+  clearDrawScene() {
+    for (var i = this.scene.children.length - 1; i >= 0; i--) {
+      let obj = this.scene.children[i];
+      if (
+        String(obj.name).includes("marker_") ||
+        String(obj.name).includes("line_") ||
+        String(obj.name).includes("line_point_")
+      ) {
+        this.scene.remove(obj);
+      }
+    }
+  }
   loadLight = () => {
     const light = new THREE.AmbientLight(0xffffff, 1.5); // 柔和的白光
     this.scene.add(light);
@@ -93,6 +110,14 @@ export default class Scene {
 
   toVertical = () => {};
 
+  lockView(open) {
+    if (open) {
+      this.player.floorplanControls.enablePan = true;
+    } else {
+      this.player.floorplanControls.enablePan = false;
+    }
+  }
+
   onResize = (width, height) => {
     this.width = width !== undefined ? width : this.domElement.clientWidth;
     this.height = height !== undefined ? height : this.domElement.clientHeight;

+ 8 - 4
src/core/box/BoxManager.js

@@ -1,6 +1,8 @@
 import * as THREE from "three";
 import HorizontalBox from "./HorizontalBox";
 import VerticalBox from "./VerticalBox";
+import SimpleLabel from "./object/SimpleLabel";
+
 export default class BoxManager {
   constructor(scene) {
     this.scene = scene;
@@ -17,23 +19,25 @@ export default class BoxManager {
 
   load = (list, type) => {
     console.log("this.model.name", this.model.name);
+    const total = list.length;
     list.forEach((item, index) => {
       if (type === 1) {
         //横排
         console.log("横排");
-        const box = new HorizontalBox(this, item, index);
+        const box = new HorizontalBox(this, item, index, total);
         this.model.add(box);
       }
       if (type === 2) {
         //竖排
-        const box = new VerticalBox(this, item, index);
+        const box = new VerticalBox(this, item, index, total);
         // console.log("竖排");
         this.model.add(box);
       }
     });
-
+    // this.model.position.y += 0.3;
+    // this.model.visible =false;
     this.scene.scene.add(this.model);
-    console.log("this.scene.scene", this.scene.scene);
+    // console.log("this.scene.scene", this.scene.scene);
   };
 
   onBindEvent = () => {

+ 54 - 5
src/core/box/HorizontalBox.js

@@ -1,14 +1,15 @@
 import * as THREE from "three";
 import TextLabel from "./object/TextLabel";
+import SimpleLabel from "./object/SimpleLabel";
 import ImgLabel from "./object/ImgLabel";
 import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
 
 export default class HorizontalBox extends THREE.Group {
-  constructor(manager, data, index) {
+  constructor(manager, data, index, total) {
     super();
     this.manager = manager;
     this.name = "horizontal_box";
-
+    this.total = total;
     this.getStyle();
     this.load(data, index);
   }
@@ -17,7 +18,15 @@ export default class HorizontalBox extends THREE.Group {
     this.height = (2 * 710) / 500;
     this.color = 0xffffff;
   }
+  cover(texture, aspect) {
+    var imageAspect = texture.image.width / texture.image.height;
 
+    if (aspect < imageAspect) {
+      texture.matrix.setUvTransform(0, 0, aspect / imageAspect, 1, 0, 0.5, 0.5);
+    } else {
+      texture.matrix.setUvTransform(0, 0, 1, imageAspect / aspect, 0, 0.5, 0.5);
+    }
+  }
   load(data, index) {
     //box
 
@@ -32,7 +41,7 @@ export default class HorizontalBox extends THREE.Group {
     box.scale.set(this.width, 1, this.height);
 
     this.add(box);
-    this.position.x = (this.width + 0.125) * index;
+    this.position.x = (this.width + 0.125) * index - 1.8;
 
     const matLine = new LineMaterial({
       color: 0xe44d54,
@@ -48,16 +57,49 @@ export default class HorizontalBox extends THREE.Group {
     data.forEach((i, j) => {
       //img
       let img;
-
       this.manager.loader.load(i.imgUrl, (texture) => {
+        let imgRatio = texture.image.width / texture.image.height;
+        texture.matrixAutoUpdate = false;
+        let planeRatio = 1.5 / 0.85;
+        // let ratio = planeRatio / imgRatio;
+        texture.matrixAutoUpdate = false;
+        if (planeRatio < imgRatio) {
+          texture.matrix.setUvTransform(
+            0,
+            0,
+            planeRatio / imgRatio,
+            1,
+            0,
+            0.5,
+            0.5
+          );
+        } else {
+          texture.matrix.setUvTransform(
+            0,
+            0,
+            1,
+            imgRatio / planeRatio,
+            0,
+            0.5,
+            0.5
+          );
+        }
+        // texture.wrapS = THREE.RepeatWrapping;
+        // texture.wrapS = THREE.RepeatWrapping;
+        // texture.wrapT = THREE.ClampToEdgeWrapping;
+        // texture.repeat.x = ratio;
+        // texture.offset.x = 0.5 * (1 - ratio);
+        // console.log("texture", texture);
         texture.colorSpace = THREE.SRGBColorSpace;
+
         img = new ImgLabel(texture, matLine);
+
         img.userData = i.id;
         img.position.y += 1;
         if (j === 0) {
           img.position.z -= 0.8;
         } else {
-          img.position.z += 0.5;
+          img.position.z += 0.43;
         }
         this.add(img);
         this.manager.imgList.push(img);
@@ -67,5 +109,12 @@ export default class HorizontalBox extends THREE.Group {
         textlabel.position.z += textlabel.scale.z * 0.5 + 0.1;
       });
     });
+    //页脚
+    const f_txt_label = ` 第 ${index + 1} 页  共 ${this.total} 页`;
+    const footlabel = new SimpleLabel(f_txt_label, true);
+    footlabel.renderOrder = 100;
+    footlabel.position.z += 1.26;
+
+    this.add(footlabel);
   }
 }

+ 46 - 10
src/core/box/VerticalBox.js

@@ -1,13 +1,15 @@
 import * as THREE from "three";
 import TextLabel from "./object/TextLabel";
 import ImgLabel from "./object/ImgLabel";
+import SimpleLabel from "./object/SimpleLabel";
 import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
 
 export default class VerticalBox extends THREE.Group {
-  constructor(manager, data, index) {
+  constructor(manager, data, index, total) {
     super();
     this.manager = manager;
-    this.name = "horizontal_box";
+    this.total = total;
+    this.name = "vertical_box";
     this.getStyle();
     this.load(data, index);
   }
@@ -30,7 +32,7 @@ export default class VerticalBox extends THREE.Group {
     box.scale.set(this.width, 1, this.height);
 
     this.add(box);
-    this.position.x = (this.width + 0.125) * index;
+    this.position.x = (this.width + 0.125) * index - 1.8;
 
     const matLine = new LineMaterial({
       color: 0xe44d54,
@@ -47,21 +49,55 @@ export default class VerticalBox extends THREE.Group {
       //img
       let img;
       this.manager.loader.load(i.imgUrl, (texture) => {
-        texture.colorSpace = THREE.SRGBColorSpace;
-        img = new ImgLabel(texture, matLine);
-        img.position.y += 1;
-        if (j === 0) {
-          img.position.z -= 0.8;
+        let imgRatio = texture.image.width / texture.image.height;
+        let planeRatio = 1.5 / 2;
+        texture.matrixAutoUpdate = false;
+        if (planeRatio < imgRatio) {
+          texture.matrix.setUvTransform(
+            0,
+            0,
+            planeRatio / imgRatio,
+            1,
+            0,
+            0.5,
+            0.5
+          );
         } else {
-          img.position.z += 0.5;
+          texture.matrix.setUvTransform(
+            0,
+            0,
+            1,
+            imgRatio / planeRatio,
+            0,
+            0.5,
+            0.5
+          );
         }
+        // let ratio = planeRatio / imgRatio;
+
+        // texture.repeat.x = ratio;
+        // texture.offset.x = 0.5 * (1 - ratio);
+
+        texture.colorSpace = THREE.SRGBColorSpace;
+        img = new ImgLabel(texture, matLine, false);
+        img.userData = i.id;
+        img.position.y += 1;
+        img.position.z -= 0.2;
         this.add(img);
         this.manager.imgList.push(img);
         const textlabel = new TextLabel(i.imgInfo, true);
         this.add(textlabel);
         textlabel.position.copy(img.position);
-        textlabel.position.z += textlabel.scale.z * 0.5 + 0.1;
+        textlabel.position.z += textlabel.scale.z * 0.5 + 0.7;
       });
     });
+
+    //页脚
+    const f_txt_label = ` 第 ${index + 1} 页  共 ${this.total} 页`;
+    const footlabel = new SimpleLabel(f_txt_label, true);
+    footlabel.renderOrder = 100;
+    footlabel.position.z += 1.26;
+
+    this.add(footlabel);
   }
 }

+ 27 - 11
src/core/box/object/ImgLabel.js

@@ -1,11 +1,30 @@
 import * as THREE from "three";
 import TouchEdge from "./TouchEdge";
 
-export default class ImgLabel extends THREE.Mesh {
-  constructor(texture, matLine) {
-    const width = 1.5;
-    const height = 0.85;
 
+
+export default class ImgLabel extends THREE.Mesh {
+  constructor(texture, matLine, isHorizontal = true) {
+    let width, height, p;
+    if (isHorizontal) {
+      width = 1.5;
+      height = 0.85;
+      p = [
+        [-0.75, 0, -0.425, 0.75, 0, -0.425],
+        [-0.75, 0, -0.425, -0.75, 0, 0.425],
+        [-0.75, 0, 0.425, 0.75, 0, 0.425],
+        [0.75, 0, 0.425, 0.75, 0, -0.425],
+      ];
+    } else {
+      width = 1.5;
+      height = 2;
+      p = [
+        [-0.75, 0, -1, 0.75, 0, -1],
+        [-0.75, 0, -1, -0.75, 0, 1],
+        [-0.75, 0, 1, 0.75, 0, 1],
+        [0.75, 0, 1, 0.75, 0, -1],
+      ];
+    }
     const g = new THREE.PlaneGeometry(width, height);
     g.rotateX(-Math.PI / 2);
 
@@ -13,17 +32,14 @@ export default class ImgLabel extends THREE.Mesh {
       map: texture,
     });
     super(g, m);
-    // console.log(g);
-    const p = [
-      [-0.75, 0, -0.425, 0.75, 0, -0.425],
-      [-0.75, 0, -0.425, -0.75, 0, 0.425],
-      [-0.75, 0, 0.425, 0.75, 0, 0.425],
-      [0.75, 0, 0.425, 0.75, 0, -0.425],
-    ];
+
+    this.width = width;
+    this.height = height;
     this.touchLines = new TouchEdge(p, matLine);
 
     this.touchLines.position.y += 0.5;
     this.add(this.touchLines);
+    // this.touchLines.children.forEach((child) => (child.visible = true));
 
     this.name = "imglabel";
   }

+ 2 - 1
src/core/box/object/Line.js

@@ -123,10 +123,11 @@ export default class Line extends Line2 {
     }
 
     const geometry = new LineGeometry();
-  
+
     cross.visible = false;
     geometry.setPositions(points);
     super(geometry, matLine);
+    this.name = "line_" + this.uuid;
     this.userData = {
       dir: endEdge.name,
       points: points,

+ 2 - 1
src/core/box/object/LinePoints.js

@@ -55,9 +55,10 @@ export default class LinePoints extends Line2 {
     }
     const geometry = new LineGeometry();
     cross.visible = false;
-    console.log("points", points);
+    // console.log("points", points);
     geometry.setPositions(points);
     super(geometry, matLine);
+    this.name = "line_point_" + this.uuid;
     this.scale.set(1, 1, 1);
     this.position.y += 0.5;
     this.add(cross);

+ 47 - 0
src/core/box/object/SimpleLabel.js

@@ -0,0 +1,47 @@
+import * as THREE from "three";
+
+export default class SimpleLabel extends THREE.Mesh {
+  constructor(text, outline) {
+    let res = 5;
+    const width = 150 * res;
+    const height = 15 * res;
+    var canvas = document.createElement("canvas");
+    canvas.width = width;
+    canvas.height = height;
+    let fontFamily = "Arial";
+    let fontSize = 5.2 * res;
+    let offsetX = 75 * res;
+    let offsetY = 10 * res;
+    var context = canvas.getContext("2d");
+
+    context.fillStyle = "#ffffff";
+    context.rect(0, 0, width, height);
+    context.fill();
+    context.font = "normal " + fontSize + "px " + fontFamily;
+    context.fillStyle = "#000000";
+    context.textAlign = "center";
+    context.fillText(text, offsetX, offsetY);
+    const canvas_map = new THREE.Texture(canvas);
+    canvas_map.colorSpace = THREE.SRGBColorSpace;
+    canvas_map.needsUpdate = true;
+    canvas_map.anisotropy = 4;
+
+    const g = new THREE.PlaneGeometry(1.5, 0.15);
+    g.rotateX(-Math.PI / 2);
+
+    const m = new THREE.MeshBasicMaterial({
+      map: canvas_map,
+    });
+    super(g, m);
+
+    // const edges = new THREE.EdgesGeometry(g);
+    // const line = new THREE.LineSegments(
+    //   edges,
+    //   new THREE.LineBasicMaterial({ color: 0xcccccc })
+    // );
+    // line.position.y += 0.5;
+    // this.add(line);
+
+    this.name = "SimpleLabel_" + text;
+  }
+}

+ 28 - 0
src/core/box/object/marker.js

@@ -0,0 +1,28 @@
+import * as THREE from "three";
+import gotoPic from "@/assets/image/arrow.svg";
+const m = new THREE.MeshBasicMaterial({
+  map: new THREE.TextureLoader().load(gotoPic),
+  color: 0xe44d54,
+  transparent: true,
+});
+
+export default class Marker extends THREE.Mesh {
+  constructor(startPoint) {
+    const g = new THREE.PlaneGeometry(0.15, 0.15);
+    g.rotateX(-Math.PI / 2);
+    super(g, m);
+    const a = startPoint.clone();
+    this.position.copy(a);
+
+    this.rotation.y = 0;
+    this.position.y = 5;
+    this.position.z -= 0.02;
+
+    this.visible = true;
+    this.scale.set(1, 1, 1);
+    this.position.y += 0.5;
+    this.name = "marker_" + this.uuid;
+    this.renderOrder = 1000;
+    // console.log(this, this.position);
+  }
+}

+ 1 - 1
src/core/controls/FloorplanControls.js

@@ -285,7 +285,7 @@ export default class FloorplanControls {
     }
   };
   onMouseWheel = (event) => {
-    console.log("this", this);
+    // console.log("this", this);
     if (this.locked) return;
     if (this.enableZoom === false) return;
     event.preventDefault();

+ 10 - 0
src/core/mitt.js

@@ -0,0 +1,10 @@
+import mitt from "mitt";
+export class Mitt {
+  constructor() {
+    const emitter = mitt();
+
+    Object.keys(emitter).forEach((method) => {
+      this[method] = emitter[method];
+    });
+  }
+}

+ 307 - 40
src/core/player/Player.js

@@ -2,8 +2,10 @@ import * as THREE from "three";
 
 import FloorplanControls from "../controls/FloorplanControls.js";
 import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
+import { TrackballControls } from "three/examples/jsm/controls/TrackballControls.js";
 import Line from "../box/object/Line";
 import LinePoints from "../box/object/LinePoints.js";
+import Marker from "../box/object/marker.js";
 import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
 
 const convertScreenToNDC = function (event, domElement) {
@@ -24,25 +26,71 @@ export default class Player {
     this.pointerdown = new THREE.Vector2();
     this.pointerup = new THREE.Vector2();
     this.pointer = new THREE.Vector2();
+    this.markPosition = new THREE.Vector3();
 
     this.touchImg = null;
     this.activeEdge = null;
     this.drawLine = null;
     this.startObj = null;
+    this.marker = null;
     this.allowDrawing = false;
 
     this.drawing = false;
     this.inited = false;
     this.renderLines = [];
+    this.renderMarkers = [];
     this.activeEdges = [];
     this.matLine = null;
     this.lineColor = 0xe44d54;
+    // 1是画线,2是标方向
+    this.mode = 0;
     this.init();
   }
 
+  setMode(mode) {
+    this.mode = mode;
+    if (mode === 1 || mode === 2) {
+      this.reset();
+      this.setEditMode();
+    }
+    if (mode === 0) {
+      this.setFreeMode();
+    }
+    this.scene.emit("mode", this.mode);
+  }
+
+  // removeMarker() {
+  //   if (this.marker) {
+  //     this.scene.scene.remove(this.marker);
+  //     this.marker = null;
+  //   }
+  // }
+  setFreeMode() {
+    this.floorplanControls.enablePan = true;
+    this.floorplanControls.mouseButtons = {
+      LEFT: THREE.MOUSE.PAN,
+      MIDDLE: THREE.MOUSE.DOLLY,
+      RIGHT: THREE.MOUSE.PAN,
+    };
+    this.reset();
+  }
+
+  setEditMode() {
+    this.floorplanControls.enablePan = true;
+    this.floorplanControls.mouseButtons = {
+      LEFT: THREE.MOUSE.ROTATE,
+      MIDDLE: THREE.MOUSE.DOLLY,
+      RIGHT: THREE.MOUSE.PAN,
+    };
+  }
+
   init = () => {
     // //floorplanControls
-    // this.floorplanControls = new FloorplanControls(this.orthCamera, this.scene.domElement, this);
+    // this.floorplanControls = new FloorplanControls(
+    //   this.orthCamera,
+    //   this.scene.domElement,
+    //   this
+    // );
     this.floorplanControls = new OrbitControls(
       this.orthCamera,
       this.scene.domElement
@@ -52,18 +100,22 @@ export default class Player {
     // this.floorplanControls.target.set(0, 1, 0);
     // this.floorplanControls.rotateSpeed = 0.5;
     // this.floorplanControls.panSpeed = 0.75
-    // this.floorplanControls.maxDistance = 100
-    // this.floorplanControls.minDistance = 3.5
+
+    this.floorplanControls.maxDistance = 100;
+    this.floorplanControls.minDistance = 3.5;
     this.floorplanControls.maxZoom = 500;
     this.floorplanControls.minZoom = 100;
 
+    // this.floorplanControls.mouseButtons = {
+    //   LEFT: THREE.MOUSE.PAN,
+    //   MIDDLE: THREE.MOUSE.DOLLY,
+    //   RIGHT: THREE.MOUSE.PAN
+    // }
+    this.setMode(0);
     this.floorplanControls.enableRotate = false;
     this.raycaster = new THREE.Raycaster();
     this.onBindEvent();
     this.inited = true;
-
-    console.log("this.floorplanControls", this.floorplanControls);
-
     this.matLine = new LineMaterial({
       color: this.lineColor,
       linewidth: 3, // in world units with size attenuation, pixels otherwise
@@ -79,52 +131,124 @@ export default class Player {
   onPointerMove = (e) => {
     if (!this.drawing) return;
     this.pointermove = convertScreenToNDC(e, this.scene.domElement);
-    this.raycaster.setFromCamera(this.pointermove, this.orthCamera);
-    let intersectArr = this.scene.boxManager.imgList;
-    // if(this.startObj) {
-    //   let i = intersectArr.indexOf(this.startObj)
-    //   intersectArr.splice(i, 1)
-    // }
-    const intersects = this.raycaster.intersectObjects(intersectArr, false);
-    if (intersects[0] && intersects[0].object !== this.startObj) {
-      this.touchImg = intersects[0];
-      this.setActiveLine(this.touchImg);
+    if (this.mode === 1) {
+      this.raycaster.setFromCamera(this.pointermove, this.orthCamera);
+      let intersectArr = this.scene.boxManager.imgList;
+      // if(this.startObj) {
+      //   let i = intersectArr.indexOf(this.startObj)
+      //   intersectArr.splice(i, 1)
+      // }
+      const intersects = this.raycaster.intersectObjects(intersectArr, false);
+      if (intersects[0] && intersects[0].object !== this.startObj) {
+        this.touchImg = intersects[0];
+        this.setActiveLine(this.touchImg);
+      }
+    }
+    if (this.mode === 2) {
+      if (this.marker) {
+        let pos = new THREE.Vector3(this.pointermove.x, this.pointermove.y, -1);
+        pos.unproject(this.orthCamera);
+        pos.y = 5;
+        // console.log("pos", pos);
+        this.marker.position.copy(pos);
+      }
     }
   };
 
   onPointerDown = (e) => {
     console.log("start draw");
     this.pointerdown = convertScreenToNDC(e, this.scene.domElement);
-    this.raycaster.setFromCamera(this.pointerdown, this.orthCamera);
-    let intersectArr = this.scene.boxManager.imgList;
-    const intersects = this.raycaster.intersectObjects(intersectArr, false);
-    console.log("intersects", intersects);
-    if (intersects[0]) {
-      this.startObj = intersects[0].object;
-      this.drawing = true;
-    } else {
-      this.startObj = null;
-      this.drawing = false;
+    if (this.mode === 1) {
+      this.raycaster.setFromCamera(this.pointerdown, this.orthCamera);
+      let intersectArr = this.scene.boxManager.imgList;
+      const intersects = this.raycaster.intersectObjects(intersectArr, false);
+      console.log("intersects", intersects);
+      if (intersects[0]) {
+        this.startObj = intersects[0].object;
+        this.drawing = true;
+      } else {
+        this.startObj = null;
+        this.drawing = false;
+      }
     }
 
+    if (this.mode === 2) {
+      if (!this.marker) {
+        let pos = new THREE.Vector3(this.pointerdown.x, this.pointerdown.y, -1);
+        pos.unproject(this.orthCamera);
+        pos.y = 5;
+        this.marker = new Marker(pos);
+        this.scene.scene.add(this.marker);
+        this.drawing = true;
+      } else {
+        // this.drawing = false;
+        this.raycaster.setFromCamera(this.pointerdown, this.orthCamera);
+        let intersectArr = this.scene.boxManager.imgList;
+        const intersects = this.raycaster.intersectObjects(intersectArr, false);
+
+        if (intersects[0]) {
+          // this.drawing = false;
+          const imageId = intersects[0].object.userData;
+          let lasPos = new THREE.Vector3(
+            this.pointerdown.x,
+            this.pointerdown.y,
+            -1
+          );
+          lasPos.unproject(this.orthCamera);
+          lasPos.y = 5;
+          const marker = new Marker(lasPos);
+
+          const activeMarkeritem = {
+            id: imageId,
+            point: lasPos.toArray(),
+          };
+          const exist = this.renderMarkers.find((item) => item.id === imageId);
+
+          if (!exist) {
+            this.scene.scene.add(marker);
+            this.renderMarkers.push(activeMarkeritem);
+            this.scene.scene.remove(this.marker);
+            this.marker = null;
+          } else {
+            this.scene.emit("markerExist");
+          }
+
+          console.log("activeMarkeritem", activeMarkeritem);
+        }
+      }
+    }
     // this.floorplanControls.enabled = false;
   };
   onPointerUp = (e) => {
-    // console.log("last Line-wPos", points);
     this.pointerup = convertScreenToNDC(e, this.scene.domElement);
-    this.drawing = false;
-    this.floorplanControls.enabled = true;
-    this.startObj = null;
+    console.log("onPointerUp", this.pointerup);
 
-    if (this.drawLine) {
-      const points = this.drawLine.userData.points;
-      const dir = this.drawLine.userData.dir;
-      const finishLine = new LinePoints(points, 0, this.matLine);
-      this.renderLines.push(points);
-      this.scene.scene.add(finishLine);
-      const imageId = this.touchImg.object.userData;
-      console.log("this.touchImg", dir, imageId, points);
+    if (this.mode === 1) {
+      this.drawing = false;
+      this.floorplanControls.enabled = true;
+      this.startObj = null;
+      if (this.drawLine) {
+        const points = this.drawLine.userData.points;
+        const dir = this.drawLine.userData.dir;
+        const finishLine = new LinePoints(points, 0, this.matLine);
+        this.renderLines.push(points);
+        this.scene.scene.add(finishLine);
+        const imageId = this.touchImg.object.userData;
+
+        const activeLineItem = {
+          id: imageId,
+          dir: [dir],
+        };
+
+        console.log("this.touchImg", activeLineItem, points);
+        this.insertActiveEdge(activeLineItem);
+        this.drawLine = null;
+      }
     }
+    if (this.mode === 2) {
+      // this.drawing = false;
+    }
+    this.syncDrawData();
   };
 
   Listener = {
@@ -167,7 +291,6 @@ export default class Player {
     if (this.drawLine) {
       this.drawLine.removeFromParent();
     }
-    // console.log("this.drawLine", this.drawLine);
     let s = new THREE.Vector3(this.pointerdown.x, this.pointerdown.y, -1);
     let e = new THREE.Vector3(this.pointermove.x, this.pointermove.y, -1);
     s.unproject(this.orthCamera);
@@ -191,7 +314,6 @@ export default class Player {
       x -= 0.5;
       y -= 0.5;
       // console.log(x, y);
-
       if (x >= 0 && y >= 0) {
         if (x > y) {
           return 3;
@@ -230,9 +352,154 @@ export default class Player {
     this.buildLine();
   };
 
+  insertActiveEdge(item) {
+    const exist = this.activeEdges.find((s) => item.id === s.id);
+    if (exist) {
+      exist.dir = [...new Set([...exist.dir, ...item.dir])];
+    } else {
+      this.activeEdges.push(item);
+    }
+  }
+
+  insertActiveMarker(item) {
+    const exist = this.activeEdges.find((s) => item.id === s.id);
+    if (exist) {
+      exist.dir = [...new Set([...exist.dir, ...item.dir])];
+    } else {
+      this.activeEdges.push(item);
+    }
+  }
+  showAllActiveEdges() {
+    if (this.inited) {
+      let imgList = this.scene.boxManager.imgList;
+      if (this.activeEdges.length > 0) {
+        this.activeEdges.forEach((edge) => {
+          const exist = imgList.find((item) => item.userData === edge.id);
+          // console.log("exist", exist);
+          if (exist) {
+            edge.dir.forEach((dir) => {
+              exist.touchLines.children[dir].visible = true;
+            });
+          }
+        });
+      } else {
+        imgList.forEach((img) => {
+          // console.log("img", img);
+          img.touchLines.children.forEach((line) => {
+            if (line.visible) {
+              line.visible = false;
+            }
+          });
+        });
+      }
+    }
+  }
+  getDrawData() {
+    let data;
+    if (this.scene.sceneType === 1) {
+      data = {
+        hor_lines: this.renderLines,
+        hor_activeEdges: this.activeEdges,
+        hor_markers: this.renderMarkers,
+        vir_lines: [],
+        vir_activeEdges: [],
+        vir_markers: [],
+      };
+    } else {
+      data = {
+        hor_lines: [],
+        hor_activeEdges: [],
+        hor_markers: [],
+        vir_lines: this.renderLines,
+        vir_activeEdges: this.activeEdges,
+        vir_markers: this.renderMarkers,
+      };
+    }
+
+    // console.log("sceneType", this.scene.sceneType);
+    return data;
+  }
+
+  syncDrawData() {
+    const data = this.getDrawData();
+    this.scene.emit("data", data);
+  }
+  load(type, data) {
+
+    if (type === 1) {
+      console.log("data1", data);
+      const { hor_activeEdges, hor_lines, hor_markers } = data;
+      hor_activeEdges && (this.activeEdges = hor_activeEdges);
+      if (hor_lines && Array.isArray(hor_lines)) {
+        this.renderLines = hor_lines;
+        hor_lines.forEach((line) => {
+          const finishLine = new LinePoints(line, 0, this.matLine);
+          this.scene.scene.add(finishLine);
+        });
+      }
+      if (hor_markers && Array.isArray(hor_markers)) {
+        this.renderMarkers = hor_markers;
+        hor_markers.forEach((pos) => {
+          console.log("pos");
+          const p = new THREE.Vector3().fromArray(pos.point);
+          const marker = new Marker(p);
+          this.scene.scene.add(marker);
+        });
+      }
+    }
+
+    if (type === 2) {
+      const { vir_activeEdges, vir_lines, vir_markers } = data;
+      vir_activeEdges && (this.activeEdges = vir_activeEdges);
+      if (vir_lines && Array.isArray(vir_lines)) {
+        this.renderLines = vir_lines;
+        vir_lines.forEach((line) => {
+          const finishLine = new LinePoints(line, 0, this.matLine);
+          this.scene.scene.add(finishLine);
+        });
+      }
+      if (vir_markers && Array.isArray(vir_markers)) {
+        this.renderMarkers = vir_markers;
+        vir_markers.forEach((pos) => {
+          const p = new THREE.Vector3().fromArray(pos.point);
+          const marker = new Marker(p);
+          this.scene.scene.add(marker);
+        });
+      }
+    }
+    this.syncDrawData();
+  }
+  reset() {
+    if (this.marker) {
+      this.scene.scene.remove(this.marker);
+      this.marker = null;
+    }
+    if (this.drawLine) {
+      this.scene.scene.remove(this.drawLine);
+      this.drawLine = null;
+    }
+    if (this.touchImg) {
+      this.touchImg = null;
+    }
+    if (this.activeEdge) {
+      this.activeEdge = null;
+    }
+    this.drawing = false;
+  }
+
+  clear() {
+    this.activeEdges = [];
+    this.renderLines = [];
+    this.renderMarkers = [];
+    this.reset();
+    this.scene.clearDrawScene();
+    this.syncDrawData();
+  }
+
   update = () => {
     if (this.floorplanControls.enabled) {
       this.floorplanControls && this.floorplanControls.update();
+      this.scene.boxManager && this.showAllActiveEdges();
     }
   };
 }

+ 8 - 0
src/core/save.json

@@ -0,0 +1,8 @@
+{
+    "hor_lines": [],
+    "vir_lines": [],
+    "hor_markers": [],
+    "vir_markers": [],
+    "hor_activeEdges": [],
+    "vir_activeEdges": []
+}

+ 5 - 0
src/request/urls.ts

@@ -193,6 +193,11 @@ export const caseExtractDetail = "/fusion-xj/caseExtractDetail/info";
 export const caseExtractDetailOpt = "/fusion-xj/caseExtractDetail/saveOrUpdate";
 export const caseExtractDetailExport = "/fusion-xj/caseExtractDetail/downDocx";
 
+//标注
+export const getCaseImgTag = "/fusion-xj/caseImgTag/info";
+export const saveCaseImgTag = "/fusion-xj/caseImgTag/saveOrUpdate";
+
+
 // 火调链接地址设置密码
 export const setCasePsw = "/fusion-xj/web/fireProject/updateRandomCode";
 export const getCasePsw = "/fusion-xj/web/fireProject/getRandCode";

+ 10 - 0
src/store/case.ts

@@ -18,6 +18,8 @@ import {
   caseExtractDetailOpt,
   caseExtractDetailExport,
   copyExample,
+  saveCaseImgTag,
+  getCaseImgTag
 } from "@/request";
 import { ModelScene, QuoteScene, Scene, SceneType } from "./scene";
 import { CaseFile } from "./caseFile";
@@ -126,3 +128,11 @@ export const exportCaseDetailInfo = (caseId: number) =>
     params: { caseId, ingoreRes: true },
     responseType: "blob",
   });
+
+// 
+
+export const saveCaseImgTagData = (params: any) =>
+  axios.post(saveCaseImgTag, { ...params });
+
+export const getCaseImgTagData = (caseId: number) =>
+  axios.get(getCaseImgTag, { params: { caseId } });

+ 4 - 2
src/view/case/addPhotoFile.vue

@@ -15,14 +15,14 @@
         :file-list="fileList"
         :http-request="httpsApi"
         :on-preview="previewFile"
-        :accept="accept"
+        :accept="photoFormats"
         :before-remove="removeFile"
       >
         <el-button type="primary" :disabled="!!file">
           <el-icon><Upload /></el-icon>上传
         </el-button>
         <template v-slot:tip>
-          <div class="el-upload__tip">注:可上传{{ size }}以内的{{ formatDesc }}</div>
+          <div class="el-upload__tip">注:可上传{{ size }}以内的{{ photoFormatDesc }}</div>
         </template>
         <template v-slot:file="{ file }">
           <div class="file" @click.stop="previewFile()">
@@ -50,6 +50,8 @@
 import {
   DrawFormatDesc,
   DrawFormats,
+  photoFormats,
+  photoFormatDesc,
   FileDrawType,
   OtherFormatDesc,
   OtherFormats,

+ 156 - 46
src/view/case/photos/index.vue

@@ -2,20 +2,41 @@
   <div class="photo">
     <div class="left">
       <div class="upload">
-        <el-button type="primary" @click="addCaseFileHandler"> 上传照片 </el-button>
-        <el-button type="primary" @click="sortType = !sortType" :icon="sortType ? FullScreen : Menu">{{ sortType ? "横排"
-          : "竖排" }}</el-button>
+        <el-button type="primary" @click="addCaseFileHandler">
+          上传照片
+        </el-button>
+        <el-button
+          type="primary"
+          @click="sortType = !sortType"
+          :icon="sortType ? FullScreen : Menu"
+          >{{ sortType ? "横排" : "竖排" }}</el-button
+        >
       </div>
-      <draggable ref="childRef" :caseId="caseId" :sortType="sortType" @changeList="changeList"
-        @handleItem="handleItem" />
+      <draggable
+        ref="childRef"
+        :caseId="caseId"
+        :sortType="sortType"
+        @changeList="changeList"
+        @handleItem="handleItem"
+      />
     </div>
     <div class="right">
       <div class="tools">
-        <el-button>开始标注</el-button>
-        <el-button>退出标注</el-button>
+        <el-button @click="handleMark">标注方向</el-button>
+        <el-button @click="handleLine">标注连线</el-button>
+        <el-button @click="handleSave" type="success">保存</el-button>
+
+        <el-button @click="handleClear" v-if="hasDrawData" type="warning"
+          >清空</el-button
+        >
+
+        <el-button @click="handleFree" v-if="isShowExitEdit" type="warning"
+          >退出编辑</el-button
+        >
       </div>
-      <!-- <swiper
+      <swiper
         class="swiper"
+        v-if="false"
         slides-per-view="auto"
         :space-between="24"
         :centeredSlides="true"
@@ -23,7 +44,11 @@
         style="height: 100%"
         @slideChange="onSlideChange"
       >
-        <swiper-slide class="swiperItem" v-for="(item, index) in newlist" :key="index">
+        <swiper-slide
+          class="swiperItem"
+          v-for="(item, index) in newlist"
+          :key="index"
+        >
           <div class="swiperList">
             <div
               class="itemper"
@@ -31,11 +56,7 @@
               v-for="eleItem in item"
               :key="eleItem"
             >
-              <img
-                class="itemImg"
-                :src="eleItem.imgUrl"
-                alt=""
-              />
+              <img class="itemImg" :src="eleItem.imgUrl" alt="" />
               <div class="text">{{ eleItem.imgInfo }}</div>
             </div>
             <div class="page">
@@ -44,48 +65,71 @@
             </div>
           </div>
         </swiper-slide>
-      </swiper> -->
-      <canvas id="canvas"></canvas>
+      </swiper>
+      <canvas id="canvas" v-show="true"></canvas>
     </div>
   </div>
 </template>
 
 <script setup>
-import { onMounted, ref } from "vue";
+import { onMounted, ref, computed, onUnmounted } from "vue";
 import { Menu, FullScreen } from "@element-plus/icons-vue";
 import { Swiper, SwiperSlide } from "swiper/vue";
 import "swiper/css";
 // import { addCaseFile } from "@/store/caseFile";
 import { addCaseImgFile } from "../quisk";
-import Scene from '@/core/Scene.js'
-import draggable from './draggable.vue';
+import { saveCaseImgTagData, getCaseImgTagData } from "@/store/case";
+import Scene from "@/core/Scene.js";
+import draggable from "./draggable.vue";
+import { ElMessage } from "element-plus";
 const props = defineProps({ caseId: Number });
 const newlist = ref([]);
 const swiperRef = ref(null);
 const childRef = ref(null);
 const caseId = ref(props.caseId);
 const sortType = ref(false);
+const drawMode = ref(0);
+const isShowExitEdit = computed(() => drawMode.value > 0);
+const loadedDrawData = ref();
+const hasDrawData = ref(false);
 let scene = null;
 
-
 const addCaseFileHandler = async () => {
   await addCaseImgFile({
-    caseId: caseId.value, data: {
-      imgUrl: '',
-      imgInfo: '',
-      id: '',
-      sort: '',
-    }
+    caseId: caseId.value,
+    data: {
+      imgUrl: "",
+      imgInfo: "",
+      id: "",
+      sort: "",
+    },
   });
   refresh();
 };
 function refresh() {
   console.log("changeList", childRef.value);
+
   if (childRef.value) {
-    childRef.value.getList()
+    childRef.value.getList();
   }
 }
-const changeList = (list) => {
+const changeList = async (list) => {
+  //同步数据
+  if (!loadedDrawData.value) {
+    const res = await getCaseImgTagData(caseId.value);
+    if (res.data) {
+      if (res.data.data) {
+        loadedDrawData.value = res.data.data;
+      }
+      if ("isHorizontal" in res.data) {
+        console.error("sortType.value", sortType.value, !res.data.isHorizontal);
+        sortType.value = !res.data.isHorizontal;
+      }
+    } else {
+      loadedDrawData.value = [];
+    }
+  }
+
   let newList = [];
   list.map((item, index) => {
     if (sortType.value) {
@@ -98,24 +142,44 @@ const changeList = (list) => {
     }
   });
   newlist.value = newList;
-  const arr = []
-  newList.map(i => arr.push(JSON.parse(JSON.stringify(i))))
-  const type = sortType.value ? 2 : 1
+  const arr = [];
+  newList.map((i) => arr.push(JSON.parse(JSON.stringify(i))));
+
+  const type = sortType.value ? 2 : 1;
+
   if (scene) {
-    scene.load(arr, type)
-    console.log("changeList", arr, type);
+    scene.load(arr, type, loadedDrawData.value || []);
+    console.log("changeList", arr, type, loadedDrawData.value);
   }
-
 };
 const renderCanvas = () => {
-  const canvas = document.getElementById('canvas')
-  // console.log(canvas)
-  scene = new Scene(canvas)
-  scene.init()
-  window.scene = scene
-}
+  const canvas = document.getElementById("canvas");
+
+  scene = new Scene(canvas);
+  scene.init();
+  window.scene = scene;
+  scene.on("mode", (mode) => {
+    console.warn("mode", mode);
+    drawMode.value = mode;
+  });
+  scene.on("markerExist", () => {
+    ElMessage.error("该案件已有方向标注!");
+  });
+  scene.on("data", (data) => {
+    let hasData = false;
+    Object.keys(data).forEach((key) => {
+      if (Array.isArray(data[key])) {
+        if (data[key].length > 0) {
+          hasData = true;
+        }
+      }
+    });
+    hasDrawData.value = hasData;
+    console.log("sync", data, hasData);
+  });
+};
 const onSwiper = (swiper) => {
-  console.log('onSwiper')
+  console.log("onSwiper");
   swiperRef.value = swiper;
 };
 const onSlideChange = (swiper) => {
@@ -123,19 +187,64 @@ const onSlideChange = (swiper) => {
 };
 const handleItem = (item) => {
   let active = sortType.value ? item : Math.floor(item / 2);
-  swiperRef.value.slideTo(active);
+  // swiperRef.value.slideTo(active);
   console.log("handleItem", item, active);
 };
 const handleDetele = async (item) => {
-  if (await confirm("删除该场景,将同时从案件和融合模型中移除,确定要删除吗?")) {
+  if (
+    await confirm("删除该场景,将同时从案件和融合模型中移除,确定要删除吗?")
+  ) {
     const scenes = getCaseScenes(list.value.filter((item) => item !== scene));
     await replaceCaseScenes(props.caseId, scenes);
     refresh();
   }
 };
+const handleMark = () => {
+  if (window.scene) {
+    window.scene.player.setMode(2);
+  }
+};
+const handleLine = () => {
+  if (window.scene) {
+    window.scene.player.setMode(1);
+  }
+};
+const handleSave = async () => {
+  if (window.scene) {
+    const data = scene.player.getDrawData();
+    scene.player.syncDrawData();
+    console.log("data", data);
+    const res = await saveCaseImgTagData({
+      caseId: caseId.value,
+      data: data,
+      isHorizontal: !sortType.value,
+    });
+    ElMessage.success("保存成功!");
+    console.log("res", res);
+  }
+};
+const handleFree = () => {
+  if (window.scene) {
+    window.scene.player.setMode(0);
+  }
+};
+const handleClear = () => {
+  if (window.scene) {
+    window.scene.player.clear();
+  }
+};
+onUnmounted(() => {});
+
 onMounted(() => {
   renderCanvas();
-})
+  console.warn("renderCanvas");
+  // try {
+  //   const res = await getCaseImgTagData(caseId.value);
+  //   const { isHorizontal, data } = res.data;
+  //   sortType.value = !isHorizontal;
+  //   data && (loadedDrawData.value = data);
+  // } catch (error) {}
+});
 </script>
 <style lang="scss" scoped>
 #canvas {
@@ -166,7 +275,7 @@ onMounted(() => {
 
     .tools {
       position: absolute;
-      top: 30px;
+      top: 15px;
       left: 30px;
     }
 
@@ -215,7 +324,8 @@ onMounted(() => {
         .oneItemper {
           height: calc(100% - 120px);
 
-          .itemImg {}
+          .itemImg {
+          }
         }
       }
     }

+ 13 - 3
src/view/case/records/index.vue

@@ -3,14 +3,21 @@
   <div class="records">
     <div class="header">
       <el-button type="primary" @click="handleSave">保存</el-button>
-      <el-button @click="handleExport">导出</el-button>
+      <el-button :disabled="isDisableExport" @click="handleExport"
+        >导出</el-button
+      >
     </div>
     <h3 class="title">基本信息</h3>
     <div class="content">
       <div class="line">
         <span>勘验次数:</span>
         <span>第</span>
-        <el-input class="input" v-model="data.count" placeholder="" style="width: 80px" />
+        <el-input
+          class="input"
+          v-model="data.count"
+          placeholder=""
+          style="width: 80px"
+        />
         <span>次勘验</span>
       </div>
 
@@ -316,7 +323,7 @@ import saveAs from "@/util/file-serve";
 const props = defineProps({ caseId: Number });
 
 console.log(props);
-
+const isDisableExport = ref(false);
 const data = reactive({
   count: "",
   startTime: {
@@ -410,6 +417,9 @@ watch(
 onMounted(async () => {
   const res = await getCaseInquestInfo(props.caseId);
   console.log("res", res);
+  if (!res.data) {
+    isDisableExport.value = true;
+  }
   for (var k in data) {
     if (res.data && res.data.hasOwnProperty(k)) {
       console.log("Key is " + k);

+ 3 - 3
src/view/case/records/manifest.vue

@@ -152,7 +152,7 @@ const data = reactive({
 
   location: '',
   detail: [{
-    id: "1",
+    // id: "1",
     name: "",
     spec: "",
     num: "",
@@ -160,7 +160,7 @@ const data = reactive({
     desc: "",
   },
   {
-    id: "2",
+    // id: "2",
     name: "",
     spec: "",
     num: "",
@@ -224,7 +224,7 @@ const addwitnessInfo = () => {
 }
 const addItem = () => {
   data.detail.push({
-    id: "1",
+    // id: "1",
     name: "",
     spec: "",
     num: "",

文件差異過大導致無法顯示
+ 32 - 704
yarn.lock