Kaynağa Gözat

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

tangning 1 yıl önce
ebeveyn
işleme
7d433228f5

+ 22 - 0
pnpm-lock.yaml

@@ -6,6 +6,7 @@ specifiers:
   '@types/node': ^20.4.5
   '@types/qs': ^6.9.7
   '@vitejs/plugin-vue': ^4.2.3
+  '@vueuse/core': ^10.11.0
   '@vueuse/router': ^10.11.0
   axios: ^1.4.0
   echarts: ^5.4.3
@@ -32,6 +33,7 @@ dependencies:
   '@amap/amap-jsapi-loader': 1.0.1
   '@element-plus/icons-vue': 2.3.1_vue@3.4.31
   '@types/qs': 6.9.15
+  '@vueuse/core': 10.11.0_vue@3.4.31
   '@vueuse/router': 10.11.0_t6yugvvfk3qpnriubpgidwndjm
   axios: 1.7.2
   echarts: 5.5.1
@@ -368,6 +370,10 @@ packages:
     resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
     dev: false
 
+  /@types/web-bluetooth/0.0.20:
+    resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+    dev: false
+
   /@vitejs/plugin-vue/4.6.2_vite@4.5.3+vue@3.4.31:
     resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==}
     engines: {node: ^14.18.0 || >=16.0.0}
@@ -507,6 +513,18 @@ packages:
   /@vue/shared/3.4.31:
     resolution: {integrity: sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==}
 
+  /@vueuse/core/10.11.0_vue@3.4.31:
+    resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==}
+    dependencies:
+      '@types/web-bluetooth': 0.0.20
+      '@vueuse/metadata': 10.11.0
+      '@vueuse/shared': 10.11.0_vue@3.4.31
+      vue-demi: 0.14.8_vue@3.4.31
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+    dev: false
+
   /@vueuse/core/9.13.0_vue@3.4.31:
     resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
     dependencies:
@@ -519,6 +537,10 @@ packages:
       - vue
     dev: false
 
+  /@vueuse/metadata/10.11.0:
+    resolution: {integrity: sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==}
+    dev: false
+
   /@vueuse/metadata/9.13.0:
     resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
     dev: false

+ 41 - 9
src/app/fire/view/dispatch/editFire.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-form ref="form" label-width="94px" class="camera-from">
+  <el-form ref="form" label-width="106px" class="camera-from">
     <div class="def-select-map" ref="mapEl"></div>
     <div class="el-form-item">
       <el-col :span="12">
@@ -57,8 +57,12 @@
         </el-form-item>
       </el-col>
       <el-col :span="12">
-        <el-form-item label="勘验人员" class="mandatory" placeholder="请输入勘验人员">
-          <el-input v-model="bindFire.organizerUsers" maxlength="50" />
+        <el-form-item label="勘验人员" class="mandatory">
+          <el-input
+            v-model="bindFire.organizerUsers"
+            maxlength="50"
+            placeholder="请输入勘验人员"
+          />
         </el-form-item>
       </el-col>
     </div>
@@ -73,11 +77,11 @@
         </el-form-item>
       </el-col> -->
       <el-col :span="12">
-        <el-form-item label="单位、职务">
+        <el-form-item label="勘验人职务">
           <el-input
             v-model="bindFire.field4"
             maxlength="50"
-            placeholder="请输入勘验人单位、职务"
+            placeholder="请输入勘验人职务"
           />
         </el-form-item>
       </el-col>
@@ -145,13 +149,25 @@
         </el-form-item>
       </el-col>
       <el-col :span="12">
-        <el-form-item label="勘验日期" class="mandatory" placeholder="请选择勘验日期">
+        <el-form-item
+          label="勘验起止时间"
+          class="mandatory"
+          placeholder="请选择勘验起止时间"
+        >
           <el-date-picker
+            v-model="f8"
+            type="daterange"
+            range-separator="-"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            :disabled-date="(date) => date.getTime() > new Date().getTime()"
+          />
+          <!-- <el-date-picker
             type="date"
             v-model="f8"
             style="width: 100%"
             :disabled-date="(date) => date.getTime() > new Date().getTime()"
-          />
+          /> -->
         </el-form-item>
       </el-col>
     </div>
@@ -186,7 +202,20 @@ const projectSite = genCascaderValue(bindFire, "projectSite");
 const accidentDate = ref(
   bindFire.value.accidentDate ? new Date(bindFire.value.accidentDate) : new Date()
 );
-const f8 = ref(bindFire.value.field8 ? new Date(bindFire.value.field8) : new Date());
+
+const f8s = [new Date(), new Date()];
+if (bindFire.value.field8) {
+  const s = bindFire.value.field8.split("至");
+  console.log(s);
+  if (s.length > 1) {
+    f8s[0] = new Date(s[0]);
+    f8s[1] = new Date(s[1]);
+  } else {
+    f8s[0] = new Date(s[0]);
+  }
+}
+
+const f8 = ref(f8s);
 const keyword = ref(bindFire.value.projectAddress || "");
 const resultEl = ref<HTMLDivElement>();
 const searchAMap = ref<any>();
@@ -272,7 +301,10 @@ defineExpose<QuiskExpose>({
     }
 
     bindFire.value.accidentDate = dateFormat(accidentDate.value, "yyyy-MM-dd");
-    bindFire.value.field8 = dateFormat(f8.value, "yyyy-MM-dd");
+    bindFire.value.field8 =
+      dateFormat(f8.value[0], "yyyy-MM-dd") +
+      "至" +
+      dateFormat(f8.value[1], "yyyy-MM-dd");
     bindFire.value.projectSiteCode = getCode(place, bindFire.value.projectSite);
     bindFire.value.id
       ? await setFire(bindFire.value)

+ 6 - 6
src/app/fire/view/dispatch/list.vue

@@ -128,15 +128,15 @@
         <el-tooltip
           class="item"
           effect="dark"
-          :content="row.field3"
+          :content="row.organizerUsers"
           placement="bottom-start"
-          v-if="row.field3 && row.field3.length > 10"
+          v-if="row.organizerUsers && row.organizerUsers.length > 10"
         >
-          <p class="tip oper-user">{{ row.field3.substring(0, 10) }}...</p>
+          <p class="tip oper-user">{{ row.organizerUsers.substring(0, 10) }}...</p>
         </el-tooltip>
-        <p class="tip" v-else>{{ row.field3 }}</p>
+        <p class="tip" v-else>{{ row.organizerUsers }}</p>
       </el-table-column>
-      <el-table-column label="勘验人单位、职务" prop="field4" v-slot:default="{ row }">
+      <el-table-column label="勘验人勘验人职务" prop="field4" v-slot:default="{ row }">
         <el-tooltip
           class="item"
           effect="dark"
@@ -151,7 +151,7 @@
 
       <el-table-column label="勘验单位" prop="organizerDeptName"></el-table-column>
       <el-table-column label="事故日期" prop="accidentDate"></el-table-column>
-      <el-table-column label="勘验日期" prop="field8"></el-table-column>
+      <el-table-column label="勘验起止时间" prop="field8"></el-table-column>
       <el-table-column label="火灾原因" prop="fireReason" v-slot:default="{ row }">
         <el-tooltip
           class="item"

+ 86 - 62
src/app/map/App.vue

@@ -16,13 +16,13 @@
       </el-button-group>
     </div>
     <el-form-item label="所属架构:" class="filter">
-      <com-company v-model="state.deptId" :id="state.caseId" />
+      <com-company v-model="state.deptId" :id="state.caseId" hideAll />
     </el-form-item>
   </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">
+      <template v-for="item of list" v-if="list.length > 0">
         <el-card
           style="width: 480px"
           shadow="hover"
@@ -44,6 +44,12 @@
           </div>
         </el-card>
       </template>
+      <template v-else>
+        <div class="no-data">
+          <img :src="emptyBG" />
+          <span>暂无数据</span>
+        </div>
+      </template>
     </div>
   </div>
 </template>
@@ -60,16 +66,17 @@ import { watch } from "vue";
 import { ElLoading } from "element-plus";
 import linkIco from "@/assets/image/fire.ico";
 import { useUrlSearchParams } from "@vueuse/core";
+import emptyBG from "@/assets/image/empty__empty.png";
 
 const params = useUrlSearchParams("history");
-console.log("params", params.caseId);
+console.log("params", params.deptId);
 
 const current = ref(0);
 const list = ref<any>([]);
 
 const state = reactive({
-  deptId: "",
-  caseId: params.caseId || "",
+  deptId: params.deptId || "",
+  // caseId: params.caseId || "",
 });
 
 const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
@@ -82,6 +89,8 @@ const handleSelect = (type: number) => {
   current.value = type;
 };
 
+const markers = ref<any>([]);
+
 const getQuery = (
   caseId: number,
   share: boolean = false,
@@ -100,6 +109,7 @@ const request = axios.create({
   },
 });
 const mapEl = ref<HTMLDivElement>();
+let AMap, map;
 
 const getDataQuest = () => {
   return new Promise(async (reslove, reject) => {
@@ -126,10 +136,9 @@ const refresh = async () => {
     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[];
+  map.remove(markers.value);
+  markers.value = [];
+  initMakers();
   loading.close();
 };
 
@@ -139,18 +148,68 @@ watch(
     refresh();
   },
   {
-    immediate: true,
+    immediate: false,
     deep: true,
   }
 );
+const initMakers = async () => {
+  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(",");
+
+    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), //图标大小
+    });
+
+    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 },
+      // offset: new AMap.Pixel(-26, -54),
+    });
+    markers.value.push(marker);
+    marker.setMap(map);
+
+    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, //事件穿透到地图
+  });
+  var overlaysList = map.getAllOverlays("polygon"); //获取多边形图层
+  map.setFitView(); //自适应显示
+};
 const loadMap = async () => {
-  const AMap = await AMapLoader.load({
+  AMap = await AMapLoader.load({
     plugins: ["AMap.PlaceSearch"],
     key: "e661b00bdf2c44cccf71ef6070ef41b8",
     version: "2.0",
   });
 
-  const map = new AMap.Map(mapEl.value, {
+  map = new AMap.Map(mapEl.value, {
     WebGLParams: {
       preserveDrawingBuffer: true,
     },
@@ -169,56 +228,6 @@ const loadMap = async () => {
   );
   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[];
-    Array.from(data).forEach((item: any) => {
-      // console.log(item)
-      const latlng = item.latlng;
-      const coord = latlng.split(",");
-
-      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), //图标大小
-      });
-
-      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 },
-        // offset: new AMap.Pixel(-26, -54),
-      });
-
-      marker.setMap(map);
-
-      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, //事件穿透到地图
-    });
-    var overlaysList = map.getAllOverlays("polygon"); //获取多边形图层
-    map.setFitView(); //自适应显示
-  };
   initMakers();
 };
 
@@ -285,6 +294,8 @@ body {
 
 .cover {
   cursor: pointer;
+  max-height: 220px;
+  object-fit: cover;
 }
 
 .card {
@@ -302,4 +313,17 @@ body {
 .amap-ctrl-list-layer {
   z-index: 100000;
 }
+.no-data {
+  width: 100%;
+  height: 100%;
+  /* background: red; */
+  min-height: 530px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+.no-data span {
+  color: #999;
+}
 </style>

+ 2 - 2
src/app/map/company-select/organization.ts

@@ -7,7 +7,7 @@ import {
 } from "@/request";
 import { useUrlSearchParams } from "@vueuse/core";
 const params = useUrlSearchParams("history");
-console.log("params", params.caseId);
+console.log("params", params.deptId);
 const request = axios.create({
   baseURL: 'https://xj-mix3d.4dkankan.com/',
   timeout: 1000,
@@ -38,7 +38,7 @@ export type Organization = {
 };
 
 export const getOrganizationTree = async (type?: string) =>
-  (await axios.get<Organization[]>(getTreeselect, { headers: { share: 1 }, params: { type, caseId: params.caseId, ingoreRes: true, share: 1, } })).data;
+  (await axios.get<Organization[]>(getTreeselect, { headers: { share: 1 }, params: { type, deptId: params.deptId, ingoreRes: true, share: 1, } })).data;
 
 export const addOrganization = async (
   dept: Omit<Organization, "id">,

+ 169 - 122
src/app/mirror/App.vue

@@ -3,11 +3,28 @@
     <!-- 图片预览 -->
     <el-dialog v-model="dialogVisible">
       <img
-        style="width: 100%"
+        v-if="checkSourceIsImage(dialogImageUrl)"
+        style="width: 100%; height: 500px; object-fit: scale-down"
         w-full
         :src="dialogImageUrl"
         alt="Preview Image"
       />
+
+      <video
+        v-if="checkSourceIsVideo(dialogImageUrl)"
+        style="width: 100%"
+        w-full
+        controls
+        :src="dialogImageUrl"
+      />
+
+      <audio
+        v-if="checkSourceIsAudio(dialogImageUrl)"
+        style="width: 100%"
+        w-full
+        controls
+        :src="dialogImageUrl"
+      />
     </el-dialog>
     <!-- 分镜配置 -->
     <div class="project-title">
@@ -21,7 +38,7 @@
     </div>
     <div class="content">
       <el-table
-      :key="data.list.length"
+        :key="data.list.length"
         class="main-table"
         key="id"
         border
@@ -85,7 +102,7 @@
                 <div style="width: 100%">
                   <img
                     class="el-upload-list__item-thumbnail"
-                    :src="file.url"
+                    :src="getCoverUrl(file.url)"
                     alt=""
                   />
                   <span class="el-upload-list__item-actions">
@@ -158,18 +175,25 @@
 <script lang="ts" setup>
 import { vDragable } from "./dragable";
 import { ElMessage } from "element-plus";
-import { reactive, ref, onMounted } from "vue";
+import { reactive, ref, onMounted, computed } from "vue";
 import type { UploadFile } from "element-plus";
 import { uploadFile as uploadFileUrl } from "@/request";
 import {
   getCaseScriptInfo,
   CaseScriptSaveOrUpdate,
 } from "@/app/mirror/store/script";
+import linkIco from "@/assets/image/fire.ico";
+import musicHeadphones from "@/assets/image/music.png";
+
+const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
+link.setAttribute("href", linkIco);
 
 const caseId = ref(null);
 const project = reactive({
   title: "我的脚本",
 });
+
+document.title = project.title;
 const dialogImageUrl = ref("");
 const dialogVisible = ref(false);
 const disabled = ref(false);
@@ -208,8 +232,8 @@ const dragOptions = [
         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);
+        data.newSortList = list;
+        console.log(evt.oldIndex, evt.newIndex, data.newSortList, data.list);
       },
     },
   },
@@ -223,6 +247,28 @@ const columns = ref([
   { prop: "words", label: "台词文案" },
   { prop: "marks", label: "备注" },
 ]);
+const checkSourceIsVideo = computed(() => (url: string) => {
+  return url.includes(".mp4");
+});
+const checkSourceIsAudio = computed(() => (url: string) => {
+  return url.includes(".mp3");
+});
+const checkSourceIsImage = computed(() => (url: string) => {
+  return url.includes(".jpg") || url.includes(".png") || url.includes(".gif");
+});
+
+const getCoverUrl = computed(() => (url: string) => {
+  switch (true) {
+    case url.includes(".mp4"):
+      return (
+        url + "?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto"
+      );
+    case url.includes(".mp3") || url.includes(".wmv"):
+      return musicHeadphones;
+    default:
+      return url;
+  }
+});
 
 const data = reactive({
   list: [{ id: 1, name: "", desc: "", fileList: [] }],
@@ -236,16 +282,18 @@ onMounted(() => {
 });
 
 function getCaseScriptList() {
-  getCaseScriptInfo(caseId.value).then((res) => {
-    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);
-  });
+  getCaseScriptInfo(caseId.value)
+    .then((res) => {
+      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() {
   // let content = sortList.value.map((index) => data.list[index]);
@@ -254,26 +302,31 @@ function handleAdd() {
   console.log("add", data.newSortList);
   for (var i = 1; i <= addLine.value; i++) {
     console.log(i);
-    data.newSortList.push({ id: active.value + 1, name: "", desc: "", fileList: [] });
+    data.newSortList.push({
+      id: active.value + 1,
+      name: "",
+      desc: "",
+      fileList: [],
+    });
   }
-  active.value ++;
-  data.list = data.newSortList
+  active.value++;
+  data.list = data.newSortList;
   sortList.value = data.list.map((_, index) => index);
 }
 const handleRemove = (data) => {
   data.fileList = [];
 };
-const handleTableRemove =(index, datas) => {
-  data.newSortList = data.newSortList.filter(ele => ele.id !== datas.id);
+const handleTableRemove = (index, datas) => {
+  data.newSortList = data.newSortList.filter((ele) => ele.id !== datas.id);
   console.log("saveProject", data.newSortList);
-  data.list = 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;
@@ -309,6 +362,7 @@ function GetRequest(value) {
   return object[value];
 }
 </script>
+<style lang="scss" scoped></style>
 
 <style lang="scss">
 body,
@@ -324,122 +378,115 @@ body,
   min-height: calc(100vh - 80px);
   margin: 0 auto;
   background: #eee;
-}
-
-.content {
-  margin: 0 auto;
-  display: flex;
-  padding: 0 40px;
-}
-
-.t-head {
-  border: 1px solid #ddd;
-  /* padding: 10px; */
-  /* display: flex; */
-  position: relative;
-  background-color: #eee;
-}
-
-tbody {
-  /* border-top: 20px solid transparent; */
-}
-
-.t-head th {
-  margin-bottom: 20px;
-}
 
-.project-title {
-  display: flex;
-  padding: 0 40px;
-  align-items: center;
-  /* justify-content: center; */
-}
+  .content {
+    margin: 0 auto;
+    display: flex;
+    padding: 0 40px;
+  }
 
-.project-title .title {
-  font-size: 28px;
-  min-height: 0;
-  height: auto;
-  background-color: transparent !important;
-  /* width: 300px; */
-  margin: 30px 0;
-}
+  .t-head {
+    border: 1px solid #ddd;
+    /* padding: 10px; */
+    /* display: flex; */
+    position: relative;
+    background-color: #eee;
+  }
 
-.el-textarea__inner {
-  background-color: transparent;
-  box-shadow: none;
-  resize: none;
-}
+  tbody {
+    /* border-top: 20px solid transparent; */
+  }
 
-.gray .el-textarea__inner {
-  background: rgba(227, 225, 225, 0.2);
-}
+  .t-head th {
+    margin-bottom: 20px;
+  }
 
-.el-textarea__inner:focus {
-  box-shadow: none;
-}
+  .project-title {
+    display: flex;
+    padding: 0 40px;
+    align-items: center;
+    /* justify-content: center; */
+  }
 
-.el-textarea__inner:hover {
-  box-shadow: none;
-}
+  .project-title .title {
+    font-size: 28px;
+    min-height: 0;
+    height: auto;
+    background-color: transparent !important;
+    /* width: 300px; */
+    margin: 30px 0;
+  }
 
-.add-handle {
-  padding: 30px 0;
-  display: flex;
-  justify-content: center;
-}
+  .el-textarea__inner {
+    background-color: transparent;
+    box-shadow: none;
+    resize: none;
+  }
 
-.add-line {
-  margin: 0 10px;
-  width: 30px;
-}
+  .gray .el-textarea__inner {
+    background: rgba(227, 225, 225, 0.2);
+  }
 
-.add-line .el-input__wrapper {
-  box-shadow: none;
-  background: rgba(23, 41, 46, 0.2);
-}
+  .add-handle {
+    padding: 30px 0;
+    display: flex;
+    justify-content: center;
+  }
 
-.add-line input {
-  color: white;
-  text-align: center;
-}
-.activefileList {
-  .el-upload-list--picture-card {
+  .add-line {
+    margin: 0 10px;
+    width: 30px;
   }
-  .el-upload--picture-card {
-    display: none;
+
+  .add-line .el-input__wrapper {
+    box-shadow: none;
+    background: rgba(23, 41, 46, 0.2);
   }
-}
-.list-upload-style {
-  width: 100%;
-  text-align: center;
-  .el-upload-list,
-  .el-upload--text {
-    width: 100%;
+
+  .add-line input {
+    color: white;
+    text-align: center;
   }
-  .el-upload-list__item-thumbnail,
-  .el-upload--picture-card {
-    min-height: 73px;
-    height: 73px;
-    width: 100%;
+  .activefileList {
+    .el-upload-list--picture-card {
+    }
+    .el-upload--picture-card {
+      display: none;
+    }
   }
-  .uploadImg,
-  .el-upload-list__item {
+  .list-upload-style {
     width: 100%;
-    min-height: 73px;
-    height: 73px;
-    line-height: 73px;
-    .el-upload-list__item-thumbnail {
+    text-align: center;
+    .el-upload-list,
+    .el-upload--text {
+      width: 100%;
+    }
+    .el-upload-list__item-thumbnail,
+    .el-upload--picture-card {
+      min-height: 73px;
+      height: 73px;
       width: 100%;
-      object-fit: cover;
+    }
+    .uploadImg,
+    .el-upload-list__item {
+      width: 100%;
+      min-height: 73px;
+      height: 73px;
+      line-height: 73px;
+      .el-upload-list__item-thumbnail {
+        width: 100%;
+        max-width: 100px;
+        object-fit: cover;
+      }
     }
   }
-}
-.marksDiv{
-  position: relative;
-  .table-delete{
-    position: absolute;
-    right: -10px;
-    top: -3px;
+  .marksDiv {
+    position: relative;
+    .table-delete {
+      position: absolute;
+      right: -10px;
+      top: -3px;
+    }
   }
 }
-</style>
+</style>

BIN
src/assets/image/empty__empty.png


BIN
src/assets/image/music.png


+ 12 - 4
src/core/Scene.js

@@ -57,6 +57,7 @@ export default class Scene extends Mitt {
       domElement.parentNode.appendChild(stats.dom);
       stats.dom.style.pointerEvents = "none";
       stats.dom.style.left = "15%";
+      stats.dom.style.display = "none";
 
       this.onBindEvent();
 
@@ -94,17 +95,20 @@ export default class Scene extends Mitt {
         String(obj.name).includes("marker_") ||
         String(obj.name).includes("line_") ||
         String(obj.name).includes("line_point_") ||
-        String(obj.name).includes("circle_")
+        String(obj.name).includes("circle_") ||
+        String(obj.name).includes("pureText_")
       ) {
         this.scene.remove(obj);
       }
     }
   }
 
-  deleteItemById(uuid) {
+  deleteItemById(uuid, type) {
     for (var i = this.scene.children.length - 1; i >= 0; i--) {
       let obj = this.scene.children[i];
       if (obj.uuid === uuid) {
+        console.log("deleteItemById-userData", obj.userData);
+        this.player.deleteItemByType(type, obj.userData);
         this.scene.remove(obj);
       }
     }
@@ -127,7 +131,9 @@ export default class Scene extends Mitt {
       this.player.floorplanControls.enablePan = false;
     }
   }
-
+  setMode(mode) {
+    this.player.setMode(mode);
+  }
   onResize = (width, height) => {
     this.width = width !== undefined ? width : this.domElement.clientWidth;
     this.height = height !== undefined ? height : this.domElement.clientHeight;
@@ -153,7 +159,9 @@ export default class Scene extends Mitt {
     stats.end();
     requestAnimationFrame(this.animate);
   };
-
+  editing(item) {
+    this.player.editing(item);
+  }
   onBindEvent = () => {
     //window.addEventListener('resize', this.onResize)
   };

+ 4 - 1
src/core/box/object/CircleTextLabel.js

@@ -4,8 +4,9 @@ import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2.js";
 import { LineGeometry } from "three/examples/jsm/lines/LineGeometry.js";
 
 export default class CircleTextLabel extends THREE.Mesh {
-  constructor(text, outline) {
+  constructor(text, pos) {
     let res = 5;
+    let point = new THREE.Vector3().copy(pos);
     const canvas = document.createElement("canvas");
     canvas.width = 128 * res;
     canvas.height = 128 * res;
@@ -55,6 +56,8 @@ export default class CircleTextLabel extends THREE.Mesh {
     line_n.position.y += 0.5;
 
     this.add(line_n);
+    this.userData = point.toArray();
+    this.position.copy(point);
     this.name = "circle_" + text;
   }
 }

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

@@ -17,7 +17,7 @@ let m = new THREE.MeshBasicMaterial({
   transparent: true,
 });
 export default class LinePoints extends Line2 {
-  constructor(points, dir, matLine) {
+  constructor(points, matLine, dir, imgId, id) {
     let g = new THREE.PlaneGeometry(0.1, 0.1);
     g.rotateX(-Math.PI / 2);
     let cross = new THREE.Mesh(g, m);
@@ -61,6 +61,15 @@ export default class LinePoints extends Line2 {
     this.name = "line_point_" + this.uuid;
     this.scale.set(1, 1, 1);
     this.position.y += 0.5;
+    if (id) {
+      this.uuid = id;
+    }
+    this.userData = {
+      id: id || this.uuid,
+      dir: dir,
+      points,
+      imgId: imgId || null,
+    };
     this.add(cross);
   }
 }

+ 26 - 14
src/core/box/object/PureTextLabel.js

@@ -1,32 +1,33 @@
 import * as THREE from "three";
 
 export default class PureTextLabel extends THREE.Mesh {
-  constructor(text, outline) {
-    let res = 5;
-    const width = 150 * res;
-    const height = 15 * res;
+  constructor(text, point, fontsize = 12, color = "#000000", id) {
+    let res = 2;
+    const width = 168 * res;
+    const height = 50 * res;
     var canvas = document.createElement("canvas");
     canvas.width = width;
     canvas.height = height;
+
     let fontFamily = "Arial";
-    let fontSize = 7 * res;
-    let offsetX = 75 * res;
-    let offsetY = 10 * res;
+    let fs = fontsize * res;
     var context = canvas.getContext("2d");
-
     context.fillStyle = "transparent";
     context.rect(0, 0, width, height);
     context.fill();
-    context.font = "normal " + fontSize + "px " + fontFamily;
-    context.fillStyle = "#000000";
+    let fontStyle = "normal " + fs + "px " + fontFamily;
+    // console.log("fontStyle", fontStyle);
+    context.font = fontStyle;
+    context.fillStyle = color;
     context.textAlign = "center";
-    context.fillText(text, offsetX, offsetY);
+    context.textBaseline = "middle";
+    context.fillText(text, width / 2, height / 2);
     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);
+    const g = new THREE.PlaneGeometry(1.5, 0.44);
     g.rotateX(-Math.PI / 2);
 
     // const texture = new THREE.CanvasTexture(canvas_map);
@@ -36,7 +37,18 @@ export default class PureTextLabel extends THREE.Mesh {
       transparent: true, // 允许材质透明
     });
     super(g, m);
-
-    this.name = "textlabel_" + text;
+    if (id) {
+      this.uuid = id;
+    }
+    const p = new THREE.Vector3().copy(point);
+    this.userData = {
+      id: this.uuid,
+      text: text,
+      color: color,
+      pos: p.toArray(),
+      fontsize: fontsize,
+    };
+    this.position.copy(p);
+    this.name = "pureText_" + text;
   }
 }

+ 1 - 1
src/core/box/object/marker.js

@@ -17,7 +17,7 @@ export default class Marker extends THREE.Mesh {
     this.rotation.y = 0;
     this.position.y = 5;
     this.position.z -= 0.02;
-    this.userData = startPoint;
+    this.userData = a.toArray();
     this.visible = true;
     this.scale.set(1, 1, 1);
     this.position.y += 0.5;

+ 254 - 23
src/core/player/Player.js

@@ -48,6 +48,7 @@ export default class Player {
     this.renderMarkers = [];
     this.activeEdges = [];
     this.renderSymbols = [];
+    this.renderTexts = [];
     this.matLine = null;
     this.lineColor = 0xe44d54;
     // 1是画线,2是标方向, 3符号, 4文本
@@ -57,7 +58,8 @@ export default class Player {
 
   setMode(mode) {
     this.mode = mode;
-    if (mode === 1 || mode === 2) {
+
+    if (mode !== 0) {
       this.reset();
       this.setEditMode();
     }
@@ -77,8 +79,10 @@ export default class Player {
       let pos = new THREE.Vector3(0, 0, -1);
       pos.unproject(this.orthCamera);
       pos.y = 5;
-      this.symbolIndex += 1;
-      this.symbol = new CircleTextLabel(this.symbolIndex, true);
+      const lastIndex = this.getLaSybIndex();
+      this.symbolIndex = lastIndex + 1;
+
+      this.symbol = new CircleTextLabel(this.symbolIndex, pos);
       this.symbol.visible = false;
       this.scene.scene.add(this.symbol);
       console.log("this.symbol", this.symbol);
@@ -89,8 +93,9 @@ export default class Player {
       let pos = new THREE.Vector3(0, 0, -1);
       pos.unproject(this.orthCamera);
       pos.y = 5;
-      this.text = new PureTextLabel(this.showText, true);
+      this.text = new PureTextLabel(this.showText, pos);
       this.text.visible = false;
+      this.showText = "文本";
       this.scene.scene.add(this.text);
       this.drawing = true;
     }
@@ -100,13 +105,16 @@ export default class Player {
     }
     this.scene.emit("mode", this.mode);
   }
+  getLaSybIndex() {
+    const maxIndexObject = this.renderSymbols.reduce(
+      (max, current) => {
+        return current.index > max.index ? current : max;
+      },
+      { index: 0, point: [] }
+    );
+    return maxIndexObject.index;
+  }
 
-  // removeMarker() {
-  //   if (this.marker) {
-  //     this.scene.scene.remove(this.marker);
-  //     this.marker = null;
-  //   }
-  // }
   setFreeMode() {
     this.floorplanControls.enablePan = true;
     this.floorplanControls.mouseButtons = {
@@ -228,6 +236,7 @@ export default class Player {
         pos.unproject(this.orthCamera);
         pos.y = 5;
         this.text.position.copy(pos);
+        this.text.userData.pos = pos.toArray();
       }
     }
   };
@@ -244,10 +253,31 @@ export default class Player {
       intersects.forEach((i) => {
         if (
           String(i.object.name).includes("marker") ||
-          String(i.object.name).includes("line")
+          String(i.object.name).includes("line") ||
+          String(i.object.name).includes("circle") ||
+          String(i.object.name).includes("pureText")
         ) {
+          let type;
+          switch (true) {
+            case String(i.object.name).includes("marker"):
+              type = 1;
+              break;
+            case String(i.object.name).includes("line"):
+              type = 2;
+              break;
+            case String(i.object.name).includes("circle"):
+              type = 3;
+              break;
+            case String(i.object.name).includes("pureText"):
+              type = 4;
+              break;
+          }
+
           this.selectItem = i.object;
-          this.scene.emit("confirmDelete", i.object.uuid);
+          this.scene.emit("confirmDelete", {
+            id: i.object.uuid,
+            type,
+          });
         }
       });
     }
@@ -312,11 +342,15 @@ export default class Player {
           this.pointerdown.y,
           -1
         );
+        lasPos.unproject(this.orthCamera);
+        lasPos.y = 5;
+
         const activeSymbolItem = {
-          id: this.symbolIndex,
+          index: this.symbolIndex,
           point: lasPos.toArray(),
         };
         this.renderSymbols.push(activeSymbolItem);
+        console.log("activeSymbolItem", activeSymbolItem);
         this.setMode(0);
       }
     }
@@ -327,8 +361,13 @@ export default class Player {
           this.pointerdown.y,
           -1
         );
-        this.scene.emit("lockText");
-        this.drawing = false;
+        this.scene.emit("edit", {
+          type: 4,
+          text: this.showText,
+          id: this.text.uuid,
+          ...this.text.userData,
+        });
+        this.drawing = true;
         // const activeSymbolItem = {
         //   id: this.symbolIndex,
         //   point: lasPos.toArray(),
@@ -348,24 +387,44 @@ export default class Player {
       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 finishLine = new LinePoints(points, this.matLine, dir, imageId);
 
+        this.scene.scene.add(finishLine);
         const activeLineItem = {
+          id: finishLine.uuid,
+          imgId: imageId,
+          dir: dir,
+          points: points,
+        };
+        const activeEdgeItem = {
           id: imageId,
           dir: [dir],
         };
 
+        this.renderLines.push(activeLineItem);
         console.log("this.touchImg", activeLineItem, points);
-        this.insertActiveEdge(activeLineItem);
+        this.insertActiveEdge(activeEdgeItem);
+        this.scene.scene.remove(this.drawLine);
         this.drawLine = null;
       }
     }
     if (this.mode === 2) {
       // this.drawing = false;
     }
+    if (this.mode === 4) {
+      if (this.text) {
+        let pos = new THREE.Vector3(this.pointerup.x, this.pointerup.y, -1);
+        pos.unproject(this.orthCamera);
+        pos.y = 5;
+        this.text.position.copy(pos);
+        this.text.userData.pos = pos.toArray();
+        const activeTextItem = this.text.userData;
+        this.drawing = false;
+        console.log("activeTextItem", activeTextItem);
+        this.insertrenderTexts(activeTextItem);
+      }
+    }
     this.syncDrawData();
   };
 
@@ -487,6 +546,16 @@ export default class Player {
       this.activeEdges.push(item);
     }
   }
+
+  insertrenderTexts(item) {
+    const index = this.renderTexts.findIndex((s) => item.id === s.id);
+    if (index > -1) {
+      this.renderTexts[index] = item;
+    } else {
+      this.renderTexts.push(item);
+    }
+  }
+
   showAllActiveEdges() {
     if (this.inited) {
       let imgList = this.scene.boxManager.imgList;
@@ -495,9 +564,14 @@ export default class Player {
           const exist = imgList.find((item) => item.userData === edge.id);
           // console.log("exist", exist);
           if (exist) {
+            let others = [0, 1, 2, 3].filter((x) => !edge.dir.includes(x));
             edge.dir.forEach((dir) => {
               exist.touchLines.children[dir].visible = true;
             });
+            // console.log("others", others);
+            others.forEach((dir) => {
+              exist.touchLines.children[dir].visible = false;
+            });
           }
         });
       } else {
@@ -512,6 +586,75 @@ export default class Player {
       }
     }
   }
+  deleteItemByType(type, data) {
+    if (type === 1) {
+      const index = this.renderMarkers.findIndex((mk) => {
+        const p = new THREE.Vector3().fromArray(mk.point);
+        const v = new THREE.Vector3().fromArray(data);
+        return p.equals(v);
+      });
+      this.renderMarkers.splice(index, 1);
+    }
+    if (type === 2) {
+      const { imgId, id, dir, points } = data;
+      const index = this.renderLines.findIndex((item) => item.id === id);
+      index > -1 && this.renderLines.splice(index, 1);
+      //线段处理完成
+      const egIndex = this.activeEdges.findIndex((eg) => eg.id === imgId);
+      if (egIndex > -1) {
+        //存在activeEdge 再找renderLines的sibling
+        const cluEgArr = this.renderLines
+          .filter((l) => l.imgId === this.activeEdges[egIndex].id)
+          .reduce((pre, curr) => pre.concat(curr["dir"]), []);
+        const uni_dir = [...new Set(cluEgArr)];
+        console.log("uni_dir", uni_dir);
+        if (uni_dir.length > 0) {
+          this.activeEdges[egIndex].dir = uni_dir;
+        } else {
+          this.activeEdges.splice(egIndex, 1);
+        }
+        console.log("exist", this.activeEdges);
+        // this.showAllActiveEdges();
+      }
+    }
+    if (type === 3) {
+      const index = this.renderSymbols.findIndex((syb) => {
+        const p = new THREE.Vector3().fromArray(syb.point);
+        const v = new THREE.Vector3().fromArray(data);
+        return p.equals(v);
+      });
+      this.renderSymbols.splice(index, 1);
+    }
+    if (type === 4) {
+      const { id } = data;
+      const index = this.renderTexts.findIndex((item) => item.id === id);
+      index > -1 && this.renderTexts.splice(index, 1);
+    }
+  }
+  editing(item) {
+    if (item.type === 4) {
+      if (this.text) {
+        const { pos } = this.text.userData;
+        const newP = new THREE.Vector3().fromArray(pos);
+        this.scene.scene.remove(this.text);
+        this.text = null;
+        this.showText = item.text;
+        console.log("editing", item, newP);
+        // // console.log("this.text", lastPos, newP, item);
+        this.text = new PureTextLabel(
+          item.text,
+          newP,
+          item.fontsize,
+          item.color,
+          item.id
+        );
+        this.scene.scene.add(this.text);
+        const activeTextItem = this.text.userData;
+        console.log("activeTextItem", activeTextItem);
+        this.insertrenderTexts(activeTextItem);
+      }
+    }
+  }
   getDrawData() {
     let data;
     if (this.scene.sceneType === 1) {
@@ -519,18 +662,26 @@ export default class Player {
         hor_lines: this.renderLines,
         hor_activeEdges: this.activeEdges,
         hor_markers: this.renderMarkers,
+        hor_symbols: this.renderSymbols,
+        hor_texts: this.renderTexts,
         vir_lines: [],
         vir_activeEdges: [],
         vir_markers: [],
+        vir_symbols: [],
+        vir_texts: [],
       };
     } else {
       data = {
         hor_lines: [],
         hor_activeEdges: [],
         hor_markers: [],
+        hor_symbols: [],
+        hor_texts: [],
         vir_lines: this.renderLines,
         vir_activeEdges: this.activeEdges,
         vir_markers: this.renderMarkers,
+        vir_symbols: this.renderSymbols,
+        vir_texts: this.renderTexts,
       };
     }
 
@@ -545,12 +696,24 @@ export default class Player {
   load(type, data) {
     if (type === 1) {
       console.log("data1", data);
-      const { hor_activeEdges, hor_lines, hor_markers } = data;
+      const {
+        hor_activeEdges,
+        hor_lines,
+        hor_markers,
+        hor_symbols,
+        hor_texts,
+      } = 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);
+          const finishLine = new LinePoints(
+            line.points,
+            this.matLine,
+            line.dir,
+            line.imgId,
+            line.id
+          );
           this.scene.scene.add(finishLine);
         });
       }
@@ -563,15 +726,52 @@ export default class Player {
           this.scene.scene.add(marker);
         });
       }
+
+      if (hor_symbols && Array.isArray(hor_symbols)) {
+        this.renderSymbols = hor_symbols;
+        hor_symbols.forEach((syb) => {
+          console.log("pos");
+          const p = new THREE.Vector3().fromArray(syb.point);
+          const symbol = new CircleTextLabel(syb.index, p);
+          this.scene.scene.add(symbol);
+        });
+      }
+      if (hor_texts && Array.isArray(hor_texts)) {
+        this.renderTexts = hor_texts;
+        hor_texts.forEach((txt) => {
+          console.log("pos");
+          const p = new THREE.Vector3().fromArray(txt.pos);
+          const text = new PureTextLabel(
+            txt.text,
+            p,
+            txt.fontsize,
+            txt.color,
+            txt.id
+          );
+          this.scene.scene.add(text);
+        });
+      }
     }
 
     if (type === 2) {
-      const { vir_activeEdges, vir_lines, vir_markers } = data;
+      const {
+        vir_activeEdges,
+        vir_lines,
+        vir_markers,
+        vir_symbols,
+        vir_texts,
+      } = 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);
+          const finishLine = new LinePoints(
+            line.points,
+            this.matLine,
+            line.dir,
+            line.imgId,
+            line.id
+          );
           this.scene.scene.add(finishLine);
         });
       }
@@ -583,6 +783,30 @@ export default class Player {
           this.scene.scene.add(marker);
         });
       }
+      if (vir_symbols && Array.isArray(vir_symbols)) {
+        this.renderSymbols = vir_symbols;
+        vir_symbols.forEach((syb) => {
+          // console.log("pos", syb);
+          const p = new THREE.Vector3().fromArray(syb.point);
+          const symbol = new CircleTextLabel(syb.index, p);
+          this.scene.scene.add(symbol);
+        });
+      }
+      if (vir_texts && Array.isArray(vir_texts)) {
+        this.renderTexts = vir_texts;
+        vir_texts.forEach((txt) => {
+          console.log("pos");
+          const p = new THREE.Vector3().fromArray(txt.pos);
+          const text = new PureTextLabel(
+            txt.text,
+            p,
+            txt.fontsize,
+            txt.color,
+            txt.id
+          );
+          this.scene.scene.add(text);
+        });
+      }
     }
     this.syncDrawData();
   }
@@ -601,6 +825,11 @@ export default class Player {
     if (this.activeEdge) {
       this.activeEdge = null;
     }
+    if (this.text) {
+      this.text = null;
+      this.scene.scene.remove(this.text);
+    }
+    this.showText = "文本";
     this.drawing = false;
   }
 
@@ -608,6 +837,8 @@ export default class Player {
     this.activeEdges = [];
     this.renderLines = [];
     this.renderMarkers = [];
+    this.renderSymbols = [];
+    this.renderTexts = [];
     this.reset();
     this.scene.clearDrawScene();
     this.syncDrawData();

+ 1 - 1
src/store/scene.ts

@@ -200,7 +200,7 @@ export const downQuoteSceneHash = async (scene: QuoteScene) => {
     params: { num: scene.num, ingoreRes: true },
     responseType: "text",
   })) as any;
-  await downHash(res, scene.sceneName);
+  await downHash(res, scene.title);
 };
 
 export type QueryDownloadQuoteSceneParams = PaggingReq<{

+ 1 - 6
src/view/camera/bind.vue

@@ -54,12 +54,7 @@ const bindCamera = ref<Camera>(
   (props.camera ? { ...props.camera } : { cameraSn: "", snCode: "" }) as Camera
 );
 
-const cameraTypes = [
-  CameraType.SWKK,
-  CameraType.SWKJ,
-  CameraType.SWSS1,
-  CameraType.SWSS2,
-];
+const cameraTypes = [CameraType.SWKJ, CameraType.SWSS1, CameraType.SWSS2];
 const users = ref<UserInfo[]>([]);
 watchEffect(async () => {
   if (bindCamera.value.deptId) {

+ 8 - 30
src/view/case/caseFile.vue

@@ -1,10 +1,5 @@
 <template>
-  <com-head
-    :options="options"
-    v-model="currentTypeId"
-    notContent
-    v-if="options.length"
-  />
+  <com-head :options="options" v-model="currentTypeId" notContent v-if="options.length" />
 
   <div class="body-layer">
     <template v-if="currentTypeId === 2">
@@ -13,7 +8,7 @@
     <template v-else-if="currentTypeId === 3">
       <Records :caseId="caseId" :title="caseInfoData.caseTitle" />
     </template>
-    <template v-else-if="currentTypeId === 5">
+    <template v-else-if="currentTypeId === 4">
       <Manifest :caseId="caseId" :title="caseInfoData.caseTitle" />
     </template>
     <template v-else>
@@ -28,27 +23,17 @@
               创建{{ BoardTypeDesc[BoardType.scene] }}
             </el-button>
           </template>
-          <el-button type="primary" @click="addCaseFileHandler">
-            上传
-          </el-button>
+          <el-button type="primary" @click="addCaseFileHandler"> 上传 </el-button>
         </div>
       </div>
 
-      <el-table
-        :data="files"
-        tooltip-effect="dark"
-        style="width: 100%"
-        size="large"
-      >
+      <el-table :data="files" tooltip-effect="dark" style="width: 100%" size="large">
         <el-table-column label="序号" width="70" v-slot:default="{ $index }">
           <div style="text-align: center">
             {{ $index + 1 }}
           </div>
         </el-table-column>
-        <el-table-column
-          label="名称"
-          v-slot:default="{ row }: { row: CaseFile }"
-        >
+        <el-table-column label="名称" v-slot:default="{ row }: { row: CaseFile }">
           <span v-if="!inputCaseTitles.includes(row)">
             {{ row.filesTitle }}
             <el-icon class="edit-title" @click="inputCaseTitles.push(row)">
@@ -72,10 +57,7 @@
           </template>
         </el-table-column>
         <el-table-column label="创建时间" prop="createTime"></el-table-column>
-        <el-table-column
-          label="操作"
-          v-slot:default="{ row }: { row: CaseFile }"
-        >
+        <el-table-column label="操作" v-slot:default="{ row }: { row: CaseFile }">
           <span class="oper-span" @click="query(row)"> 查看 </span>
           <span
             class="oper-span"
@@ -129,9 +111,7 @@ const updateFileTitle = async (caseFile: CaseFile) => {
     return ElMessage.error("卷宗标题不能为空!");
   }
   await updateCaseInfo(caseFile);
-  inputCaseTitles.value = inputCaseTitles.value.filter(
-    (item) => item !== caseFile
-  );
+  inputCaseTitles.value = inputCaseTitles.value.filter((item) => item !== caseFile);
 };
 
 const currentTypeId = ref<number>();
@@ -154,9 +134,7 @@ const refresh = async () => {
 watchEffect(() => caseId.value && currentTypeId.value && refresh());
 
 const query = (file: CaseFile) => {
-  const ext = file.filesUrl
-    .substring(file.filesUrl.lastIndexOf("."))
-    .toLocaleLowerCase();
+  const ext = file.filesUrl.substring(file.filesUrl.lastIndexOf(".")).toLocaleLowerCase();
   if ([".raw", ".dcm"].includes(ext)) {
     window.open(
       `/xfile-viewer/index.html?file=${file.filesUrl}&name=${file.filesTitle}&time=` +

+ 2 - 0
src/view/case/help.ts

@@ -301,6 +301,8 @@ export enum OpenType {
 export const openSceneUrl = async (scene: Scene, type: OpenType) => {
   const pathname = SceneTypePaths[scene.type][type];
   const url = new URL(pathname || "", window.location.href);
+
+  url.searchParams.append("lang", "zh");
   if (scene.type === SceneType.SWMX) {
     url.searchParams.append(
       "modelId",

+ 154 - 0
src/view/case/photos/edit.vue

@@ -0,0 +1,154 @@
+<template>
+  <div class="layout" v-if="isShow">
+    <el-icon class="close" @click="handleClose">
+      <Close />
+    </el-icon>
+    <el-form :inline="true" :model="form" label-width="auto">
+      <el-form-item label="内容">
+        <el-input type="input" :maxlength="40" v-model="form.text" />
+      </el-form-item>
+
+      <el-form-item label="字号:">
+        <el-select
+          v-model="form.fontsize"
+          placeholder="选择字号"
+          style="width: 200px"
+        >
+          <el-option
+            v-for="item in fontSizeOptions"
+            v-bind="item"
+            :key="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="颜色:">
+        <el-color-picker
+          v-model="form.color"
+          color-format="rgba"
+          show-alpha
+          :predefine="predefineColors"
+        />
+      </el-form-item>
+      <el-form-item label="删除:">
+        <el-button type="primary" @click="handleDel">删除</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+<script setup>
+import { reactive, ref, watch } from "vue";
+
+const isShow = ref(false);
+const props = defineProps({ show: Boolean, data: Object });
+const emit = defineEmits(["update", "del", "close"]);
+
+const predefineColors = [
+  "#ff0f00",
+  "#ffbe00",
+  "#1a9bff",
+  "#1aad19",
+  "#000000",
+  "#ffffff",
+  "#666666",
+];
+
+watch(
+  props,
+  ({ show, data }) => {
+    isShow.value = show;
+    form.text = data.text;
+    form.id = data.id;
+    form.type = data.type;
+    form.pos = data.pos;
+    form.fontsize = data.fontsize || 12;
+    console.log("data", data);
+  },
+  {
+    deep: true,
+  }
+);
+
+// do not use same name with ref
+const defaultfrom = {
+  id: "",
+  text: "",
+  fontsize: 12,
+  type: null,
+  pos: null,
+  color: "#000000",
+};
+let form = reactive(defaultfrom);
+
+watch(
+  form,
+  () => {
+    handleUpdate();
+  },
+  {
+    deep: true,
+  }
+);
+
+const fontSizeRange = [8, 30];
+const fontSizeOptions = [];
+for (let i = fontSizeRange[0]; i <= fontSizeRange[1]; i++) {
+  fontSizeOptions.push({ value: i, label: i.toString() });
+}
+
+const handleClose = () => {
+  isShow.value = false;
+  emit("close", form);
+  form = reactive(defaultfrom);
+};
+const handleUpdate = () => {
+  emit("update", form);
+};
+
+const handleDel = () => {
+  isShow.value = false;
+  emit("del", form);
+  form = reactive(defaultfrom);
+};
+</script>
+
+<style>
+.layout {
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  right: 0;
+  background: #fff;
+  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+  z-index: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 15px 25px 0 10px;
+  width: fit-content;
+  /* width: 300px; */
+}
+.layout .el-form {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: flex-start;
+  max-width: 300px;
+}
+.layout .el-form-item {
+  margin-left: 10px;
+  margin-right: 10px;
+  flex: 0 0 auto;
+  display: flex;
+  align-items: center;
+}
+.close {
+  align-self: start;
+  font-size: 14px;
+  color: rgba(0, 0, 0, 0.85);
+  position: absolute !important;
+  right: 10px;
+  top: 10px;
+  cursor: pointer;
+}
+</style>

+ 66 - 66
src/view/case/photos/index.vue

@@ -1,8 +1,8 @@
 <template>
-  <div class="photo ">
+  <div class="photo">
     <div class="left">
       <div class="upload my-photo-upload">
-          <!-- <el-upload
+        <!-- <el-upload
           v-model:file-list="fileList"
           class="upload-demo"
           multiple
@@ -47,45 +47,21 @@
           >退出编辑</el-button
         >
       </div>
-      <swiper
-        class="swiper"
-        v-if="false"
-        slides-per-view="auto"
-        :space-between="24"
-        :centeredSlides="true"
-        @swiper="onSwiper"
-        style="height: 100%"
-        @slideChange="onSlideChange"
-      >
-        <swiper-slide
-          class="swiperItem"
-          v-for="(item, index) in newlist"
-          :key="index"
-        >
-          <div class="swiperList">
-            <div
-              class="itemper"
-              :class="{ oneItemper: sortType }"
-              v-for="eleItem in item"
-              :key="eleItem"
-            >
-              <img class="itemImg" :src="eleItem.imgUrl" alt="" />
-              <div class="text">{{ eleItem.imgInfo }}</div>
-            </div>
-            <div class="page">
-              <span style="margin-right: 16px">第 {{ index + 1 }} 页</span>
-              <span>共 {{ newlist.length }} 页</span>
-            </div>
-          </div>
-        </swiper-slide>
-      </swiper>
+
       <canvas id="canvas" v-show="true"></canvas>
+      <edit
+        :show="editing.show"
+        :data="editing.data"
+        @update="handleEditingUpdate"
+        @del="handleEditingDel"
+        @close="handleEditingClose"
+      />
     </div>
   </div>
 </template>
 
 <script setup>
-import { onMounted, ref, computed, onUnmounted } from "vue";
+import { onMounted, ref, computed, onUnmounted, reactive } from "vue";
 import { Menu, FullScreen } from "@element-plus/icons-vue";
 import { Swiper, SwiperSlide } from "swiper/vue";
 import "swiper/css";
@@ -94,9 +70,16 @@ import { addCaseImgFile, addCaseImgFileAll } from "../quisk";
 import { saveCaseImgTagData, getCaseImgTagData } from "@/store/case";
 import Scene from "@/core/Scene.js";
 import draggable from "./draggable.vue";
+import edit from "./edit.vue";
+
 import { ElMessage, ElMessageBox } from "element-plus";
 
 const props = defineProps({ caseId: Number });
+
+const editing = ref({
+  show: false,
+  data: {},
+});
 const newlist = ref([]);
 const fileList = ref([]);
 const swiperRef = ref(null);
@@ -194,14 +177,15 @@ const renderCanvas = () => {
   scene.on("markerExist", () => {
     ElMessage.error("该案件已有方向标注!");
   });
-  scene.on("confirmDelete", async (uuid) => {
+  scene.on("confirmDelete", async ({ id, type }) => {
     const res = await ElMessageBox.confirm("是否删除该部件?", "温馨提示", {
       confirmButtonText: "确定",
       cancelButtonText: "取消",
       type: "default",
     });
     if (res) {
-      window.scene.deleteItemById(uuid);
+      window.scene.deleteItemById(id, type);
+
     }
   });
   scene.on("data", (data) => {
@@ -216,6 +200,12 @@ const renderCanvas = () => {
     hasDrawData.value = hasData;
     console.log("sync", data, hasData);
   });
+  scene.on("edit", (editData) => {
+    console.log("editData", editData);
+    editing.value.show = true;
+    editing.value.data = editData;
+    // debugger;
+  });
 };
 const onSwiper = (swiper) => {
   console.log("onSwiper");
@@ -226,14 +216,14 @@ const onSlideChange = (swiper) => {
 };
 const handleChange = (val, list) => {
   fileList.value = list;
-  console.log('handleChange',val, list, fileList.value);
-}
+  console.log("handleChange", val, list, fileList.value);
+};
 const handleRequest = (val, list) => {
-  console.log('handleRequest',val, list);
-}
+  console.log("handleRequest", val, list);
+};
 const handleUpload = (val) => {
-  console.log('handleUpload', val);
-}
+  console.log("handleUpload", val);
+};
 const handleItem = (item) => {
   let active = sortType.value ? item : Math.floor(item / 2);
   // swiperRef.value.slideTo(active);
@@ -250,23 +240,23 @@ const handleDetele = async (item) => {
 };
 const handleLine = () => {
   if (window.scene) {
-    window.scene.player.setMode(1);
+    window.scene.setMode(1);
   }
 };
 const handleMark = () => {
   if (window.scene) {
-    window.scene.player.setMode(2);
+    window.scene.setMode(2);
   }
 };
 
 const handleSymbol = () => {
   if (window.scene) {
-    window.scene.player.setMode(3);
+    window.scene.setMode(3);
   }
 };
 const handleText = () => {
   if (window.scene) {
-    window.scene.player.setMode(4);
+    window.scene.setMode(4);
   }
 };
 const handleSave = async () => {
@@ -285,7 +275,7 @@ const handleSave = async () => {
 };
 const handleFree = () => {
   if (window.scene) {
-    window.scene.player.setMode(0);
+    window.scene.setMode(0);
   }
 };
 const handleClear = () => {
@@ -293,30 +283,41 @@ const handleClear = () => {
     window.scene.player.clear();
   }
 };
-onUnmounted(() => {});
+
+const handleEditingUpdate = (data) => {
+  // console.log("update", data);
+  if (window.scene) {
+    window.scene.editing(data);
+  }
+};
+const handleEditingDel = (form) => {
+  if (window.scene) {
+    const { id, type } = form;
+    console.log("handleEditingDel", form);
+    window.scene.deleteItemById(id, type);
+    window.scene.setMode(0);
+  }
+};
+const handleEditingClose = () => {
+  window.scene.setMode(0);
+};
 
 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">
-.my-photo-upload{
-      .upload-demo{    
-        display: inline-block;
-        margin-right: 20px;
-        position: relative;
-        bottom: -1px;
-        .el-upload-list{
-          display: none;
-        }
-      }
+.my-photo-upload {
+  .upload-demo {
+    display: inline-block;
+    margin-right: 20px;
+    position: relative;
+    bottom: -1px;
+    .el-upload-list {
+      display: none;
+    }
+  }
 }
 </style>
 <style lang="scss" scoped>
@@ -337,7 +338,6 @@ onMounted(() => {
     background: #ffffff;
     box-shadow: 10px 0 10px -10px rgba(0, 0, 0, 0.15);
     // box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.15);
-
   }
 
   .right {

+ 1 - 1
src/view/case/records/index.vue

@@ -128,7 +128,7 @@
         />
       </div>
       <div class="line">
-        <span>勘验人员姓名、单位、职务(含技术职务):</span>
+        <span>勘验人员姓名、勘验人职务(含技术职务):</span>
         <el-input
           class="input"
           type="tel"

+ 30 - 26
src/view/statistics/statisticsInject.ts

@@ -162,30 +162,30 @@ export const statisticsConfigs: ConfigItem[] = reactive([
       ],
     },
   },
-  // {
-  //   title: "火灾原因统计",
-  //   data: {
-  //     tooltip: {
-  //       trigger: "axis",
-  //     },
-  //     xAxis: {
-  //       type: "category",
-  //       data: [],
-  //     },
-  //     yAxis: {
-  //       type: "value",
-  //     },
-  //     series: [
-  //       {
-  //         label: {
-  //           show: true,
-  //         },
-  //         data: [],
-  //         type: "bar",
-  //       },
-  //     ],
-  //   },
-  // },
+  {
+    title: "火灾原因统计",
+    data: {
+      tooltip: {
+        trigger: "axis",
+      },
+      xAxis: {
+        type: "category",
+        data: [],
+      },
+      yAxis: {
+        type: "value",
+      },
+      series: [
+        {
+          label: {
+            show: true,
+          },
+          data: [],
+          type: "bar",
+        },
+      ],
+    },
+  },
 ]);
 
 const numRotate = 8;
@@ -212,7 +212,9 @@ export const updateParams = async (params: StatisticsParams) => {
       }));
       console.log(statisticsConfigs[ndx].data);
     } else {
-      statisticsConfigs[ndx].data.xAxis.data = items.map((item) => item.groupKey);
+      statisticsConfigs[ndx].data.xAxis.data = items.map(
+        (item) => item.groupKey
+      );
       statisticsConfigs[ndx].data.xAxis.axisLabel = {
         interval: "auto",
         rotate: 0,
@@ -263,7 +265,9 @@ export const updateParams = async (params: StatisticsParams) => {
           },
         ];
       }
-      statisticsConfigs[ndx].data.series![0].data = items.map((item) => item.dataCount);
+      statisticsConfigs[ndx].data.series![0].data = items.map(
+        (item) => item.dataCount
+      );
     }
   }
 };

+ 1 - 1
vite.config.ts

@@ -45,7 +45,7 @@ export default defineConfig({
     host: "0.0.0.0",
     proxy: {
       "/api": {
-        target: dev ? "http://xj-mix3d.4dkankan.com" : "mix3d.4dkankan.com",
+        target: dev ? "https://xj-mix3d.4dkankan.com" : "mix3d.4dkankan.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(new RegExp(`^/api`), ""),
       },