浏览代码

Merge branch 'master' into v.1.7.0

bill 11 月之前
父节点
当前提交
fab4f410cd
共有 99 个文件被更改,包括 4666 次插入1401 次删除
  1. 805 353
      pnpm-lock.yaml
  2. 9 2
      src/app/criminal/constant.ts
  3. 2 1
      src/app/criminal/routeConfig.ts
  4. 296 0
      src/app/criminal/view/login/index.vue
  5. 75 0
      src/app/criminal/view/login/style.scss
  6. 2 1
      src/app/fire/routeConfig.ts
  7. 2 1
      src/app/xmfire/routeConfig.ts
  8. 72 3
      src/assets/icon/fuse/demo_index.html
  9. 15 3
      src/assets/icon/fuse/iconfont.css
  10. 1 1
      src/assets/icon/fuse/iconfont.js
  11. 21 0
      src/assets/icon/fuse/iconfont.json
  12. 二进制
      src/assets/icon/fuse/iconfont.ttf
  13. 二进制
      src/assets/icon/fuse/iconfont.woff
  14. 二进制
      src/assets/icon/fuse/iconfont.woff2
  15. 二进制
      src/assets/image/criminal-32.png
  16. 二进制
      src/assets/image/criminal.ico
  17. 二进制
      src/assets/image/criminalBanner.png
  18. 18 1
      src/assets/style/public.scss
  19. 1 1
      src/constant/permission.ts
  20. 2 1
      src/helper/message.ts
  21. 26 14
      src/helper/mount.ts
  22. 7 3
      src/hook/upload.ts
  23. 8 0
      src/request/urls.ts
  24. 1 1
      src/router/config.ts
  25. 1 1
      src/router/routeName.ts
  26. 1 1
      src/store/permission.ts
  27. 1 1
      src/store/scene.ts
  28. 1 0
      src/store/user.ts
  29. 52 3
      src/util/image-rotate.ts
  30. 44 4
      src/util/index.ts
  31. 551 0
      src/util/mt4.ts
  32. 1 1
      src/view/case/caseFile.vue
  33. 23 0
      src/view/case/draw/board/editCAD/Controls/MoveBgImage.js
  34. 21 0
      src/view/case/draw/board/editCAD/Controls/MoveCustomImage.js
  35. 531 288
      src/view/case/draw/board/editCAD/Controls/UIControl.js
  36. 4 0
      src/view/case/draw/board/editCAD/Coordinate.js
  37. 2 5
      src/view/case/draw/board/editCAD/FloorplanData.js
  38. 5 0
      src/view/case/draw/board/editCAD/Geometry/Arrow.js
  39. 60 6
      src/view/case/draw/board/editCAD/Geometry/BgImage.js
  40. 5 0
      src/view/case/draw/board/editCAD/Geometry/Circle.js
  41. 13 6
      src/view/case/draw/board/editCAD/Geometry/Compass.js
  42. 102 0
      src/view/case/draw/board/editCAD/Geometry/CustomImage.js
  43. 1 0
      src/view/case/draw/board/editCAD/Geometry/Geometry.js
  44. 5 0
      src/view/case/draw/board/editCAD/Geometry/Rectangle.js
  45. 8 0
      src/view/case/draw/board/editCAD/Geometry/Sign.js
  46. 3 2
      src/view/case/draw/board/editCAD/Geometry/Table.js
  47. 31 0
      src/view/case/draw/board/editCAD/Geometry/Tag.js
  48. 1 1
      src/view/case/draw/board/editCAD/Geometry/Title.js
  49. 5 0
      src/view/case/draw/board/editCAD/Geometry/Wall.js
  50. 83 41
      src/view/case/draw/board/editCAD/History/Change.js
  51. 74 64
      src/view/case/draw/board/editCAD/History/History.js
  52. 62 41
      src/view/case/draw/board/editCAD/History/HistoryUtil.js
  53. 51 15
      src/view/case/draw/board/editCAD/Layer.js
  54. 85 5
      src/view/case/draw/board/editCAD/ListenLayer.js
  55. 34 24
      src/view/case/draw/board/editCAD/Load.js
  56. 221 78
      src/view/case/draw/board/editCAD/Renderer/Draw.js
  57. 54 42
      src/view/case/draw/board/editCAD/Renderer/Render.js
  58. 1 0
      src/view/case/draw/board/editCAD/Service/ArrowService.js
  59. 36 0
      src/view/case/draw/board/editCAD/Service/BgImageService.js
  60. 1 0
      src/view/case/draw/board/editCAD/Service/CircleService.js
  61. 42 0
      src/view/case/draw/board/editCAD/Service/CustomImageService.js
  62. 54 34
      src/view/case/draw/board/editCAD/Service/FloorplanService.js
  63. 1 0
      src/view/case/draw/board/editCAD/Service/RectangleService.js
  64. 1 0
      src/view/case/draw/board/editCAD/Service/SignService.js
  65. 8 0
      src/view/case/draw/board/editCAD/Service/StateService.js
  66. 2 0
      src/view/case/draw/board/editCAD/Service/TagService.js
  67. 1 0
      src/view/case/draw/board/editCAD/Service/WallService.js
  68. 60 60
      src/view/case/draw/board/editCAD/Style.js
  69. 7 1
      src/view/case/draw/board/editCAD/enum/HistoryEvents.js
  70. 6 0
      src/view/case/draw/board/editCAD/enum/LayerEvents.js
  71. 2 0
      src/view/case/draw/board/editCAD/enum/UIEvents.js
  72. 1 0
      src/view/case/draw/board/editCAD/enum/VectorType.js
  73. 6 1
      src/view/case/draw/board/index.d.ts
  74. 95 51
      src/view/case/draw/board/index.js
  75. 1 0
      src/view/case/draw/board/shape.js
  76. 16 2
      src/view/case/draw/board/useBoard.ts
  77. 30 0
      src/view/case/draw/edit-shape/bgImage.vue
  78. 43 0
      src/view/case/draw/edit-shape/compass.vue
  79. 5 0
      src/view/case/draw/edit-shape/delete.vue
  80. 43 0
      src/view/case/draw/edit-shape/image.vue
  81. 25 0
      src/view/case/draw/edit-shape/index.ts
  82. 27 0
      src/view/case/draw/edit-shape/label.vue
  83. 9 0
      src/view/case/draw/edit-shape/preset.ts
  84. 31 0
      src/view/case/draw/edit-shape/table.vue
  85. 50 0
      src/view/case/draw/edit-shape/tag.vue
  86. 29 0
      src/view/case/draw/edit-shape/title.vue
  87. 190 52
      src/view/case/draw/editEshapeTable.vue
  88. 45 78
      src/view/case/draw/eshape.vue
  89. 36 12
      src/view/case/draw/index.vue
  90. 15 6
      src/view/case/draw/selectFuseImage.vue
  91. 102 38
      src/view/case/draw/slider.vue
  92. 18 11
      src/view/case/help.ts
  93. 1 2
      src/view/case/quisk.ts
  94. 3 0
      src/view/home/index.vue
  95. 1 1
      src/view/statistics/statisticsInject.ts
  96. 115 35
      src/view/system/imageCropper.vue
  97. 2 0
      src/view/vrmodel/quisk.ts
  98. 5 2
      src/view/vrmodel/sceneContent.vue
  99. 1 0
      vite.config.ts

文件差异内容过多而无法显示
+ 805 - 353
pnpm-lock.yaml


+ 9 - 2
src/app/criminal/constant.ts

@@ -1,13 +1,20 @@
 import { AppConstant } from "../";
 import banner from "@/assets/image/criminalBanner.png";
 import ico from "@/assets/image/criminal.ico";
+import linkIco from "@/assets/image/criminal-32.png";
+
 import { criminalDeptId } from "@/constant/appDeptId";
 
 export const appConstant: AppConstant = {
-  title: "刑事现场三维远程勘验平台",
-  desc: "Three-dimensional remote prospecting platform for criminal scenes",
+  title: "多尺度融合的现场3D数字化重建系统",
+  desc: "",
   ico,
+
   banner,
   name: "criminal",
+  loginComponent: () => import("./view/login/index.vue"),
   deptId: criminalDeptId,
 };
+
+const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
+link.setAttribute("href", linkIco);

+ 2 - 1
src/app/criminal/routeConfig.ts

@@ -7,13 +7,14 @@ export const CriminalRouteName = {
 } as const;
 
 export const menuRouteNames = [
-  // CriminalRouteName.home,
+  // CriminalRouteName.statistics,
   CriminalRouteName.vrmodel,
   CriminalRouteName.camera,
   CriminalRouteName.example,
   CriminalRouteName.organization,
   CriminalRouteName.role,
   CriminalRouteName.user,
+  CriminalRouteName.downloadLog,
 ];
 
 export const routes: Routes = [

+ 296 - 0
src/app/criminal/view/login/index.vue

@@ -0,0 +1,296 @@
+<template>
+  <div class="login-layer">
+    <div class="content">
+      <div class="info">
+        <img src="@/assets/image/criminal.ico" alt="" />
+        <h1>{{ appConstant.title }}</h1>
+      </div>
+      <el-form class="panel login" :model="form" @submit.stop>
+        <h2>欢 迎 登 录</h2>
+        <el-form-item class="panel-form-item">
+          <p class="err-info">{{ verification.phone }}</p>
+          <el-input
+            :maxlength="11"
+            v-model.trim="form.phone"
+            placeholder="手机号"
+            @keydown.enter="submitClick"
+          ></el-input>
+        </el-form-item>
+        <el-form-item class="panel-form-item">
+          <p class="err-info">{{ verification.psw }}</p>
+          <el-input
+            v-model="form.psw"
+            :maxlength="16"
+            placeholder="密码"
+            :type="flag ? 'password' : 'text'"
+            @keydown.enter="submitClick"
+          >
+            <template v-slot:suffix>
+              <img
+                v-if="flag"
+                @click="flag = !flag"
+                style="width: 20px; margin: 10px 15px"
+                src="@/assets/image/pasword.png"
+                alt=""
+              />
+              <el-icon :size="20" @click="flag = !flag" class="icon-style" v-else>
+                <View />
+              </el-icon>
+            </template>
+          </el-input>
+        </el-form-item>
+
+        <el-form-item class="panel-form-item code-form-item">
+          <p class="err-info">{{ verification.code }}</p>
+          <el-input
+            v-model="form.code"
+            placeholder="验证码"
+            @keydown.enter="submitClick"
+            class="code-input"
+          >
+            <template v-slot:append>
+              <img :src="codeImg" class="code-img" @click="refer" />
+            </template>
+          </el-input>
+        </el-form-item>
+
+        <el-form-item class="panel-form-item" style="margin-top: 18px">
+          <el-button type="primary" class="fill" @click="submitClick">登录</el-button>
+        </el-form-item>
+
+        <!-- <div class="more">
+          <a @click="$router.push({ name: 'forget' })">忘记密码</a>
+        </div> -->
+      </el-form>
+    </div>
+
+    <p class="desc">
+      公安部鉴定中心 & 珠海市四维时代网络科技有限公司 |
+      公安部科技强警基础工作计划(2022JC13)
+    </p>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, watch, ref, computed } from "vue";
+import { openErrorMsg, baseURL, getCode } from "@/request";
+import { PHONE } from "@/constant/REG";
+import { guid, strToParams } from "@/util";
+import { RouteName, router } from "@/router";
+import { login } from "@/store/system";
+import { appConstant } from "@/app";
+import { user } from "@/store/user";
+
+// 是否显示明文密码
+const flag = ref(true);
+// 表单
+const form = reactive({
+  phone: localStorage.getItem("userName") || "",
+  psw: localStorage.getItem("password") || "",
+  code: "",
+  remember: import.meta.env.DEV || localStorage.getItem("remember") === "1",
+});
+const verification = reactive({ phone: "", psw: "", code: "" });
+// 验证
+watch(
+  form,
+  () => {
+    console.log("form", form);
+    if (!form.phone) {
+      verification.phone = "请输入手机号";
+    } else if (form.phone == "88888888888") {
+      verification.phone = "";
+    } else {
+      verification.phone = PHONE.REG.test(form.phone) ? "" : PHONE.tip;
+    }
+    if (!form.psw) {
+      verification.psw = "请输入密码";
+    } else {
+      verification.psw = "";
+    }
+    if (!form.code.trim()) {
+      verification.code = "请输入验证码";
+    } else {
+      verification.code = "";
+    }
+  },
+  { immediate: true }
+);
+
+// 图片验证码
+const imgKey = ref(guid());
+const refer = () => (imgKey.value = guid());
+const codeImg = computed(() => baseURL + getCode + "?key=" + imgKey.value);
+
+// 表单提交
+const submitClick = async () => {
+  if (verification.phone && verification.phone !== "88888888888") {
+    return openErrorMsg(verification.phone);
+  }
+  if (verification.psw) return openErrorMsg(verification.psw);
+  if (verification.code) return openErrorMsg(verification.code);
+
+  try {
+    await login({ phoneNum: form.phone, code: form.code, password: form.psw });
+
+    if (form.remember) {
+      localStorage.setItem("userName", form.phone);
+      localStorage.setItem("password", form.psw);
+      localStorage.setItem("remember", "1");
+    } else {
+      localStorage.setItem("userName", "");
+      localStorage.setItem("password", "");
+      localStorage.setItem("remember", "0");
+    }
+
+    const params = strToParams(window.location.search);
+    if ("redirect" in params) {
+      const url = new URL(unescape(params.redirect));
+      url.searchParams.delete("token");
+      url.searchParams.append("token", user.value.token);
+      window.location.replace(url);
+    } else {
+      router.replace({ name: RouteName.scene });
+    }
+  } catch (e) {
+    console.error(e);
+    return refer();
+  }
+};
+</script>
+
+<style lang="sass">
+@import "./style.scss"
+</style>
+
+<style lang="scss" scoped>
+.login-layer {
+  width: 100%;
+  height: 100%;
+  background: #eceff2 url("@/assets/image/criminalBanner.png") no-repeat center center;
+  background-size: cover;
+  position: inherit;
+
+  .desc {
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 30px;
+    font-size: 14px;
+    color: #999999;
+    line-height: 30px;
+    width: 100%;
+    padding: 0 20px;
+    text-align: center;
+    letter-spacing: 1px;
+  }
+}
+.content {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  height: 100%;
+}
+.info {
+  color: #fff;
+  margin-right: 143px;
+  padding-top: 40px;
+  padding-left: 44px;
+  flex: none;
+  text-align: left;
+  display: flex;
+  align-items: center;
+
+  img {
+    width: 40px;
+    height: 40px;
+    margin-right: 20px;
+  }
+  h1 {
+    font-size: 2.2rem;
+  }
+}
+
+.login {
+  width: 400px;
+  margin-right: 12.5%;
+  position: relative;
+  display: inline-block;
+  background: none;
+  box-shadow: none;
+  align-self: center;
+
+  h2 {
+    padding-left: 0;
+    padding-bottom: 0;
+    border-bottom: none;
+    margin-bottom: 2.14rem;
+    text-align: center;
+    font-weight: bold;
+    font-size: 36px;
+
+    span {
+      color: #646566;
+      font-size: 1.33rem;
+      margin-top: 0.71rem;
+      display: block;
+    }
+  }
+
+  .panel-form-item {
+    padding-left: 0;
+    padding-right: 0;
+    .icon-style {
+      margin-right: 14px;
+      font-size: 20px;
+      line-height: 50px;
+    }
+  }
+
+  .more a:first-child::after {
+    content: "";
+    position: absolute;
+    right: -5px;
+    width: 1px;
+    height: 8px;
+    background: #dcdee0;
+    top: 50%;
+    transform: translateY(-50%);
+  }
+}
+
+.code-img {
+  height: 40px;
+}
+</style>
+
+<style>
+.login-layer .el-input {
+  --el-input-bg-color: #f6f8fb;
+  /* var(--el-fill-color-blank) */
+}
+.login .code-form-item .el-input {
+  display: flex;
+}
+
+.login .code-form-item .el-input-group__append {
+  flex: none;
+  margin-left: 10px;
+  width: 95px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0;
+}
+
+.login .code-form-item .el-input__inner {
+  flex: 1;
+}
+.login .code-form-item .el-input-group__append,
+.login .code-form-item .el-input__inner {
+  border-radius: 4px;
+}
+input[type="password"]::-ms-reveal {
+  display: none;
+}
+</style>

+ 75 - 0
src/app/criminal/view/login/style.scss

@@ -0,0 +1,75 @@
+.panel {
+  background   : rgba(255, 255, 255, 0.7);
+  box-shadow   : 0px 2px 20px 0px rgba(5, 38, 38, 0.15);
+  border-radius: 10px;
+  width        : 600px;
+  padding      : 30px 0 40px;
+  text-align   : initial;
+
+  h2 {
+    color         : #323233;
+    font-size     : 1.85rem;
+    margin-bottom : 2.14rem;
+    font-weight   : normal;
+    padding-left  : 60px;
+    padding-bottom: 20px;
+    border-bottom : 1px solid #E9E9E9;
+  }
+
+  .panel-form-item {
+    position      : relative;
+    padding-bottom: 2.14rem;
+    margin        : 0;
+    padding-left  : 90px;
+    padding-right : 90px;
+
+    &.remember {
+      padding: 0;
+    }
+
+    .err-info {
+      position   : absolute;
+      top        : 100%;
+      left       : 20px;
+      font-size  : 1rem;
+      line-height: 2.14rem;
+      color      : #FA5555;
+    }
+
+  }
+
+  .more {
+    text-align: center;
+
+    a {
+      color          : #323233;
+      line-height    : 21px;
+      font-size      : 16px;
+      margin         : 0 5px;
+      position       : relative;
+      text-decoration: none;
+      cursor         : pointer;
+    }
+  }
+}
+
+
+.panel-form-item .el-select {
+  width: 100%;
+}
+
+.panel-form-item .el-button,
+.panel-form-item .el-input__inner {
+  height   : 40px;
+  font-size: 1.14rem;
+}
+
+.panel-form-item .el-button {
+  line-height: 26px;
+  font-weight: bold;
+  font-size  : 16px;
+}
+
+.panel-form-item .el-form-item__label {
+  line-height: 40px;
+}

+ 2 - 1
src/app/fire/routeConfig.ts

@@ -8,16 +8,17 @@ export const FireRouteName = {
 } as const;
 
 export const menuRouteNames = [
-  // FireRouteName.home,
   FireRouteName.vrmodel,
   FireRouteName.camera,
   FireRouteName.dispatch,
   FireRouteName.teaching,
+  FireRouteName.statistics,
   FireRouteName.organization,
   FireRouteName.downloadLog,
   FireRouteName.statistics,
   FireRouteName.role,
   FireRouteName.user,
+  FireRouteName.downloadLog,
 ];
 
 export const routes: Routes = [

+ 2 - 1
src/app/xmfire/routeConfig.ts

@@ -8,7 +8,8 @@ export const FireRouteName = {
 } as const;
 
 export const menuRouteNames = [
-  // FireRouteName.home,
+  FireRouteName.statistics,
+  FireRouteName.downloadLog,
   FireRouteName.vrmodel,
   FireRouteName.camera,
   FireRouteName.dispatch,

+ 72 - 3
src/assets/icon/fuse/demo_index.html

@@ -55,6 +55,24 @@
           <ul class="icon_lists dib-box">
           
             <li class="dib">
+              <span class="icon iconfont">&#xe74d;</span>
+                <div class="name">fire_statistics</div>
+                <div class="code-name">&amp;#xe74d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe74b;</span>
+                <div class="name">query_home</div>
+                <div class="code-name">&amp;#xe74b;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe740;</span>
+                <div class="name">list-detail</div>
+                <div class="code-name">&amp;#xe740;</div>
+              </li>
+          
+            <li class="dib">
               <span class="icon iconfont">&#xe70e;</span>
                 <div class="name">without</div>
                 <div class="code-name">&amp;#xe70e;</div>
@@ -474,9 +492,9 @@
 <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1692171577992') format('woff2'),
-       url('iconfont.woff?t=1692171577992') format('woff'),
-       url('iconfont.ttf?t=1692171577992') format('truetype');
+  src: url('iconfont.woff2?t=1706348932330') format('woff2'),
+       url('iconfont.woff?t=1706348932330') format('woff'),
+       url('iconfont.ttf?t=1706348932330') format('truetype');
 }
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -503,6 +521,33 @@
         <ul class="icon_lists dib-box">
           
           <li class="dib">
+            <span class="icon iconfont icon-fire_statistics"></span>
+            <div class="name">
+              fire_statistics
+            </div>
+            <div class="code-name">.icon-fire_statistics
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-query_home"></span>
+            <div class="name">
+              query_home
+            </div>
+            <div class="code-name">.icon-query_home
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-list-detail"></span>
+            <div class="name">
+              list-detail
+            </div>
+            <div class="code-name">.icon-list-detail
+            </div>
+          </li>
+          
+          <li class="dib">
             <span class="icon iconfont icon-without"></span>
             <div class="name">
               without
@@ -1134,6 +1179,30 @@
           
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-fire_statistics"></use>
+                </svg>
+                <div class="name">fire_statistics</div>
+                <div class="code-name">#icon-fire_statistics</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-query_home"></use>
+                </svg>
+                <div class="name">query_home</div>
+                <div class="code-name">#icon-query_home</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-list-detail"></use>
+                </svg>
+                <div class="name">list-detail</div>
+                <div class="code-name">#icon-list-detail</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-without"></use>
                 </svg>
                 <div class="name">without</div>

+ 15 - 3
src/assets/icon/fuse/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: "iconfont"; /* Project id 3549513 */
-  src: url('iconfont.woff2?t=1692171577992') format('woff2'),
-       url('iconfont.woff?t=1692171577992') format('woff'),
-       url('iconfont.ttf?t=1692171577992') format('truetype');
+  src: url('iconfont.woff2?t=1706348932330') format('woff2'),
+       url('iconfont.woff?t=1706348932330') format('woff'),
+       url('iconfont.ttf?t=1706348932330') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,18 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-fire_statistics:before {
+  content: "\e74d";
+}
+
+.icon-query_home:before {
+  content: "\e74b";
+}
+
+.icon-list-detail:before {
+  content: "\e740";
+}
+
 .icon-without:before {
   content: "\e70e";
 }

文件差异内容过多而无法显示
+ 1 - 1
src/assets/icon/fuse/iconfont.js


+ 21 - 0
src/assets/icon/fuse/iconfont.json

@@ -6,6 +6,27 @@
   "description": "",
   "glyphs": [
     {
+      "icon_id": "39135106",
+      "name": "fire_statistics",
+      "font_class": "fire_statistics",
+      "unicode": "e74d",
+      "unicode_decimal": 59213
+    },
+    {
+      "icon_id": "38948724",
+      "name": "query_home",
+      "font_class": "query_home",
+      "unicode": "e74b",
+      "unicode_decimal": 59211
+    },
+    {
+      "icon_id": "37076167",
+      "name": "list-detail",
+      "font_class": "list-detail",
+      "unicode": "e740",
+      "unicode_decimal": 59200
+    },
+    {
       "icon_id": "33692561",
       "name": "without",
       "font_class": "without",

二进制
src/assets/icon/fuse/iconfont.ttf


二进制
src/assets/icon/fuse/iconfont.woff


二进制
src/assets/icon/fuse/iconfont.woff2


二进制
src/assets/image/criminal-32.png


二进制
src/assets/image/criminal.ico


二进制
src/assets/image/criminalBanner.png


+ 18 - 1
src/assets/style/public.scss

@@ -16,13 +16,26 @@ body {
   height: 100%;
 }
 
-body {
+:root {
   --primaryColor: var(--el-color-primary);
   --colorColor  : #303133;
   --bgColor     : #f0f2f5;
+}
+
+body {
 
   font-family: "Microsoft YaHei";
   color      : var(--colorColor);
+  overflow   : auto;
+
+}
+
+#app {
+  position  : relative;
+  min-width : 1280px;
+  min-height: 760px;
+  height    : 100%;
+  overflow  : hidden;
 }
 
 .fill.el-button {
@@ -551,4 +564,8 @@ html .el-input-group__append button.el-button {
 
 .el-cascader__dropdown {
   max-width: 100%;
+}
+
+.el-color-predefine__color-selector {
+  border: 1px solid #e5e5e5;
 }

+ 1 - 1
src/constant/permission.ts

@@ -6,7 +6,7 @@ import { DataScope } from "@/store/role";
 export const permissionChilds = ["select", "update", "delete", "add"];
 
 // 共有菜单
-export const pubPermissionMenus = [RouteName.home];
+export const pubPermissionMenus = [RouteName.statistics];
 
 // 登录用户共有权限
 export const pubPermissions: UserPermission[] = pubPermissionMenus.map(

+ 2 - 1
src/helper/message.ts

@@ -2,9 +2,10 @@ import { InfoFilled, SuccessFilled } from "@element-plus/icons-vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { markRaw } from "vue";
 
-export const confirm = (msg: string) =>
+export const confirm = (msg: string, okText = "确定") =>
   ElMessageBox.confirm(msg, "系统提示", {
     type: "warning",
+    confirmButtonText: okText,
     // icon: markRaw(InfoFilled),
   });
 export const alert = (msg: string) =>

+ 26 - 14
src/helper/mount.ts

@@ -1,7 +1,7 @@
 import { router } from "@/router";
 import { createVNode, reactive, render, watch, watchEffect } from "vue";
 import Locale from "@/config/locale.vue";
-import type { App, VNode } from "vue";
+import type { App, Ref, VNode } from "vue";
 
 export type MountContext<P = any> = {
   props?: P;
@@ -80,12 +80,12 @@ export const mountComponent = <P>(
 };
 
 import Dialog from "@/components/dialog/index.vue";
-import { DialogProps } from "@/components/dialog/type";
+import { DialogProps, dialogPropsKeys } from "@/components/dialog/type";
 
 export type QuiskExpose = {
   submit?: () => void;
   quit?: () => void;
-};
+} & Partial<{ [key in keyof DialogProps]?: Ref<DialogProps[key]> }>;
 
 export const quiskMountFactory =
   <P>(comp: ComponentConstructor<P>, dprops: DialogProps) =>
@@ -94,6 +94,7 @@ export const quiskMountFactory =
     dRef?: (expose: { quit: () => void; submit: () => void }) => void
   ): Promise<T> => {
     let ref: QuiskExpose;
+
     return new Promise((resolve) => {
       const api = {
         onQuit: async () => {
@@ -103,6 +104,7 @@ export const quiskMountFactory =
           } else {
             resolve(false as any);
           }
+          console.error('?')
           destroy();
         },
         onSubmit: async () => {
@@ -112,21 +114,31 @@ export const quiskMountFactory =
           } else {
             resolve(true as any);
           }
+          console.error('?')
           destroy();
         },
       };
 
-      const destroy = mountComponent(
-        Dialog,
-        { ...dprops, ref: undefined, show: true, ...api },
-        {
-          default: () =>
-            createVNode(comp, {
-              ...props,
-              ref: (v: any) => (ref = v),
-            }),
-        }
-      );
+      const layoutProps = reactive({
+        ...dprops,
+        ref: undefined,
+        show: true,
+        ...api,
+      });
+      const destroy = mountComponent(Dialog, layoutProps, {
+        default: () =>
+          createVNode(comp, {
+            ...props,
+            ref: (v: any) => {
+              for (const key in v) {
+                if (dialogPropsKeys.includes(key as any)) {
+                  layoutProps[key] = v[key];
+                }
+              }
+              ref = v;
+            },
+          }),
+      });
 
       dRef &&
         dRef({

+ 7 - 3
src/hook/upload.ts

@@ -30,12 +30,16 @@ export const useUpload = <T>(props: UploadProps<T>) => {
   const accept = computed(() =>
     props.formats
       .map((format) => {
-        const index = format.indexOf(".");
-        const ext = ~index ? format.substring(index + 1) : format;
-        return mime.getType(ext) as string;
+        // const index = format.indexOf(".");
+        // const ext = ~index ? format.substring(index + 1) : format;
+        // const extMime = mime.getType(ext);
+        // return (extMime.substring(0, extMime.indexOf("/") + 1) + ext) as string;
+
+        return format;
       })
       .join(", ")
   );
+  console.log(accept);
   const fileRef = ref<File>();
 
   (window as any).fileRef = fileRef;

+ 8 - 0
src/request/urls.ts

@@ -68,6 +68,14 @@ export const delScene = "/fusion/scene/deleteNum";
 export const checkGenMeshScene = "/fusion/scene/sceneDetail";
 export const genMeshSceneByCloud = "/fusion/scene/buildSceneObj";
 
+// 统计
+export const sceneStatistics = `/fusion/data/sceneGroupByDept`;
+export const caseStatistics = `/fusion/data/projectGroupByDept`;
+export const cameraTypeStatistics = `/fusion/data/cameraGroupType`;
+export const caseTimeStatistics = `/fusion/data/FireTrend`;
+export const casePlaceStatistics = `/fusion/data/FirePlaceTrend`;
+export const caseReasonStatistics = `/fusion/data/FireReasonTrend`;
+
 // 获取模型场景列表
 export const getModelSceneList = `/fusion/model/list`;
 export const updateModelScene = `/fusion/model/updateTitle`;

+ 1 - 1
src/router/config.ts

@@ -56,7 +56,7 @@ export const routes: Routes = [
       },
       {
         name: RouteName.vrmodel,
-        path: "vrmodel",
+        path: "home",
         component: () => import("@/view/vrmodel/index.vue"),
         meta: { title: "场景管理", icon: "iconfire_scenes" },
       },

+ 1 - 1
src/router/routeName.ts

@@ -5,7 +5,7 @@ export const RouteName = {
   statistics: "statistics",
   forget: "forget",
   viewLayout: "viewLayout",
-  home: "home",
+  statistics: "statistics",
   vrmodel: "scene",
   camera: "camera",
   caseFile: "caseFile",

+ 1 - 1
src/store/permission.ts

@@ -19,7 +19,7 @@ changSaveLocal("permission", () => permission.value);
  * @param routeNames 所有路由
  */
 export const getPermissionRoutes = (routeNames: string[]) => {
-  console.log(permission.value);
+  console.error(permission.value);
   return routeNames
     .filter((routeName) =>
       permission.value.some((p) => p.resourceKey === routeName)

+ 1 - 1
src/store/scene.ts

@@ -27,7 +27,7 @@ interface BaseScene {
 // 只有当location 为4 时,才能生成obj
 export enum LocationEnum {
   Scene_Location_Slam, //slam\n" +
-  Scene_Location_SFM, //sfm\n" +
+  Scene_Location_SFM, //sfm\n" +F
   Scene_Location_SFMAI, //SFM + AI\n" +
   Scene_Location_MutiFloor, //多楼层\n" +
   Scene_Location_PointCloud, //点云\n" +

+ 1 - 0
src/store/user.ts

@@ -27,6 +27,7 @@ export type UserInfo = {
   deptId: string;
   deptName: string;
   id: string;
+  deptLevel: number;
   departmentId: string;
   cameraSns: string[];
   status: 1 | 0;

+ 52 - 3
src/util/image-rotate.ts

@@ -39,15 +39,19 @@ export const imageRotate = async (
   );
 };
 
+export const loadImage = async (blob: Blob) => {
+  const img = new Image();
+  img.src = URL.createObjectURL(blob);
+  return new Promise<HTMLImageElement>((resolve) => (img.onload = () => resolve(img)));
+};
+
 export const fixImageSize = async (
   blob: Blob,
   max: number,
   min: number,
   scale = true
 ) => {
-  const img = new Image();
-  img.src = URL.createObjectURL(blob);
-  await new Promise((resolve) => (img.onload = resolve));
+  const img = await loadImage(blob);
 
   let width = img.width;
   let height = img.height;
@@ -72,6 +76,7 @@ export const fixImageSize = async (
   let size = width > height ? width : height;
   size = size > min ? size : min;
 
+  console.log(size, width, height);
   const $canvas = document.createElement("canvas");
   $canvas.width = size;
   $canvas.height = size;
@@ -86,3 +91,47 @@ export const fixImageSize = async (
   );
   return newBlob;
 };
+
+export const coverImageSize = async (
+  blob: Blob,
+  coverWidth: number,
+  coverHeight: number,
+  scale = true
+) => {
+  const img = await loadImage(blob);
+
+  let width = img.width,
+    useWidth;
+  let height = img.height,
+    useHeight;
+
+  const proportion = coverWidth / coverHeight;
+  const cProportion = width / height;
+
+  if (cProportion > proportion) {
+    useWidth = width;
+    useHeight = width / proportion;
+  } else if (cProportion < proportion) {
+    // h偏大
+    useWidth = height * proportion;
+    useHeight = height;
+  }
+
+  const $canvas = document.createElement("canvas");
+  $canvas.width = useWidth;
+  $canvas.height = useHeight;
+  const ctx = $canvas.getContext("2d")!;
+  ctx.rect(0, 0, useWidth, useHeight);
+  ctx.fillStyle = "#fff";
+  ctx.fill();
+  ctx.drawImage(img, (useWidth - width) / 2, (useHeight - height) / 2, width, height);
+
+  const newBlob = await new Promise<Blob | null>((resolve) =>
+    $canvas.toBlob(resolve, "png")
+  );
+  return {
+    blob: newBlob!,
+    width: useWidth,
+    height: useHeight,
+  };
+};

+ 44 - 4
src/util/index.ts

@@ -1,4 +1,5 @@
 import { Base64 } from "js-base64";
+import { positionTransform } from "./mt4";
 
 export const dateFormat = (date: Date, fmt: string) => {
   var o: any = {
@@ -11,7 +12,10 @@ export const dateFormat = (date: Date, fmt: string) => {
     S: date.getMilliseconds(), //毫秒
   };
   if (/(y+)/.test(fmt)) {
-    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
+    fmt = fmt.replace(
+      RegExp.$1,
+      (date.getFullYear() + "").substr(4 - RegExp.$1.length)
+    );
   }
   for (var k in o) {
     if (new RegExp("(" + k + ")").test(fmt)) {
@@ -37,7 +41,10 @@ export const copyText = (text: string) => {
 };
 
 // 防抖
-export const debounce = <T extends (...args: any) => any>(fn: T, delay: number = 160) => {
+export const debounce = <T extends (...args: any) => any>(
+  fn: T,
+  delay: number = 160
+) => {
   let timeout: any;
 
   return function <This>(this: This, ...args: Parameters<T>) {
@@ -169,7 +176,10 @@ export function encodePwd(str: string, strv = "") {
   if (strv) {
     const strv1 = strv.substring(0, NUM);
     const strv2 = strv.substring(NUM);
-    return [front + str2 + middle + str1 + end, front + strv2 + middle + strv1 + end];
+    return [
+      front + str2 + middle + str1 + end,
+      front + strv2 + middle + strv1 + end,
+    ];
   }
 
   return front + str2 + middle + str1 + end;
@@ -234,7 +244,10 @@ export const drawImage = (
 ) => {
   let dWidth = bg_w / imgWidth; // canvas与图片的宽度比例
   let dHeight = bg_h / imgHeight; // canvas与图片的高度比例
-  if ((imgWidth > bg_w && imgHeight > bg_h) || (imgWidth < bg_w && imgHeight < bg_h)) {
+  if (
+    (imgWidth > bg_w && imgHeight > bg_h) ||
+    (imgWidth < bg_w && imgHeight < bg_h)
+  ) {
     if (dWidth > dHeight) {
       ctx.drawImage(
         imgPath,
@@ -313,3 +326,30 @@ export const strToParams = (str: string) => {
 
   return result;
 };
+
+export const getDomMatrix = (dom: HTMLElement) => {
+  const str = getComputedStyle(dom, null).getPropertyValue("transform");
+  const matrix2d = str
+    .substring(7, str.length - 2)
+    .split(", ")
+    .map(Number);
+
+  return [
+    matrix2d[0],
+    matrix2d[1],
+    0,
+    0,
+    matrix2d[2],
+    matrix2d[3],
+    0,
+    0,
+    0,
+    0,
+    1,
+    0,
+    matrix2d[4] + dom.offsetWidth / 2,
+    matrix2d[5] + dom.offsetHeight / 2,
+    0,
+    1,
+  ];
+};

+ 551 - 0
src/util/mt4.ts

@@ -0,0 +1,551 @@
+type NumArr = number[];
+export const identity = () => [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
+
+// 转置矩阵
+export const transpose = (m: number[]) => {
+  return [
+    m[0],
+    m[4],
+    m[8],
+    m[12],
+    m[1],
+    m[5],
+    m[9],
+    m[13],
+    m[2],
+    m[6],
+    m[10],
+    m[14],
+    m[3],
+    m[7],
+    m[11],
+    m[15],
+  ];
+};
+
+export const orthographic = (
+  left: number,
+  right: number,
+  bottom: number,
+  top: number,
+  near: number,
+  far: number
+) => {
+  const dst = new Float32Array(16);
+
+  dst[0] = 2 / (right - left);
+  dst[1] = 0;
+  dst[2] = 0;
+  dst[3] = 0;
+  dst[4] = 0;
+  dst[5] = 2 / (top - bottom);
+  dst[6] = 0;
+  dst[7] = 0;
+  dst[8] = 0;
+  dst[9] = 0;
+  dst[10] = 2 / (near - far);
+  dst[11] = 0;
+  dst[12] = (left + right) / (left - right);
+  dst[13] = (bottom + top) / (bottom - top);
+  dst[14] = (near + far) / (near - far);
+  dst[15] = 1;
+
+  return dst;
+};
+
+export const getFrustumArgumentsOnMatrix = (projectionMatrix: NumArr) => {
+  const inverseProjectionMatrix = inverse(projectionMatrix);
+  const ltn = positionTransform([-1, 1, -1], inverseProjectionMatrix);
+  const rbn = positionTransform([1, -1, -1], inverseProjectionMatrix);
+  const ccf = positionTransform([0, 0, 1], inverseProjectionMatrix);
+
+  const [left, top, near] = ltn;
+  const [right, bottom] = rbn;
+  const far = ccf[2];
+
+  return {
+    left,
+    top,
+    right,
+    bottom,
+    near,
+    far,
+  };
+};
+
+export const frustum = (
+  left: number,
+  right: number,
+  bottom: number,
+  top: number,
+  near: number,
+  far: number
+) => {
+  const dst = new Float32Array(16);
+
+  var dx = right - left;
+  var dy = top - bottom;
+  var dz = far - near;
+
+  dst[0] = (2 * near) / dx;
+  dst[1] = 0;
+  dst[2] = 0;
+  dst[3] = 0;
+  dst[4] = 0;
+  dst[5] = (2 * near) / dy;
+  dst[6] = 0;
+  dst[7] = 0;
+  dst[8] = (left + right) / dx;
+  dst[9] = (top + bottom) / dy;
+  dst[10] = -(far + near) / dz;
+  dst[11] = -1;
+  dst[12] = 0;
+  dst[13] = 0;
+  dst[14] = (-2 * near * far) / dz;
+  dst[15] = 0;
+
+  return dst;
+};
+
+export const makeZToWMatrix = (fudgeFactor: number) => [
+  1,
+  0,
+  0,
+  0,
+  0,
+  1,
+  0,
+  0,
+  0,
+  0,
+  1,
+  fudgeFactor,
+  0,
+  0,
+  0,
+  1,
+];
+
+export const translate = (tx: number, ty: number, tz: number) => [
+  1,
+  0,
+  0,
+  0,
+  0,
+  1,
+  0,
+  0,
+  0,
+  0,
+  1,
+  0,
+  tx,
+  ty,
+  tz,
+  1,
+];
+
+export const rotateX = (angleInRadians: number) => {
+  const s = Math.sin(angleInRadians);
+  const c = Math.cos(angleInRadians);
+
+  return [1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1];
+};
+
+export const rotateY = (angleInRadians: number) => {
+  const s = Math.sin(angleInRadians);
+  const c = Math.cos(angleInRadians);
+
+  return [c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1];
+};
+
+export const rotateZ = (angleInRadians: number) => {
+  const s = Math.sin(angleInRadians);
+  const c = Math.cos(angleInRadians);
+
+  return [c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
+};
+
+export const scale = (sx: number, sy: number, sz: number) => [
+  sx,
+  0,
+  0,
+  0,
+  0,
+  sy,
+  0,
+  0,
+  0,
+  0,
+  sz,
+  0,
+  0,
+  0,
+  0,
+  1,
+];
+
+// 正交
+export const projection = (width: number, height: number, depth: number) => [
+  2 / width,
+  0,
+  0,
+  0,
+  0,
+  -2 / height,
+  0,
+  0,
+  0,
+  0,
+  2 / depth,
+  0,
+  -1,
+  1,
+  0,
+  1,
+];
+
+// 根据三维bound转化,正交形式
+export const orthogonal = (
+  left: number,
+  right: number,
+  top: number,
+  bottom: number,
+  near: number,
+  far: number
+) => [
+  2 / (right - left),
+  0,
+  0,
+  0,
+  0,
+  2 / (bottom - top),
+  0,
+  0,
+  0,
+  0,
+  2 / (far - near),
+  0,
+  (left + right) / (left - right),
+  (top + bottom) / (top - bottom),
+  (far + near) / (near - far),
+  1,
+];
+
+export const perspective = (
+  l: number,
+  r: number,
+  t: number,
+  b: number,
+  n: number,
+  f: number
+) => [
+  (2 * n) / (r - l),
+  0,
+  0,
+  0,
+  0,
+  (2 * n) / (b - t),
+  0,
+  0,
+  (l + r) / (l - r),
+  (b + t) / (t - b),
+  2 / (f - n),
+  1,
+  0,
+  0,
+  -(f + n) / (n - f),
+  0,
+];
+
+// 正中间投影 l r 和 t b对称
+export const straightPerspective = (w: number, h: number, n: number, f: number) => {
+  return [
+    (2 * n) / w,
+    0,
+    0,
+    0,
+    0,
+    (-2 * n) / h,
+    0,
+    0,
+    0,
+    0,
+    2 / (f - n),
+    1,
+    0,
+    0,
+    -(f + n) / (n - f),
+    0,
+  ];
+};
+
+/**
+ * @param fieldOfViewInRadians 可视角度
+ * @param aspect w / h 比例
+ * @param near 近面
+ * @param far 远面
+ */
+export const straightPerspective1 = (
+  fieldOfViewInRadians: number,
+  aspect: number,
+  near: number,
+  far: number
+) => {
+  // const a = Math.atan((Math.PI  - fieldOfViewInRadians) / 2)
+
+  // return [
+  //   a/aspect, 0, 0,           0,
+  //   0,  -a, 0,           0,
+  //   0,   0, 1/(far-near),     1,
+  //   0,   0, -(near)/(far-near), 0
+  // ]
+  var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
+  var rangeInv = 1.0 / (near - far);
+
+  return [
+    f / aspect,
+    0,
+    0,
+    0,
+    0,
+    f,
+    0,
+    0,
+    0,
+    0,
+    (near + far) * rangeInv,
+    -1,
+    0,
+    0,
+    near * far * rangeInv * 2,
+    0,
+  ];
+};
+
+export const multiply = (...matrixs: NumArr[]): NumArr => {
+  if (matrixs.length === 1) {
+    return matrixs[0];
+  }
+
+  const radio = 4;
+  const count = radio * radio;
+  const result: number[] = [];
+
+  for (let i = 0; i < count; i++) {
+    const row = Math.floor(i / radio);
+    const column = i % radio;
+
+    let currentResult = 0;
+    for (let offset = 0; offset < radio; offset++) {
+      const rowIndex = row * radio + offset;
+      const columnIndex = column + offset * radio;
+      currentResult += matrixs[1][rowIndex] * matrixs[0][columnIndex];
+    }
+    result[i] = currentResult;
+  }
+
+  if (matrixs.length === 2) {
+    return result;
+  } else {
+    return multiply(result, ...matrixs.slice(2));
+  }
+};
+
+export const positionTransform = (pos: NumArr, matrix: number[]) => {
+  const radio = 4;
+  const w =
+    pos[0] * matrix[3] +
+    pos[1] * matrix[radio + 3] +
+    pos[2] * matrix[radio * 2 + 3] +
+    matrix[radio * 3 + 3];
+  return [
+    (pos[0] * matrix[0] +
+      pos[1] * matrix[radio] +
+      pos[2] * matrix[radio * 2] +
+      matrix[radio * 3]) /
+      w,
+    (pos[0] * matrix[1] +
+      pos[1] * matrix[radio + 1] +
+      pos[2] * matrix[radio * 2 + 1] +
+      matrix[radio * 3 + 1]) /
+      w,
+    (pos[0] * matrix[2] +
+      pos[1] * matrix[radio + 2] +
+      pos[2] * matrix[radio * 2 + 2] +
+      matrix[radio * 3 + 2]) /
+      w,
+  ];
+};
+
+export const addVectors = (a: NumArr, b: NumArr, v: NumArr = []) => {
+  v[0] = a[0] + b[0];
+  v[1] = a[1] + b[1];
+  v[2] = a[2] + b[2];
+  return v;
+};
+
+export const subtractVectors = (a: NumArr, b: NumArr) => [
+  a[0] - b[0],
+  a[1] - b[1],
+  a[2] - b[2],
+];
+
+export const scaleVector = (a: NumArr, b: number) => [a[0] * b, a[1] * b, a[2] * b];
+
+export const normalVector = (v: NumArr, cv: NumArr = []) => {
+  const [x, y, z] = v;
+  const len = Math.sqrt(x * x + y * y + z * z);
+  if (len > 0) {
+    cv[0] = x / len;
+    cv[1] = y / len;
+    cv[2] = z / len;
+  } else {
+    cv[0] = 0;
+    cv[1] = 0;
+    cv[2] = 0;
+  }
+  return cv;
+};
+
+// 向量叉乘,叉乘结果向量必然同事垂直两个向量
+export const cross = (a: NumArr, b: NumArr) => [
+  a[1] * b[2] - a[2] * b[1],
+  a[2] * b[0] - a[0] * b[2],
+  a[0] * b[1] - a[1] * b[0],
+];
+
+// 对准一个目标(实际上是制作一个矩阵,将target扭转到cameraPosition)
+export const lookAt = (cameraPosition: number[], target: number[], up: number[]) => {
+  // camera是正对-z轴的 所以需要反向
+  const zAxis = normalVector(subtractVectors(cameraPosition, target));
+  // 相对于zAxis做出x朝向,注意顺序不能反,因为三维有两个垂直朝向,通过叉乘通过顺序确认
+  const xAxis = normalVector(cross(up, zAxis));
+  const yAxis = normalVector(cross(zAxis, xAxis));
+
+  return [...xAxis, 0, ...yAxis, 0, ...zAxis, 0, ...cameraPosition, 1];
+};
+
+export const dot = (v1: NumArr, v2: NumArr) =>
+  v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+
+export const inverse = (m: NumArr) => {
+  var m00 = m[0 * 4 + 0];
+  var m01 = m[0 * 4 + 1];
+  var m02 = m[0 * 4 + 2];
+  var m03 = m[0 * 4 + 3];
+  var m10 = m[1 * 4 + 0];
+  var m11 = m[1 * 4 + 1];
+  var m12 = m[1 * 4 + 2];
+  var m13 = m[1 * 4 + 3];
+  var m20 = m[2 * 4 + 0];
+  var m21 = m[2 * 4 + 1];
+  var m22 = m[2 * 4 + 2];
+  var m23 = m[2 * 4 + 3];
+  var m30 = m[3 * 4 + 0];
+  var m31 = m[3 * 4 + 1];
+  var m32 = m[3 * 4 + 2];
+  var m33 = m[3 * 4 + 3];
+  var tmp_0 = m22 * m33;
+  var tmp_1 = m32 * m23;
+  var tmp_2 = m12 * m33;
+  var tmp_3 = m32 * m13;
+  var tmp_4 = m12 * m23;
+  var tmp_5 = m22 * m13;
+  var tmp_6 = m02 * m33;
+  var tmp_7 = m32 * m03;
+  var tmp_8 = m02 * m23;
+  var tmp_9 = m22 * m03;
+  var tmp_10 = m02 * m13;
+  var tmp_11 = m12 * m03;
+  var tmp_12 = m20 * m31;
+  var tmp_13 = m30 * m21;
+  var tmp_14 = m10 * m31;
+  var tmp_15 = m30 * m11;
+  var tmp_16 = m10 * m21;
+  var tmp_17 = m20 * m11;
+  var tmp_18 = m00 * m31;
+  var tmp_19 = m30 * m01;
+  var tmp_20 = m00 * m21;
+  var tmp_21 = m20 * m01;
+  var tmp_22 = m00 * m11;
+  var tmp_23 = m10 * m01;
+
+  var t0 =
+    tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31 - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
+  var t1 =
+    tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31 - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
+  var t2 =
+    tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31 - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
+  var t3 =
+    tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21 - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
+
+  var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
+
+  return [
+    d * t0,
+    d * t1,
+    d * t2,
+    d * t3,
+    d *
+      (tmp_1 * m10 +
+        tmp_2 * m20 +
+        tmp_5 * m30 -
+        (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
+    d *
+      (tmp_0 * m00 +
+        tmp_7 * m20 +
+        tmp_8 * m30 -
+        (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
+    d *
+      (tmp_3 * m00 +
+        tmp_6 * m10 +
+        tmp_11 * m30 -
+        (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
+    d *
+      (tmp_4 * m00 +
+        tmp_9 * m10 +
+        tmp_10 * m20 -
+        (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)),
+    d *
+      (tmp_12 * m13 +
+        tmp_15 * m23 +
+        tmp_16 * m33 -
+        (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
+    d *
+      (tmp_13 * m03 +
+        tmp_18 * m23 +
+        tmp_21 * m33 -
+        (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
+    d *
+      (tmp_14 * m03 +
+        tmp_19 * m13 +
+        tmp_22 * m33 -
+        (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
+    d *
+      (tmp_17 * m03 +
+        tmp_20 * m13 +
+        tmp_23 * m23 -
+        (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)),
+    d *
+      (tmp_14 * m22 +
+        tmp_17 * m32 +
+        tmp_13 * m12 -
+        (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
+    d *
+      (tmp_20 * m32 +
+        tmp_12 * m02 +
+        tmp_19 * m22 -
+        (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
+    d *
+      (tmp_18 * m12 +
+        tmp_23 * m32 +
+        tmp_15 * m02 -
+        (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
+    d *
+      (tmp_22 * m22 +
+        tmp_16 * m02 +
+        tmp_21 * m12 -
+        (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)),
+  ];
+};

+ 1 - 1
src/view/case/caseFile.vue

@@ -115,7 +115,7 @@ const refresh = async () => {
 };
 watchEffect(() => caseId.value && currentTypeId.value && refresh());
 
-const query = (file: CaseFile) => window.open(file.filesUrl);
+const query = (file: CaseFile) => window.open(file.filesUrl + "?time=" + Date.now());
 const del = async (file: CaseFile) => {
   if (await confirm("确定要删除此数据?")) {
     await delCaseFile({ caseId: caseId.value!, filesId: file.filesId });

+ 23 - 0
src/view/case/draw/board/editCAD/Controls/MoveBgImage.js

@@ -0,0 +1,23 @@
+import { floorplanService } from '../Service/FloorplanService'
+import { mathUtil } from '../MathUtil.js'
+import { coordinate } from '../Coordinate'
+import Constant from '../Constant'
+
+export default class MoveBgImage {
+    constructor() {
+
+    }
+
+    moveFullBgImage(dx,dy, bgImageId) {
+        let bgImage = floorplanService.getBgImage()
+        if(bgImage.url){
+            bgImage.center = {
+                x:bgImage.center.x + dx * Constant.defaultZoom/coordinate.zoom,
+                y:bgImage.center.y + dy * Constant.defaultZoom/coordinate.zoom,
+            }
+        }
+    }
+}
+
+const moveBgImage = new MoveBgImage()
+export { moveBgImage }

+ 21 - 0
src/view/case/draw/board/editCAD/Controls/MoveCustomImage.js

@@ -0,0 +1,21 @@
+import { floorplanService } from '../Service/FloorplanService'
+import { mathUtil } from '../MathUtil.js'
+import { coordinate } from '../Coordinate'
+import Constant from '../Constant'
+
+export default class MoveCustomImage {
+    constructor() {
+
+    }
+
+    moveFullCustomImage(dx,dy, customImageId) {
+        let customImage = floorplanService.getCustomImage(customImageId)
+        customImage.center = {
+            x:customImage.center.x + dx/coordinate.res * Constant.defaultZoom/coordinate.zoom,
+            y:customImage.center.y - dy/coordinate.res * Constant.defaultZoom/coordinate.zoom,
+        }
+    }
+}
+
+const moveCustomImage = new MoveCustomImage()
+export { moveCustomImage }

+ 531 - 288
src/view/case/draw/board/editCAD/Controls/UIControl.js

@@ -1,321 +1,564 @@
-import { coordinate } from '../Coordinate.js'
-import LayerEvents from '../enum/LayerEvents.js'
-import UIEvents from '../enum/UIEvents.js'
-import VectorType from '../enum/VectorType.js'
-import { stateService } from '../Service/StateService.js'
-import { floorplanService } from '../Service/FloorplanService.js'
-import { historyService } from '../Service/HistoryService.js'
-import { elementService } from '../Service/ElementService'
-import { mathUtil } from '../MathUtil.js'
-import { wallService } from '../Service/WallService.js'
-import { tagService } from '../Service/TagService.js'
-import { tableService } from '../Service/TableService.js'
-import Constant from '../Constant'
-import { addWall } from '../Controls/AddWall'
-import { floorplanData } from '../FloorplanData.js'
-import { signService } from '../Service/SignService.js'
-import mitt from 'mitt'
-import {history} from '../History/History.js'
-import { iconService } from '../Service/IconService.js'
+import { coordinate } from "../Coordinate.js";
+import LayerEvents from "../enum/LayerEvents.js";
+import UIEvents from "../enum/UIEvents.js";
+import VectorType from "../enum/VectorType.js";
+import { stateService } from "../Service/StateService.js";
+import { floorplanService } from "../Service/FloorplanService.js";
+import { historyService } from "../Service/HistoryService.js";
+import { elementService } from "../Service/ElementService";
+import { mathUtil } from "../MathUtil.js";
+import { wallService } from "../Service/WallService.js";
+import { tagService } from "../Service/TagService.js";
+import { tableService } from "../Service/TableService.js";
+import Constant from "../Constant";
+import { addWall } from "../Controls/AddWall";
+import { floorplanData } from "../FloorplanData.js";
+import { signService } from "../Service/SignService.js";
+import { customImageService } from "../Service/CustomImageService.js";
+import mitt from "mitt";
+import { history } from "../History/History.js";
+import { iconService } from "../Service/IconService.js";
+import { bgImageService } from "../Service/BgImageService.js";
 
-export default class UIControl{
-    constructor(layer) {
-        this.layer = layer
-        this.bus = mitt()
-        this.selectUI = null;
+export default class UIControl {
+  constructor(layer) {
+    this.layer = layer;
+    this.bus = mitt();
+    this.selectUI = null;
+    this.appendData = null;
 
-        // this.bus.emit('')
+    // this.bus.emit('')
+  }
+
+  //点击左侧栏后,更新事件
+  updateEventNameForSelectUI() {
+    elementService.hideAll();
+    //正在添加tag的时候,需要先删除
+    const eventName = stateService.getEventName();
+    // if (eventName == LayerEvents.AddTag) {
+    //     let item = stateService.getDraggingItem()
+    //     if (item && item.type == VectorType.Tag) {
+    //         floorplanService.deleteTag(item.vectorId)
+    //     }
+    // }
+    // stateService.clearItems()
+    if (this.selectUI == UIEvents.Wall) {
+      stateService.setEventName(LayerEvents.AddWall);
+    } else if (this.selectUI == UIEvents.Table) {
+      stateService.setEventName(LayerEvents.AddTable);
+    } else if (this.selectUI == UIEvents.Rectangle) {
+      stateService.setEventName(LayerEvents.AddRectangle);
+    } else if (this.selectUI == UIEvents.Circle) {
+      stateService.setEventName(LayerEvents.AddCircle);
+    } else if (this.selectUI == UIEvents.Arrow) {
+      stateService.setEventName(LayerEvents.AddArrow);
+    } else if (this.selectUI == UIEvents.Icon) {
+      stateService.setEventName(LayerEvents.AddIcon);
+    } else if (this.selectUI == UIEvents.Tag) {
+      stateService.setEventName(LayerEvents.AddTag);
+    } else if (
+      this.selectUI == UIEvents.Cigaret ||
+      this.selectUI == UIEvents.FirePoint ||
+      this.selectUI == UIEvents.LeftFootPrint ||
+      this.selectUI == UIEvents.RightFootPrint ||
+      this.selectUI == UIEvents.LeftShoePrint ||
+      this.selectUI == UIEvents.RightShoePrint ||
+      this.selectUI == UIEvents.FingerPrint ||
+      this.selectUI == UIEvents.DeadBody ||
+      this.selectUI == UIEvents.BloodStain
+    ) {
+      stateService.setEventName(LayerEvents.AddSign);
     }
+  }
 
-    //点击左侧栏后,更新事件
-    updateEventNameForSelectUI() {
-        elementService.hideAll()
-        //正在添加tag的时候,需要先删除
-        const eventName = stateService.getEventName()
-        // if (eventName == LayerEvents.AddTag) {
-        //     let item = stateService.getDraggingItem()
-        //     if (item && item.type == VectorType.Tag) {
-        //         floorplanService.deleteTag(item.vectorId)
-        //     }
-        // }
-        // stateService.clearItems()
-        if (this.selectUI == UIEvents.Wall) 
-        {
-            stateService.setEventName(LayerEvents.AddWall)
-        } 
-        else if (this.selectUI == UIEvents.Table ) 
-        {
-            stateService.setEventName(LayerEvents.AddTable)
-        } 
-        else if (this.selectUI == UIEvents.Rectangle ) 
-        {
-            stateService.setEventName(LayerEvents.AddRectangle)
-        } 
-        else if (this.selectUI == UIEvents.Circle ) 
-        {
-            stateService.setEventName(LayerEvents.AddCircle)
-        } 
-        else if (this.selectUI == UIEvents.Arrow ) 
-        {
-            stateService.setEventName(LayerEvents.AddArrow)
+  /**
+   * @param {*} type 部件类型
+   * @param {*} name 属性名称
+   * @param {*} value 属性值
+   */
+  async setAttributes(type, name, value) {
+    let item = stateService.getFocusItem();
+    let flag = true;
+    switch (name) {
+      case "delete":
+        this.deleteItem();
+        break;
+      case 'update':
+        if(type == VectorType.Tag){
+            const tag = floorplanService.getTag(item.vectorId)
+            if(value.hasOwnProperty('version')){
+              value.color&&tag.setColor(value.color)
+              value.fontSize&&tag.setFontSize(value.fontSize)
+              value.text&&tag.setValue(value.text)
+            }
+            else{
+                tag.setValue(value)
+            }
         }
-        else if (this.selectUI == UIEvents.Icon ) 
-        {
-            stateService.setEventName(LayerEvents.AddIcon)
-        }  
-        else if (this.selectUI == UIEvents.Tag) 
-        {
-            stateService.setEventName(LayerEvents.AddTag)
-        } 
-        else if (
-            this.selectUI == UIEvents.Cigaret ||
-            this.selectUI == UIEvents.FirePoint ||
-            this.selectUI == UIEvents.LeftFootPrint ||
-            this.selectUI == UIEvents.RightFootPrint ||
-            this.selectUI == UIEvents.LeftShoePrint ||
-            this.selectUI == UIEvents.RightShoePrint ||
-            this.selectUI == UIEvents.FingerPrint ||
-            this.selectUI == UIEvents.DeadBody ||
-            this.selectUI == UIEvents.BloodStain 
-        ) {
-            stateService.setEventName(LayerEvents.AddSign)
+        else if(type == VectorType.Arrow){
+            const arrow = floorplanService.getArrow(item.vectorId)
+            if(value.hasOwnProperty('version')){
+                arrow.setColor(value.color)
+            }
         }
-    }
-
-    /**
-     * @param {*} type 部件类型
-     * @param {*} name 属性名称
-     * @param {*} value 属性值
-     */
-    async setAttributes(type, name, value) {
-        let item = stateService.getFocusItem()
-        switch (name) {
-            case 'delete':
-                this.deleteItem()
-                break;
-            case 'update':
-                if(type == VectorType.Tag){
-                    const tag = floorplanService.getTag(item.vectorId)
-                    tag.setValue(value)
-                }
-                else if(type == VectorType.Table){
-                    const table = floorplanService.getTable(item.vectorId)
-                    table.setValue(value)
-                }
-                else if(type == VectorType.Title){
-                    floorplanService.updateTitle(value);
-                }
-                else if(type == VectorType.BgImage){
-                    await floorplanService.updateBgImage(value);
-                }
-                else if(type == VectorType.Compass){
-                    floorplanService.updateCompass(value);
-                }
-                break;
+        else if(type == VectorType.Wall){
+            const wall = floorplanService.getWall(item.vectorId)
+            if(value.hasOwnProperty('version')){
+                wall.setColor(value.color)
+            }
         }
-        history.save()
-        stateService.clearFocusItem();
-        this.bus.emit('hideAttribute')
-        this.bus.emit('hideUI')
-        this.layer.renderer.autoRedraw()
-    }
+        else if(type == VectorType.Rectangle){
+            const rectangle = floorplanService.getRectangle(item.vectorId)
+            if(value.hasOwnProperty('version')){
+                rectangle.setColor(value.color)
+            }
+        }
+        else if(type == VectorType.Circle){
+            const circle = floorplanService.getCircle(item.vectorId)
+            if(value.hasOwnProperty('version')){
+                circle.setColor(value.color)
+            }
+        }
+        else if(type == VectorType.Table){
+            const table = floorplanService.getTable(item.vectorId)
+            if(value.hasOwnProperty('version')){
+                table.setValue(value.content)
+            }
+            else{
+                table.setValue(value)
+            }
+        }
+        else if(type == VectorType.Title){
+            if(value.hasOwnProperty('version')){
+                floorplanService.updateTitle(value.text);
+            }
+            else{
+                floorplanService.updateTitle(value);
+            }
+        }
+        else if(type == VectorType.Compass){
+          if(value.hasOwnProperty('version')){
+            floorplanService.updateCompass(value.rotate)
+            flag = value.save
+          }
+          else{
+            floorplanService.updateCompass(value)
+          }
+        }
+        else if(type == VectorType.CustomImage){
+          const customImage = floorplanService.getCustomImage(item.vectorId)
+          if(value.hasOwnProperty('rotate')){
+            customImage.setAngle(value.rotate)
+            flag = value.save
+          }
+          else if(value.hasOwnProperty('scale')){
+            customImage.setScale(value.scale)
+            flag = value.save
+          }
+          else if(value.hasOwnProperty('ratio')){
+            customImage.setRatio(floor.customImages[key].ratio)
+          }
+        }
+        else if(type == VectorType.BgImage){
+          const bgImage = floorplanService.getBgImage()
+          if(value.hasOwnProperty('scale')){
+            bgImage.setScale(value.scale)
+            flag = value.save
+          }
+        }
+        else if(signService.isSign(type)){
+          const sign = floorplanService.getSign(item.vectorId)
+          if(value.hasOwnProperty('rotate')){
+            sign.setAngle(value.rotate)
+            flag = value.save
+          }
+          else if(value.hasOwnProperty('scale')){
+            sign.setScale(value.scale)
+            flag = value.save
+          }
+        }
+        break;
+      case "upload":
+        if(type == VectorType.CustomImage){
+          const customImage = await customImageService.createCustomImage(value.url,{
+            x:0,
+            y:0
+          })
 
-    showAttributes(item) {
-        let type = item.type;
-        let value = null;
-        switch (item.type) {
-            case VectorType.Tag:
-                const tag = floorplanService.getTag(item.vectorId)
-                if(!tag){
-                    return;
-                }
-                value = tag.value;
-                break;
-            case VectorType.Table:
-                const table = floorplanService.getTable(item.vectorId)
-                if(!table){
-                    return;
-                }
-                const cellIds = table.cells;
-                value = [];
-                for(let i=0;i<cellIds.length;++i){
-                    for(let j=0;j<cellIds[i].length;++j){
-                        const cell = floorplanService.getCell(cellIds[i][j])
-                        value.push({
-                            width:cell.width,
-                            height:cell.height,
-                            value:cell.value,
-                            colIndex:cell.colIndex,
-                            rowIndex:cell.rowIndex
-                        })
-                    }
-                }
-                break;
-            case VectorType.Title:
-                const title = floorplanService.getTitle()
-                if(!title){
-                    return;
-                }
-                value = title.value;
-                break;
-            case VectorType.Compass:
-                const compass = floorplanService.getCompass()
-                if(!compass){
-                    return;
-                }
-                value = compass.angle;
-                break;
+          //stateService.setEventName(LayerEvents.MoveCustomImage);
+          let focusItem = {
+            vectorId: customImage.vectorId,
+            type: VectorType.CustomImage,
+          };
+          stateService.setFocusItem(focusItem);
+          this.showAttributes(focusItem);
+        }  
+        else if(type == VectorType.BgImage){
+          const bgImage = await bgImageService.createBgImage(value.url)
+          //stateService.setEventName(LayerEvents.MoveBgImage);
+          let focusItem = {
+            vectorId: bgImage.vectorId,
+            type: VectorType.BgImage,
+          };
+          stateService.setFocusItem(focusItem);
+          this.showAttributes(focusItem);
         }
-        this.bus.emit('showAttribute',{
-            type:type,
-            value:value
-        })
+        break;
     }
-
-    clearUI() {
-        this.selectUI = null
-        this.bus.emit('hideAttribute')
-        this.bus.emit('hideUI')
+    if(flag){
+      history.save();
     }
+    //stateService.clearFocusItem();
+    this.layer.renderer.autoRedraw();
+  }
 
-    deleteItem() {
-        let item = stateService.getFocusItem()
-        if (item) {
-            if (item.type == VectorType.Wall) {
-                floorplanService.deleteWall(item.vectorId)
-            } else if (item.type == VectorType.Rectangle) {
-                floorplanService.deleteRectangle(item.vectorId)
-            } else if (item.type == VectorType.Circle) {
-                floorplanService.deleteCircle(item.vectorId)
-            } else if (item.type == VectorType.Arrow) {
-                floorplanService.deleteArrow(item.vectorId)
-            } else if (item.type == VectorType.Icon) {
-                iconService.deleteIcon(item.vectorId)
-            }  else if (item.type == VectorType.Tag) {
-                floorplanService.deleteTag(item.vectorId)
-            } else if (item.type == VectorType.Table) {
-                floorplanService.deleteTable(item.vectorId)
-            } else if (signService.isSign(item.type)) {
-                floorplanService.deleteSign(item.vectorId)
-            } else if (item.type == VectorType.WallCorner) {
-                wallService.deleteWallCorner(item.vectorId)
+  showAttributes(item) {
+    let type = item.type;
+    let value = null;
+
+    if(signService.isSign(type)){
+      const sign = floorplanService.getSign(item.vectorId);
+      if (!sign) {
+        return;
+      }
+      value = {
+          version:'2.0',
+          type: type,
+          rotate:sign.angle,
+          scale:sign.scale
+      };
+    }
+    else{
+      switch (item.type) {
+        case VectorType.Tag:
+          const tag = floorplanService.getTag(item.vectorId);
+          if (!tag) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              text:tag.value,
+              color: tag.color,
+              fontSize: tag.fontSize,
+          };
+          break;
+        case VectorType.Table:
+          const table = floorplanService.getTable(item.vectorId);
+          if (!table) {
+            return;
+          }
+          const cellIds = table.cells;
+          let content = [];
+          for (let i = 0; i < cellIds.length; ++i) {
+            for (let j = 0; j < cellIds[i].length; ++j) {
+              const cell = floorplanService.getCell(cellIds[i][j]);
+              content.push({
+                width: cell.width,
+                height: cell.height,
+                value: cell.value,
+                colIndex: cell.colIndex,
+                rowIndex: cell.rowIndex,
+              });
             }
-            history.save()
-            this.layer.renderer.autoRedraw()
-        }
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              content: content,
+          };
+          break;
+        case VectorType.Title:
+          const title = floorplanService.getTitle();
+          if (!title) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              text: title.value,
+          };
+          break;
+        case VectorType.Compass:
+          const compass = floorplanService.getCompass();
+          if (!compass) {
+            return;
+          }
+          //value = compass.angle;
+          value = {
+            version:'2.0',
+            type: type,
+            rotate:compass.angle
+          };
+          break;
+        case VectorType.CustomImage:
+          const customImage = floorplanService.getCustomImage(item.vectorId);
+          if (!customImage) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              url: customImage.url,
+              rotate:customImage.angle,
+              ratio:customImage.ratio,
+              scale:customImage.scale
+          };
+          break;
+        case VectorType.BgImage:
+          const bgImage = floorplanService.getBgImage(item.vectorId);
+          if (!bgImage) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              url: bgImage.url,
+              scale:bgImage.scale
+          };
+          break;
+        case VectorType.Circle:
+          const circle = floorplanService.getCircle(item.vectorId);
+          if (!circle) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              color: circle.color,
+          };
+          break;
+        case VectorType.Rectangle:
+          const rectangle = floorplanService.getRectangle(item.vectorId);
+          if (!rectangle) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              color: rectangle.color,
+          };
+          break;
+        case VectorType.Wall:
+          const wall = floorplanService.getWall(item.vectorId);
+          if (!wall) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              color: wall.color,
+          };
+          break;
+        case VectorType.Arrow:
+          const arrow = floorplanService.getArrow(item.vectorId);
+          if (!arrow) {
+            return;
+          }
+          value = {
+              version:'2.0',
+              type: type,
+              color: arrow.color,
+          };
+          break;
+      }
     }
+    
+    this.bus.emit("showAttribute", {
+      type: type,
+      value: value,
+    });
+  }
 
-    getSignTypeForUI() {
-        if (this.selectUI == UIEvents.Cigaret) {
-            return VectorType.Cigaret
-        } else if (this.selectUI == UIEvents.FirePoint) {
-            return VectorType.FirePoint
-        } else if (this.selectUI == UIEvents.LeftFootPrint) {
-            return VectorType.LeftFootPrint
-        } else if (this.selectUI == UIEvents.RightFootPrint) {
-            return VectorType.RightFootPrint
-        } else if (this.selectUI == UIEvents.LeftShoePrint) {
-            return VectorType.LeftShoePrint
-        } else if (this.selectUI == UIEvents.RightShoePrint) {
-            return VectorType.RightShoePrint
-        } else if (this.selectUI == UIEvents.FingerPrint) {
-            return VectorType.FingerPrint
-        } else if (this.selectUI == UIEvents.DeadBody) {
-            return VectorType.DeadBody
-        } else if (this.selectUI == UIEvents.BloodStain) {
-            return VectorType.BloodStain
-        }
+  clearUI() {
+    this.selectUI = null;
+    this.bus.emit("hideAttribute");
+    this.bus.emit("hideUI");
+  }
+
+  deleteItem() {
+    let item = stateService.getFocusItem();
+    if (item) {
+      if (item.type == VectorType.Wall) {
+        floorplanService.deleteWall(item.vectorId);
+      } else if (item.type == VectorType.Rectangle) {
+        floorplanService.deleteRectangle(item.vectorId);
+      } else if (item.type == VectorType.Circle) {
+        floorplanService.deleteCircle(item.vectorId);
+      } else if (item.type == VectorType.Arrow) {
+        floorplanService.deleteArrow(item.vectorId);
+      } else if (item.type == VectorType.Icon) {
+        iconService.deleteIcon(item.vectorId);
+      } else if (item.type == VectorType.Tag) {
+        floorplanService.deleteTag(item.vectorId);
+      } else if (item.type == VectorType.Table) {
+        floorplanService.deleteTable(item.vectorId);
+      } else if (signService.isSign(item.type)) {
+        floorplanService.deleteSign(item.vectorId);
+      } else if (item.type == VectorType.WallCorner) {
+        wallService.deleteWallCorner(item.vectorId);
+      } else if (item.type == VectorType.CustomImage) {
+        floorplanService.deleteCustomImage(item.vectorId);
+      } else if (item.type == VectorType.BgImage) {
+        floorplanService.deleteBgImage();
+      }
+      history.save();
+      this.layer.renderer.autoRedraw();
     }
+  }
 
-    exportJSON() {
-        const json = {
-            version: floorplanData.version,
-            floors: floorplanData.floors,
-            currentId: floorplanService.getCurrentId(),
-        }
-        return json
+  getSignTypeForUI() {
+    if (this.selectUI == UIEvents.Cigaret) {
+      return VectorType.Cigaret;
+    } else if (this.selectUI == UIEvents.FirePoint) {
+      return VectorType.FirePoint;
+    } else if (this.selectUI == UIEvents.LeftFootPrint) {
+      return VectorType.LeftFootPrint;
+    } else if (this.selectUI == UIEvents.RightFootPrint) {
+      return VectorType.RightFootPrint;
+    } else if (this.selectUI == UIEvents.LeftShoePrint) {
+      return VectorType.LeftShoePrint;
+    } else if (this.selectUI == UIEvents.RightShoePrint) {
+      return VectorType.RightShoePrint;
+    } else if (this.selectUI == UIEvents.FingerPrint) {
+      return VectorType.FingerPrint;
+    } else if (this.selectUI == UIEvents.DeadBody) {
+      return VectorType.DeadBody;
+    } else if (this.selectUI == UIEvents.BloodStain) {
+      return VectorType.BloodStain;
     }
+  }
 
-    downloadCadImg(canvas, filename) {
-        // 图片导出为 png 格式
-        var type = 'png'
-        var imgData = canvas.toDataURL(type, 1)
+  exportJSON() {
+    const json = {
+      version: floorplanData.version,
+      floors: floorplanData.floors,
+      currentId: floorplanService.getCurrentId(),
+    };
+    return json;
+  }
 
-        let blobImg = this.base64ToBlob(imgData)
-        return blobImg
+  exportImg(canvas,filename, callback){
+    coordinate.setRatio(3)
+    canvas.width = canvas.width * coordinate.ratio
+    canvas.height = canvas.height * coordinate.ratio
+    stateService.clearItems();
+    this.layer.renderer.autoRedrawForImg()
+    setTimeout(() => {
+      // let blobImg = this.downloadCadImg(canvas, filename)
+      // // 完成callback传出blob  
+      // callback(blobImg)
+      canvas.toBlob(callback, 'image/jpeg', 1)
+      
+      canvas.width = canvas.width / coordinate.ratio
+      canvas.height = canvas.height / coordinate.ratio
+      coordinate.setRatio(1)
+      this.layer.renderer.autoRedraw()
+    },100)
 
-        // 加工image data,替换mime type
-        //imgData = imgData.replace(this._fixType(type), 'image/octet-stream')
+  }
 
-        // download
-        //this.saveFile(imgData, filename)
-    }
+  downloadCadImg(canvas, filename) {
+    
+    // 图片导出为 png 格式
+    var type = "image/png";
+    var imgData = canvas.toDataURL(type, 1);
 
-    saveFile(data, filename) {
-        var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a')
-        save_link.href = data
-        save_link.download = filename
+    //let blobImg = this.base64ToBlob(imgData);
+    
 
-        var event = document.createEvent('MouseEvents')
-        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
-        save_link.dispatchEvent(event)
-    }
+    // 加工image data,替换mime type
+    // let blobImg  = imgData.replace(this._fixType(type), 'image/octet-stream')
+    // console.log(imgData)
+    return imgData;
+    // download
+    //this.saveFile(imgData, filename)
+  }
 
-    _fixType(type) {
-        type = type.toLowerCase().replace(/jpg/i, 'jpeg')
-        var r = type.match(/png|jpeg|bmp|gif/)[0]
-        return 'image/' + r
-    }
+  saveFile(data, filename) {
+    var save_link = document.createElementNS(
+      "http://www.w3.org/1999/xhtml",
+      "a"
+    );
+    save_link.href = data;
+    save_link.download = filename;
 
-    base64ToBlob(base64) {
-        let arr = base64.split(','),
-            mime = arr[0].match(/:(.*?);/)[1],
-            bstr = atob(arr[1]),
-            n = bstr.length,
-            u8arr = new Uint8Array(n)
-        while (n--) {
-            u8arr[n] = bstr.charCodeAt(n)
-        }
-        return new Blob([u8arr], { type: mime })
-    }
+    var event = document.createEvent("MouseEvents");
+    event.initMouseEvent(
+      "click",
+      true,
+      false,
+      window,
+      0,
+      0,
+      0,
+      0,
+      0,
+      false,
+      false,
+      false,
+      false,
+      0,
+      null
+    );
+    save_link.dispatchEvent(event);
+  }
 
-    //截图
-    menu_screenShot(fileName) {
-        // this.menu_flex();
-        // this.layer.stopAddVector()
-        // setTimeout(function(){
-        //     this.downloadCadImg(this.layer.canvas,fileName)
-        // }.bind(this),100)
+  _fixType(type) {
+    type = type.toLowerCase().replace(/jpg/i, "jpeg");
+    var r = type.match(/png|jpeg|bmp|gif/)[0];
+    return "image/" + r;
+  }
 
-        this.layer.stopAddVector()
-        return this.downloadCadImg(this.layer.canvas,fileName)
+  base64ToBlob(base64) {
+    let arr = base64.split(","),
+      mime = arr[0].match(/:(.*?);/)[1],
+      bstr = atob(arr[1]),
+      n = bstr.length,
+      u8arr = new Uint8Array(n);
+    while (n--) {
+      u8arr[n] = bstr.charCodeAt(n);
     }
+    return new Blob([u8arr], { type: mime });
+  }
 
-    menu_flex() {
-        coordinate.reSet()
-        this.layer.renderer.autoRedraw()
-    }
+  //截图
+  menu_screenShot(fileName) {
+    // this.menu_flex();
+    // this.layer.stopAddVector()
+    // setTimeout(function(){
+    //     this.downloadCadImg(this.layer.canvas,fileName)
+    // }.bind(this),100)
 
-    initTopTable(value){
-        let center = {
-            x:770,
-            y:200
-        }
-        center = coordinate.getXYFromScreen(center)
-        let table = tableService.createTable(center)
-        table.setValue(value)
-        this.layer.renderer.autoRedraw()
-    }
+    this.layer.stopAddVector();
+    return this.downloadCadImg(this.layer.canvas, fileName);
+  }
 
-    initDownTable(value){
-        let center = {
-            x:770,
-            y:520
-        }
-        center = coordinate.getXYFromScreen(center)
-        let table = tableService.createTable(center)
-        table.setValue(value)
-        this.layer.renderer.autoRedraw()
-    }
-    /******************************************************************************************************************************************************************/
+  menu_flex() {
+    coordinate.reSet();
+    this.layer.renderer.autoRedraw();
+  }
+
+  initTopTable(value) {
+    let center = {
+      x: 770,
+      y: 250,
+    };
+    center = coordinate.getXYFromScreen(center);
+    let table = tableService.createTable(center);
+    table.setValue(value);
+    this.layer.renderer.autoRedraw();
+  }
+
+  initDownTable(value) {
+    let center = {
+      x: 770,
+      y: 520,
+    };
+    center = coordinate.getXYFromScreen(center);
+    let table = tableService.createTable(center);
+    table.setValue(value);
+    this.layer.renderer.autoRedraw();
+  }
+  /******************************************************************************************************************************************************************/
 }

+ 4 - 0
src/view/case/draw/board/editCAD/Coordinate.js

@@ -40,6 +40,10 @@ export default class Coordinate {
         }
     }
 
+    setRatio(ratio) {
+        this.ratio = ratio;
+    }
+
     // 世界坐标转换成屏幕坐标
     getScreenXY(point) {
         if (this.width == null || this.height == null) {

+ 2 - 5
src/view/case/draw/board/editCAD/FloorplanData.js

@@ -20,12 +20,9 @@ export default class FloorplanData {
         this.floors[floorNum].arrows = {}
         this.floors[floorNum].icons = {}
         this.floors[floorNum].signs = {}
-
-        // this.floors[floorNum].title = new Title();
-        // this.floors[floorNum].image = new BgImage();
-        // this.floors[floorNum].compass = new Compass();
+        this.floors[floorNum].customImages = {}
         this.floors[floorNum].title = null;
-        this.floors[floorNum].image = null;
+        this.floors[floorNum].bgImage = null;
         this.floors[floorNum].compass = null;
     }
 }

+ 5 - 0
src/view/case/draw/board/editCAD/Geometry/Arrow.js

@@ -10,6 +10,7 @@ export default class Arrow extends Geometry {
         this.startPoint = startPoint
         this.endPoint = endPoint
         this.floor = floor?floor:0
+        this.color = 'rgba(	0,0,0,1)';
         this.geoType = VectorType.Arrow
         this.setId(vectorId)
     }
@@ -42,4 +43,8 @@ export default class Arrow extends Geometry {
             mathUtil.clonePoint(this.endPoint,newPosition)
         }
     }
+
+    setColor(color) {
+        this.color = color
+    }
 }

+ 60 - 6
src/view/case/draw/board/editCAD/Geometry/BgImage.js

@@ -1,13 +1,27 @@
 import VectorType from '../enum/VectorType.js'
 import Geometry from './Geometry'
+import { mathUtil } from '../MathUtil.js'
+import SelectState from '../enum/SelectState.js'
+import { coordinate } from '../Coordinate.js'
 
 export default class BgImage extends Geometry {
-    constructor(src,vectorId, floor) {
+    constructor(url,center,vectorId) {
         super()
-        this.src = src;
-        //this.src = src;
+        this.url = url;
+        if(center){
+            this.center = center    //左上角
+        }else{
+            this.center = {
+                x:80,
+                y:150
+            }
+        }
+        
         this.image = null;
-        this.floor = floor?floor:0
+        this.width = 540;
+        this.height = 390;
+        this.scale = 1 //缩放比例
+        
         this.geoType = VectorType.BgImage
         this.setId(vectorId)
     }
@@ -16,7 +30,47 @@ export default class BgImage extends Geometry {
         this.image = imgData;
     }
 
-    setSrc(src){
-        this.src = src;
+    setUrl(url){
+        this.url = url;
+    }
+
+    setScale(scale){
+        this.scale = scale;
+    }
+
+    isContain(position) {
+
+        let p0 = {
+            x:this.center.x,
+            y:this.center.y
+        }
+
+        let p1 = {
+            x:this.center.x,
+            y:this.center.y + this.height*this.scale
+        }
+
+        let p2 = {
+            x:this.center.x + this.width*this.scale,
+            y:this.center.y + this.height*this.scale
+        }
+
+        let p3 = {
+            x:this.center.x + this.width*this.scale,
+            y:this.center.y
+        }
+        
+        position = coordinate.getScreenXY(position);
+
+        this.points = [];
+        this.points.push(p0)
+        this.points.push(p1)
+        this.points.push(p2)
+        this.points.push(p3)
+        if(mathUtil.isPointInPoly(position, this.points)){
+            return SelectState.Select
+        }else {
+            return null
+        }
     }
 }

+ 5 - 0
src/view/case/draw/board/editCAD/Geometry/Circle.js

@@ -11,6 +11,7 @@ export default class Circle extends Geometry {
         this.radius = radius
         this.points = [];                       //顺时针
         this.setPoints()
+        this.color = 'rgba(0,0,0,1)';
         this.floor = floor?floor:0
         this.geoType = VectorType.Circle
         this.setId(vectorId)
@@ -137,4 +138,8 @@ export default class Circle extends Geometry {
         }
         return lastIndex;
     }
+
+    setColor(color) {
+        this.color = color
+    }
 }

+ 13 - 6
src/view/case/draw/board/editCAD/Geometry/Compass.js

@@ -11,8 +11,8 @@ export default class Compass extends Geometry {
 
         //固定位置
         this.center = {
-            x:800,
-            y:70
+            x:880,
+            y:120
         }
 
         this.radius = 52   //svg的大小
@@ -24,13 +24,20 @@ export default class Compass extends Geometry {
         this.angle = angle
     }
 
+    //center是左上角,宽是56,高是36
     isContain(position) {
         const point = coordinate.getScreenXY(position)
-        const dis = mathUtil.getDistance(this.center,point)
-        if(dis < this.radius){
+        // const dis = mathUtil.getDistance(this.center,point)
+        // if(dis < this.radius){
+        //     return true
+        // }
+        // else{
+        //     return false;
+        // }
+        //console.log('xy:'+(point.x - this.center.x)+','+(point.y - this.center.y))
+        if(point.x - this.center.x > -5 && point.x - this.center.x < 40 && point.y - this.center.y > -5 && point.y - this.center.y < 60){
             return true
-        }
-        else{
+        }else{
             return false;
         }
     }

+ 102 - 0
src/view/case/draw/board/editCAD/Geometry/CustomImage.js

@@ -0,0 +1,102 @@
+import VectorType from '../enum/VectorType.js'
+import Geometry from './Geometry'
+import { mathUtil } from '../MathUtil.js'
+import SelectState from '../enum/SelectState.js'
+import { coordinate } from '../Coordinate'
+
+export default class CustomImage extends Geometry {
+    constructor(url,center,vectorId) {
+        super()
+        this.center = center   //实际上是左下角
+        this.url = url;
+        this.image = null;
+        this.width = 40;
+        this.height = 30;
+        this.angle = 0 //逆时针为负,顺时针为正。单位是:°
+        this.scale = 1 //缩放比例
+        this.ratio = 1;
+        this.points = [];
+        this.geoType = VectorType.CustomImage
+        this.setId(vectorId)
+    }
+
+    isContain(position) {
+        let p0 = {
+            x:this.center.x - this.ratio*this.width/coordinate.res*this.scale,
+            y:this.center.y + this.ratio*this.height/coordinate.res*this.scale
+        }
+
+        let p1 = {
+            x:this.center.x,
+            y:this.center.y + this.ratio*this.height/coordinate.res*this.scale
+        }
+
+        let p2 = {
+            x:this.center.x,
+            y:this.center.y
+        }
+
+        let p3 = {
+            x:this.center.x- this.ratio*this.width/coordinate.res*this.scale,
+            y:this.center.y
+        }
+        let center = {
+            x:this.center.x - this.ratio*this.width/2/coordinate.res*this.scale,
+            y:this.center.y + this.ratio*this.height/2/coordinate.res*this.scale
+        }
+
+        p0 = this.rotatePoint(p0, center, this.angle)
+        p1 = this.rotatePoint(p1, center, this.angle)
+        p2 = this.rotatePoint(p2, center, this.angle)
+        p3 = this.rotatePoint(p3, center, this.angle)
+
+        //let points = [];
+        this.points = [];
+        this.points.push(p0)
+        this.points.push(p3)
+        this.points.push(p2)
+        this.points.push(p1)
+        if(mathUtil.isPointInPoly(position, this.points)){
+            return SelectState.Select
+        }else {
+            return null
+        }
+    }
+
+    // ptSrc: 圆上某点(初始点);
+    // ptRotationCenter: 圆心点;
+    // angle: 旋转角度°  -- [angle * M_PI / 180]:将角度换算为弧度
+    // 【注意】angle 逆时针为正,顺时针为负
+    rotatePoint(ptSrc, ptRotationCenter, angle) {
+        angle = -1 * angle //设计是逆时针为负,顺时针为正
+        var a = ptRotationCenter.x
+        var b = ptRotationCenter.y
+        var x0 = ptSrc.x
+        var y0 = ptSrc.y
+        var rx = a + (x0 - a) * Math.cos((angle * Math.PI) / 180) - (y0 - b) * Math.sin((angle * Math.PI) / 180)
+        var ry = b + (x0 - a) * Math.sin((angle * Math.PI) / 180) + (y0 - b) * Math.cos((angle * Math.PI) / 180)
+        var json = { x: rx, y: ry }
+        return json
+    }
+
+    setImageData(imgData){
+        this.image = imgData;
+    }
+
+    setUrl(url){
+        this.url = url;
+    }
+
+    setAngle(angle){
+        this.angle = angle;
+    }
+
+    setScale(scale){
+        this.scale = scale;
+    }
+
+    setRatio(ratio){
+        this.ratio = ratio;
+    }
+    
+}

+ 1 - 0
src/view/case/draw/board/editCAD/Geometry/Geometry.js

@@ -4,6 +4,7 @@ import { mathUtil } from '../MathUtil.js'
 export default class Geometry {
     constructor() {
         this.len = null
+        this.version = 2;
     }
 
     setId(vectorId) {

+ 5 - 0
src/view/case/draw/board/editCAD/Geometry/Rectangle.js

@@ -11,6 +11,7 @@ export default class Rectangle extends Geometry {
         this.floor = floor?floor:0
         this.angle = 0;
         this.setPoints(leftTopPosition,rightDownPosition)
+        this.color = 'rgba(0,0,0,1)';
         this.geoType = VectorType.Rectangle
         this.setId(vectorId)
     }
@@ -147,4 +148,8 @@ export default class Rectangle extends Geometry {
         }
         return null;
     }
+
+    setColor(color) {
+        this.color = color
+    }
 }

+ 8 - 0
src/view/case/draw/board/editCAD/Geometry/Sign.js

@@ -46,4 +46,12 @@ export default class Sign extends Geometry {
         // }
         return 0.2;
     }
+
+    setAngle(angle){
+        this.angle = angle;
+    }
+
+    setScale(scale){
+        this.scale = scale;
+    }
 }

+ 3 - 2
src/view/case/draw/board/editCAD/Geometry/Table.js

@@ -96,6 +96,7 @@ export default class Table extends Geometry {
     }
 
     setValue(value) {
+        this.cells = []; 
         let maxCol = 0;
         let maxRow = 0;
         for(let i=0;i<value.length;++i){
@@ -192,11 +193,11 @@ export default class Table extends Geometry {
         const cell = floorplanService.getCell(vectorId)
         for(let i=0;i<cell.rowIndex;++i){
             const _cell = floorplanService.getCell(this.cells[i][0])
-            height += _cell.height;
+            height += _cell.height*coordinate.ratio;
         }
         for(let i=0;i<cell.colIndex;++i){
             const _cell = floorplanService.getCell(this.cells[0][i])
-            width += _cell.width;
+            width += _cell.width*coordinate.ratio;
         }
 
         return {

+ 31 - 0
src/view/case/draw/board/editCAD/Geometry/Tag.js

@@ -15,6 +15,9 @@ export default class Tag extends Geometry {
         this.sideWidth = 30 //像素
         this.sideThickness = 30 //像素
 
+        this.color = 'rgba(0,0,0,1)';
+        this.fontSize = 12;
+
         this.geoType = VectorType.Tag
         this.setId(vectorId)
     }
@@ -28,6 +31,26 @@ export default class Tag extends Geometry {
         return mathUtil.isPointInPoly(position, points)
     }
 
+    setFontLenAndHeight(){
+        let height = 1;
+        let row = 0;
+        let textValues = [];
+        textValues[0] = '';
+        for(let i=0;i<this.value.length;++i){
+            if(this.value[i] == '\n'){
+                ++height;
+                ++row;
+                textValues[row] = '';
+            }else{
+                textValues[row] += this.value[i]
+            }
+        }
+        return {
+            textValues:textValues,
+            height:height
+        }
+    }
+
     setPoints2d() {
         this.points2d = []
         const minX = this.center.x - ((this.sideWidth / coordinate.res) * Constant.defaultZoom) / coordinate.zoom / 2
@@ -89,4 +112,12 @@ export default class Tag extends Geometry {
     setValue(value) {
         this.value = value
     }
+
+    setColor(color) {
+        this.color = color
+    }
+
+    setFontSize(fontSize) {
+        this.fontSize = fontSize
+    }
 }

+ 1 - 1
src/view/case/draw/board/editCAD/Geometry/Title.js

@@ -8,7 +8,7 @@ export default class Title extends Geometry {
     constructor(value,vectorId, floor) {
         super()
         this.value = value?value:defaultValue;
-        this.height = 50     //里顶部距离50像素
+        this.height = 100     //里顶部距离50像素
         this.floor = floor?floor:0
         this.geoType = VectorType.Title
         this.setId(vectorId)

+ 5 - 0
src/view/case/draw/board/editCAD/Geometry/Wall.js

@@ -7,6 +7,7 @@ export default class Wall extends Geometry {
         this.start = pointId1
         this.end = pointId2
         this.floor = floor?floor:0
+        this.color = 'rgba(0,0,0,1)';
         this.geoType = VectorType.Wall
         this.setId(vectorId)
     }
@@ -28,4 +29,8 @@ export default class Wall extends Geometry {
             return null
         }
     }
+
+    setColor(color) {
+        this.color = color
+    }
 }

+ 83 - 41
src/view/case/draw/board/editCAD/History/Change.js

@@ -50,12 +50,20 @@ export default class Change {
     this.lastData.title = JSON.parse(
       JSON.stringify(floorplanService.getTitle())
     );
-    this.lastData.image = JSON.parse(
-      JSON.stringify(floorplanService.getBgImage())
-    );
+    
+    let bgImage = floorplanService.getBgImage()
+    if(bgImage){
+      this.lastData.bgImage = JSON.parse(
+        JSON.stringify(bgImage)
+      );
+    }
+
     this.lastData.compass = JSON.parse(
       JSON.stringify(floorplanService.getCompass())
     );
+    this.lastData.customImages = JSON.parse(
+      JSON.stringify(floorplanService.getCustomImages())
+    );
   }
 
   operate() {
@@ -78,8 +86,9 @@ export default class Change {
     this.compareIcons();
 
     this.compareTitle();
-    this.compareImage();
+    this.compareBgImage();
     this.compareCompass();
+    this.compareCustomImages();
     // }
     // //旋转了
     // else {
@@ -98,8 +107,9 @@ export default class Change {
       this.elements.arrows.length == 0 &&
       this.elements.icons.length == 0 &&
       this.elements.title == null &&
-      this.elements.image == null &&
-      this.elements.compass == null
+      this.elements.bgImage == null &&
+      this.elements.compass == null && 
+      this.elements.customImages.length == 0
     ) {
       this.saveCurrentInfo();
       return false;
@@ -516,29 +526,6 @@ export default class Change {
     }
   }
 
-  // compareAngle() {
-  //     const angle = floorplanService.getAngle()
-  //     const lastAngle = this.lastData.angle
-  //     const lastRes = this.lastData.res
-  //     if (historyUtil.isDifferentForAngle(angle, lastAngle)) {
-  //         const item = {
-  //             handle: HistoryEvents.ModifyAngle,
-  //             preState: {
-  //                 angle: historyUtil.getDataForAngle(lastAngle),
-  //                 res: historyUtil.getDataForRes(lastRes),
-  //             },
-  //             curState: {
-  //                 angle: historyUtil.getDataForAngle(angle),
-  //                 res: historyUtil.getDataForRes(coordinate.res),
-  //             },
-  //         }
-  //         this.elements.rotate = item
-  //         return true
-  //     } else {
-  //         return false
-  //     }
-  // }
-
   compareTitle() {
     this.elements.title = null;
     const title = floorplanService.getTitle();
@@ -555,19 +542,35 @@ export default class Change {
     }
   }
 
-  compareImage() {
-    this.elements.image = null;
-    const image = floorplanService.getBgImage();
-    const lastImage = this.lastData.image;
-
-    const flag = historyUtil.isDifferentForImage(image, lastImage);
-    if (flag) {
-      const item = {
-        handle: HistoryEvents.ModifyImage,
-        preImage: historyUtil.getDataForImage(lastImage),
-        curImage: historyUtil.getDataForImage(image),
+  compareBgImage() {
+    this.elements.bgImage = null;
+    const bgImage = floorplanService.getBgImage();
+    const lastBgImage = this.lastData.bgImage;
+    let item = {};
+    if ((!lastBgImage||!lastBgImage.url)&&(!bgImage||!bgImage.geoType)) {
+      return;
+    }
+    else if ((!lastBgImage||!lastBgImage.url)&&(bgImage&&bgImage.geoType)) {
+      item = {
+        handle: HistoryEvents.AddBgImage,
+        bgImage: historyUtil.getDataForBgImage(bgImage),
+      };
+      this.elements.bgImage = item;
+    } 
+    else if((lastBgImage&&lastBgImage.url)&&(!bgImage||!bgImage.geoType)){
+      item = {
+        handle: HistoryEvents.DeleteBgImage,
+        bgImage: historyUtil.getDataForBgImage(this.lastData.bgImage),
+      };
+      this.elements.bgImage = item;
+    }
+    else if(historyUtil.isDifferentForBgImage(bgImage, lastBgImage)){
+      item = {
+        handle: HistoryEvents.ModifyBgImage,
+        preBgImage: historyUtil.getDataForBgImage(lastBgImage),
+        curBgImage: historyUtil.getDataForBgImage(bgImage),
       };
-      this.elements.image = item;
+      this.elements.bgImage = item;
     }
   }
 
@@ -586,6 +589,45 @@ export default class Change {
       this.elements.compass = item;
     }
   }
+
+  compareCustomImages(){
+    this.elements.customImages = [];
+    const customImages = floorplanService.getCustomImages();
+    for (const key in customImages) {
+      const customImage = customImages[key];
+      const lastCustomImage = this.lastData.customImages[key];
+
+      // 不存在意味着增加
+      if (!lastCustomImage) {
+        const item = {
+          handle: HistoryEvents.AddCustomImage,
+          customImage: historyUtil.getDataForCustomImage(customImage),
+        };
+        this.elements.customImages.push(item);
+      } else {
+        if (!historyUtil.isDifferentForCustomImages(customImage, lastCustomImage)) {
+          delete this.lastData.customImages[key];
+          continue;
+        } else {
+          const item = {
+            handle: HistoryEvents.ModifyCustomImage,
+            preCustomImage: historyUtil.getDataForCustomImage(lastCustomImage),
+            curCustomImage: historyUtil.getDataForCustomImage(customImage),
+          };
+          this.elements.customImages.push(item);
+        }
+      }
+      delete this.lastData.customImages[key];
+    }
+
+    for (const key in this.lastData.customImages) {
+      const item = {
+        handle: HistoryEvents.DeleteCustomImage,
+        customImage: historyUtil.getDataForCustomImage(this.lastData.customImages[key]),
+      };
+      this.elements.customImages.push(item);
+    }
+  }
 }
 
 const change = new Change();

+ 74 - 64
src/view/case/draw/board/editCAD/History/History.js

@@ -14,7 +14,9 @@ import { rectangleService } from '../Service/RectangleService'
 import { circleService } from '../Service/CircleService'
 import { arrowService } from '../Service/ArrowService'
 import { iconService } from '../Service/IconService'
+import { customImageService } from "../Service/CustomImageService";
 import mitt from 'mitt'
+import { bgImageService } from '../Service/BgImageService'
 
 export default class History {
     constructor() {
@@ -27,6 +29,12 @@ export default class History {
         this.bus.emit('undoAvailable', false)
     }
 
+    clear(){
+        change.lastData = {}; 
+        change.elements = {}; 
+        historyService.clearHistoryRecord();
+    }
+
     save() {
         const flag = change.operate()
         if (!flag) {
@@ -85,8 +93,8 @@ export default class History {
     }
 
     // 撤销
-    handleUndo() {
-        this.goPreState()
+    async handleUndo() {
+        await this.goPreState()
         this.layer.renderer.autoRedraw()
         const historyState = historyService.getHistoryState()
         if (historyState.pre) {
@@ -102,8 +110,8 @@ export default class History {
     }
 
     // 恢复
-    handleRedo() {
-        this.goNextState()
+    async handleRedo() {
+        await this.goNextState()
         this.layer.renderer.autoRedraw()
         const historyState = historyService.getHistoryState()
         if (historyState.next) {
@@ -119,7 +127,7 @@ export default class History {
     }
    
     // 撤销
-    goPreState() {
+    async goPreState() {
         const item = historyService.getHistoryRecord()
         if (item) {
             stateService.clearItems()
@@ -139,8 +147,9 @@ export default class History {
             this.goPreForSigns(item.signs)
 
             this.goPreForTitle(item.title)
-            this.goPreForImage (item.image)
+            await this.goPreForBgImage (item.bgImage)
             this.goPreForCompass(item.compass)
+            await this.goPreForCustomImages(item.customImages)
 
             historyService.undoHistoryRecord()
             change.saveCurrentInfo()
@@ -328,11 +337,18 @@ export default class History {
         }
     }
 
-    goPreForImage(itemForImage) {
-        if (itemForImage != null && itemForImage.handle == HistoryEvents.ModifyImage) {
-            const preImage = itemForImage.preImage
-            let curImage = floorplanService.getBgImage()
-            historyUtil.assignImageFromImage(curImage, preImage,this.layer)
+    async goPreForBgImage(itemForBgImage) {
+        if(itemForBgImage){
+            if (itemForBgImage.handle == HistoryEvents.AddBgImage) {    
+                bgImageService.deleteBgImage()
+            } else if (itemForBgImage.handle == HistoryEvents.DeleteBgImage) {
+                let newBgImage = await bgImageService.createBgImage(itemForBgImage.bgImage.url,itemForBgImage.bgImage.id)
+                historyUtil.assignBgImageFromBgImage(newBgImage, itemForBgImage.bgImage)
+            } else if (itemForBgImage.handle == HistoryEvents.ModifyBgImage) {
+                const preBgImage = itemForBgImage.preBgImage
+                let curBgImage = floorplanService.getBgImage(itemForBgImage.curBgImage.id)
+                historyUtil.assignBgImageFromBgImage(curBgImage, preBgImage)
+            }
         }
     }
 
@@ -344,25 +360,21 @@ export default class History {
         }
     }
 
-
-    // goPreForAngle(itemForAngle) {
-    //     if (itemForAngle.handle == HistoryEvents.ModifyAngle) {
-    //         coordinate.reSet()
-    //         coordinate._setRes(itemForAngle.preState.res)
-    //         //旋转cad
-    //         floorplanService.setAngle(itemForAngle.preState.angle)
-    //         coordinate.updateForRotate(itemForAngle.preState.angle - itemForAngle.curState.angle)
-    //         //旋转三维模型
-    //         let info = coordinate.getScreenInfoForCAD()
-    //         info.floorPlanAngle = itemForAngle.preState.angle
-    //         this.layer.app.core.get('CameraControls').emit('syncCadAnd3DForRotate', info)
-    //         this.layer.app.store.getValue('metadata').floorPlanAngle = itemForAngle.preState.angle
-    //         this.layer.initPanos(floorplanService.getCurrentFloor())
-    //         return true
-    //     } else {
-    //         return false
-    //     }
-    // }
+    async goPreForCustomImages(itemForCustomImages) {
+        for (let i = 0; i < itemForCustomImages.length; ++i) {
+            const item = itemForCustomImages[i]
+            if (item.handle == HistoryEvents.AddCustomImage) {  
+                customImageService.deleteCustomImage(item.customImage.id)
+            } else if (item.handle == HistoryEvents.DeleteCustomImage) {
+                let newCustomImage = await customImageService.createCustomImage(item.customImage.url,item.customImage.center,item.customImage.id)
+                historyUtil.assignCustomImageFromCustomImage(newCustomImage, item.customImage)
+            } else if (item.handle == HistoryEvents.ModifyCustomImage) {
+                const preCustomImage = item.preCustomImage
+                let curCustomImage = floorplanService.getCustomImage(item.curCustomImage.id)
+                historyUtil.assignCustomImageFromCustomImage(curCustomImage, preCustomImage)
+            }
+        }
+    }
 
     goNextForPoints(itemForPoints) {
         for (let i = 0; i < itemForPoints.length; ++i) {
@@ -533,12 +545,20 @@ export default class History {
         }
     }
 
-    goNextForImage(itemForImage) {
-        if (itemForImage != null && itemForImage.handle == HistoryEvents.ModifyImage) {
-            const currentImage = itemForImage.curImage
-            let preImage = floorplanService.getBgImage()
-            historyUtil.assignImageFromImage(preImage, currentImage,this.layer)
+    async goNextForBgImage(itemForBgImage) {
+        if(itemForBgImage){
+            if (itemForBgImage.handle == HistoryEvents.AddBgImage) {
+                let vBgImage = await bgImageService.createBgImage(itemForBgImage.bgImage.url,itemForBgImage.bgImage.center, itemForBgImage.bgImage.id)
+                historyUtil.assignBgImageFromBgImage(vBgImage, itemForBgImage.bgImage)
+            } else if (itemForBgImage.handle == HistoryEvents.DeleteBgImage) {
+                floorplanService.deleteBgImage()
+            } else if (itemForBgImage.handle == HistoryEvents.ModifyBgImage) {
+                const currentBgImage = itemForBgImage.curBgImage
+                let preBgImage = floorplanService.getBgImage(itemForBgImage.curBgImage.id)
+                historyUtil.assignBgImageFromBgImage(preBgImage, currentBgImage)
+            }
         }
+
     }
 
     goNextForCompass(itemForCompass) {
@@ -549,27 +569,24 @@ export default class History {
         }
     }
 
-    // goNextForAngle(itemForAngle) {
-    //     if (itemForAngle.handle == HistoryEvents.ModifyAngle) {
-    //         coordinate.reSet()
-    //         coordinate._setRes(itemForAngle.curState.res)
-    //         //旋转cad
-    //         floorplanService.setAngle(itemForAngle.curState.angle)
-    //         coordinate.updateForRotate(itemForAngle.curState.angle - itemForAngle.preState.angle)
-    //         //旋转三维模型
-    //         let info = coordinate.getScreenInfoForCAD()
-    //         info.floorPlanAngle = itemForAngle.curState.angle
-    //         this.layer.app.core.get('CameraControls').emit('syncCadAnd3DForRotate', info)
-    //         this.layer.app.store.getValue('metadata').floorPlanAngle = itemForAngle.curState.angle
-    //         this.layer.initPanos(floorplanService.getCurrentFloor())
-    //         return true
-    //     } else {
-    //         return false
-    //     }
-    // }
+    async goNextForCustomImages(itemForCustomImages) {
+        for (let i = 0; i < itemForCustomImages.length; ++i) {
+            const item = itemForCustomImages[i]
+            if (item.handle == HistoryEvents.AddCustomImage) {
+                let vCustomImage = await customImageService.createCustomImage(item.customImage.url,item.customImage.center, item.customImage.id)
+                historyUtil.assignCustomImageFromCustomImage(vCustomImage, item.customImage)
+            } else if (item.handle == HistoryEvents.DeleteCustomImage) {
+                floorplanService.deleteCustomImage(item.customImage.id)
+            } else if (item.handle == HistoryEvents.ModifyCustomImage) {
+                const currentCustomImage = item.curCustomImage
+                let preCustomImage = floorplanService.getCustomImage(item.curCustomImage.id)
+                historyUtil.assignCustomImageFromCustomImage(preCustomImage, currentCustomImage)
+            }
+        }
+    }
 
     // 恢复
-    goNextState() {
+    async goNextState() {
         historyService.redoHistoryRecord()
         const item = historyService.getHistoryRecord()
         if (item) {
@@ -595,20 +612,13 @@ export default class History {
                 this.goNextForSigns(item.signs)
 
                 this.goNextForTitle(item.title)
-                this.goNextForImage (item.image)
+                await this.goNextForBgImage (item.bgImage)
                 this.goNextForCompass(item.compass)
+
+                await this.goNextForCustomImages(item.customImages)
             }
             change.saveCurrentInfo()
             this.setState()
-
-            // const points = floorplanService.getPoints()
-            // if (Object.keys(points).length > 0) {
-            //     this.layer.$xui.toolbar.clear = true
-            //     this.layer.$xui.toolbar.download = true
-            // } else {
-            //     this.layer.$xui.toolbar.clear = false
-            //     this.layer.$xui.toolbar.download = false
-            // }
         } else {
             historyService.undoHistoryRecord()
             console.error('goNextState超出范围!')

+ 62 - 41
src/view/case/draw/board/editCAD/History/HistoryUtil.js

@@ -1,6 +1,8 @@
 import { mathUtil } from '../MathUtil'
 import { arrowService } from '../Service/ArrowService'
+import { bgImageService } from '../Service/BgImageService'
 import { circleService } from '../Service/CircleService'
+import { customImageService } from '../Service/CustomImageService'
 import { floorplanService } from '../Service/FloorplanService'
 import { iconService } from '../Service/IconService'
 import { rectangleService } from '../Service/RectangleService'
@@ -13,7 +15,7 @@ export default class HistoryUtil {
     constructor() {}
 
     isDifferentForWalls(wall1, wall2) {
-        if (wall1.start == wall2.start && wall1.end == wall2.end) {
+        if (wall1.start == wall2.start && wall1.end == wall2.end && wall1.color == wall2.color) {
             return false
         } else {
             return true
@@ -21,7 +23,7 @@ export default class HistoryUtil {
     }
   
     isDifferentForTags(tag1, tag2) {
-        if (mathUtil.equalPoint(tag1.center, tag2.center) && tag1.value == tag2.value) {
+        if (mathUtil.equalPoint(tag1.center, tag2.center) && tag1.value == tag2.value && tag1.color == tag2.color && tag1.fontSize == tag2.fontSize) {
             return false
         } else {
             return true
@@ -60,7 +62,7 @@ export default class HistoryUtil {
 
     isDifferentForRectangles(rectangle1, rectangle2) {
         for(let i=0;i<rectangle1.points.length;++i){
-            if(!mathUtil.equalPoint(rectangle1.points[i], rectangle2.points[i])){
+            if(!mathUtil.equalPoint(rectangle1.points[i], rectangle2.points[i]) || rectangle1.color != rectangle2.color){
                 return true;
             }
         }
@@ -68,10 +70,7 @@ export default class HistoryUtil {
     }
 
     isDifferentForCircles(circle1, circle2) {
-        if(!mathUtil.equalPoint(circle1.center, circle2.center)){
-            return true;
-        }
-        else if(circle1.radius != circle2.radius){
+        if(!mathUtil.equalPoint(circle1.center, circle2.center) || circle1.color != circle2.color||circle1.radius != circle2.radius){
             return true;
         }
         else {
@@ -85,7 +84,7 @@ export default class HistoryUtil {
     }
 
     isDifferentForArrows(arrow1, arrow2) {
-        if (mathUtil.equalPoint(arrow1.startPoint, arrow2.startPoint) && mathUtil.equalPoint(arrow1.endPoint, arrow2.endPoint)) {
+        if (mathUtil.equalPoint(arrow1.startPoint, arrow2.startPoint) && mathUtil.equalPoint(arrow1.endPoint, arrow2.endPoint) && arrow1.color == arrow2.color) {
             return false
         } else {
             return true
@@ -131,8 +130,8 @@ export default class HistoryUtil {
         }
     }
 
-    isDifferentForImage(image1, image2) {
-        if (image1.src == image2.src) {
+    isDifferentForBgImage(bgImage1, bgImage2) {
+        if (bgImage1.url == bgImage2.url && bgImage1.scale == bgImage2.scale && JSON.stringify(bgImage1.center) == JSON.stringify(bgImage2.center)) {
             return false
         } else {
             return true
@@ -147,13 +146,13 @@ export default class HistoryUtil {
         }
     }
 
-    // isDifferentForAngle(angle1, angle2) {
-    //     if (angle1 == angle2) {
-    //         return false
-    //     } else {
-    //         return true
-    //     }
-    // }
+    isDifferentForCustomImages(customImage1, customImage2) {
+        if (customImage1.angle == customImage2.angle && customImage1.scale == customImage2.scale && customImage1.url == customImage2.url&& customImage1.ratio == customImage2.ratio) {
+            return false
+        } else {
+            return true
+        }
+    }
 
     // wall2赋值给wall1
     assignWallFromWall(wall1, wall2) {
@@ -161,6 +160,7 @@ export default class HistoryUtil {
         wallInfo.vectorId = wall1.vectorId
         wallInfo.start = wall2.start
         wallInfo.end = wall2.end
+        wallInfo.color = wall2.color
         wallService.setWallInfo(wallInfo)
     }
 
@@ -176,6 +176,8 @@ export default class HistoryUtil {
         const tagInfo = {}
         tagInfo.vectorId = tag1.vectorId
         tagInfo.value = tag2.value
+        tagInfo.color = tag2.color
+        tagInfo.fontSize = tag2.fontSize
         tagInfo.center = JSON.parse(JSON.stringify(tag2.center))
         tagInfo.points2d = JSON.parse(JSON.stringify(tag2.points))
         tagService.setTagInfo(tagInfo)
@@ -204,20 +206,11 @@ export default class HistoryUtil {
         tableService.setTableInfo(tableInfo)
     }
 
-    // eslint-disable-next-line no-dupe-class-members
-    assignTagFromTag(tag1, tag2) {
-        const tagInfo = {}
-        tagInfo.vectorId = tag1.vectorId
-        tagInfo.value = tag2.value
-        tagInfo.center = JSON.parse(JSON.stringify(tag2.center))
-        tagInfo.points2d = JSON.parse(JSON.stringify(tag2.points))
-        tagService.setTagInfo(tagInfo)
-    }
-
     assignRectangleFromRectangle(rectangle1, rectangle2) {
         const rectangleInfo = {}
         rectangleInfo.vectorId = rectangle1.vectorId
         rectangleInfo.angle = rectangle2.angle
+        rectangleInfo.color = rectangle2.color
         rectangleInfo.points = JSON.parse(JSON.stringify(rectangle2.points))
         rectangleService.setRectangleInfo(rectangleInfo)
     }
@@ -226,6 +219,7 @@ export default class HistoryUtil {
         const circleInfo = {}
         circleInfo.vectorId = circle1.vectorId
         circleInfo.radius = circle2.radius
+        circleInfo.color = circle2.color
         circleInfo.center = JSON.parse(JSON.stringify(circle2.center))
         circleInfo.points = JSON.parse(JSON.stringify(circle2.points))
         circleService.setCircleInfo(circleInfo)
@@ -234,6 +228,7 @@ export default class HistoryUtil {
     assignArrowFromArrow(arrow1, arrow2) {
         const arrowInfo = {}
         arrowInfo.vectorId = arrow1.vectorId
+        arrowInfo.color = arrow2.color
         arrowInfo.startPoint = JSON.parse(JSON.stringify(arrow2.startPoint))
         arrowInfo.endPoint = JSON.parse(JSON.stringify(arrow2.endPoint))
         arrowService.setArrowInfo(arrowInfo)
@@ -267,11 +262,13 @@ export default class HistoryUtil {
         floorplanService.updateTitle(titleInfo.value)
     }
 
-    assignImageFromImage(image1, image2,layer) {
-        const imageInfo = {}
-        imageInfo.vectorId = image1.vectorId
-        imageInfo.src = image2.src
-        floorplanService.updateBgImage(imageInfo.src,layer)
+    assignBgImageFromBgImage(image1, image2) {
+        const bgImageInfo = {}
+        bgImageInfo.vectorId = image1.vectorId
+        bgImageInfo.url = image2.url
+        bgImageInfo.center = JSON.parse(JSON.stringify(image2.center))
+        bgImageInfo.scale = image2.scale
+        bgImageService.setBgImageInfo(bgImageInfo)
     }
 
     assignCompassFromCompass(compass1, compass2) {
@@ -281,6 +278,16 @@ export default class HistoryUtil {
         floorplanService.updateCompass(compassInfo.angle )
     }
 
+    assignCustomImageFromCustomImage(customImage1, customImage2) {
+        const customImageInfo = {}
+        customImageInfo.vectorId = customImage1.vectorId
+        customImageInfo.angle = customImage2.angle
+        customImageInfo.url = customImage2.url
+        customImageInfo.center = JSON.parse(JSON.stringify(customImage2.center))
+        customImageInfo.scale = customImage2.scale
+        customImageService.setCustomImageInfo(customImageInfo)
+    }
+
     deletePoint(pointId) {
         const point = floorplanService.getPoint(pointId)
         const parent = point.parent
@@ -299,6 +306,7 @@ export default class HistoryUtil {
     getDataForWall(wall) {
         const data = {}
         data.id = wall.vectorId
+        data.color = wall.color
         data.start = wall.start
         data.end = wall.end
         data.type = wall.geoType
@@ -319,6 +327,8 @@ export default class HistoryUtil {
         const data = {}
         data.id = tag.vectorId
         data.type = tag.geoType
+        data.color = tag.color
+        data.fontSize = tag.fontSize
         data.center = {}
         mathUtil.clonePoint(data.center, tag.center)
         data.points = [].concat(tag.points2d)
@@ -356,6 +366,7 @@ export default class HistoryUtil {
         const data = {}
         data.id = rectangle.vectorId
         data.type = rectangle.geoType
+        data.color = rectangle.color
         data.angle = rectangle.angle
         data.points = [].concat(rectangle.points)
         return data
@@ -365,6 +376,7 @@ export default class HistoryUtil {
         const data = {}
         data.id = circle.vectorId
         data.type = circle.geoType
+        data.color = circle.color
         data.center = {}
         mathUtil.clonePoint(data.center, circle.center)
         data.points = [].concat(circle.points)
@@ -376,6 +388,7 @@ export default class HistoryUtil {
         const data = {}
         data.id = arrow.vectorId
         data.type = arrow.geoType
+        data.color = arrow.color
         data.startPoint = {}
         mathUtil.clonePoint(data.startPoint, arrow.startPoint)
         data.endPoint = {}
@@ -404,11 +417,14 @@ export default class HistoryUtil {
         return data
     }
 
-    getDataForImage(image) {
+    getDataForBgImage(image) {
         const data = {}
         data.id = image.vectorId
         data.type = image.geoType
-        data.src = image.src
+        data.url = image.url
+        data.center = {}
+        mathUtil.clonePoint(data.center, image.center)
+        data.scale = image.scale
         return data
     }
 
@@ -420,13 +436,18 @@ export default class HistoryUtil {
         return data
     }
 
-    // getDataForAngle(angle) {
-    //     return angle
-    // }
-
-    // getDataForRes(res) {
-    //     return res
-    // }
+    getDataForCustomImage(customImage) {
+        const data = {}
+        data.id = customImage.vectorId
+        data.type = customImage.geoType
+        data.angle = customImage.angle
+        data.scale = customImage.scale
+        data.url = customImage.url
+        data.center = {}
+        mathUtil.clonePoint(data.center, customImage.center)
+        data.ratio = customImage.ratio
+        return data
+    }
 }
 
 const historyUtil = new HistoryUtil()

+ 51 - 15
src/view/case/draw/board/editCAD/Layer.js

@@ -35,6 +35,9 @@ import { wallService } from "./Service/WallService";
 import { history } from "./History/History.js";
 import { signService } from "./Service/SignService";
 import { iconService } from "./Service/IconService";
+import { customImageService } from "./Service/CustomImageService.js";
+import { moveCustomImage } from "./Controls/MoveCustomImage.js";
+import { moveBgImage } from "./Controls/MoveBgImage.js";
 
 export default class Layer {
   constructor() {
@@ -139,11 +142,13 @@ export default class Layer {
       if (eventName == null && selectItem) {
         stateService.setDraggingItem(selectItem);
         this.uiControl.selectUI = selectItem.type;
-      } else if (eventName == null) {
-        this.uiControl.clearUI();
-      }
+      } 
+      // else if (eventName == null) {
+      //   this.uiControl.clearUI();
+      // }
     }
     this.setEventName("mouseDown");
+    this.uiControl.clearUI();
     // 清除上一个状态
     // 设置当前事件名称
     e.preventDefault();
@@ -178,12 +183,6 @@ export default class Layer {
         needAutoRedraw = listenLayer.start(position);
         break;
       case LayerEvents.PanBackGround:
-        // stateService.clearItems()
-        // coordinate.center.x = coordinate.center.x - (dx * Constant.defaultZoom) / coordinate.zoom / coordinate.res
-        // coordinate.center.y = coordinate.center.y + (dy * Constant.defaultZoom) / coordinate.zoom / coordinate.res
-        // this.lastX = X
-        // this.lastY = Y
-        // needAutoRedraw = true
         break;
       case LayerEvents.AddWall:
         stateService.clearDraggingItem();
@@ -516,11 +515,27 @@ export default class Layer {
           mathUtil.clonePoint(sign.center, position);
         }
         break;
+      case LayerEvents.MoveCustomImage:
+        needAutoRedraw = true;
+        if (draggingItem != null) {
+          moveCustomImage.moveFullCustomImage(dx, dy, draggingItem.vectorId);
+          this.lastX = X;
+          this.lastY = Y;
+        }
+        break;
       case LayerEvents.MoveSign:
         needAutoRedraw = true;
         const sign = floorplanService.getSign(draggingItem.vectorId);
         mathUtil.clonePoint(sign.center, position);
         break;
+      case LayerEvents.MoveBgImage:
+        needAutoRedraw = true;
+        if (draggingItem != null) {
+          moveBgImage.moveFullBgImage(dx, dy, draggingItem.vectorId);
+          this.lastX = X;
+          this.lastY = Y;
+        }
+        break;
     }
 
     if (needAutoRedraw) {
@@ -560,6 +575,7 @@ export default class Layer {
         focusItem = null;
       }
       stateService.setFocusItem(focusItem);
+      //this.uiControl.clearUI();
     }
 
     let position = coordinate.getXYFromScreen({
@@ -578,7 +594,6 @@ export default class Layer {
       case LayerEvents.MoveWallPoint:
         if (focusItem == null) {
           needAutoRedraw = true;
-          elementService.hideAll();
           let point = floorplanService.getPoint(draggingItem.vectorId);
           if (point) {
             listenLayer.start(point, draggingItem.vectorId, point.parent);
@@ -625,6 +640,7 @@ export default class Layer {
         } else {
           this.uiControl.showAttributes(focusItem);
         }
+        elementService.hideAll();
         break;
       case LayerEvents.AddingWall:
         needAutoRedraw = true;
@@ -788,15 +804,30 @@ export default class Layer {
       case LayerEvents.AddSign:
         needAutoRedraw = true;
         this.uiControl.clearUI();
+        this.uiControl.showAttributes(focusItem);
         history.save();
         break;
       case LayerEvents.MoveSign:
-        needAutoRedraw = true;
         if (focusItem != null && signService.isSign(focusItem.type)) {
-          this.uiControl.selectUI = focusItem.type;
+          //history.save();
+          this.uiControl.showAttributes(focusItem);
+        } else {
+          needAutoRedraw = true;
+          history.save();
+        }
+        break;
+      case LayerEvents.MoveCustomImage:
+        if (focusItem == null) {
           history.save();
         } else {
+          this.uiControl.showAttributes(focusItem);
+        }
+        break;
+      case LayerEvents.MoveBgImage:
+        if (focusItem == null) {
           history.save();
+        } else {
+          this.uiControl.showAttributes(focusItem);
         }
         break;
     }
@@ -894,7 +925,11 @@ export default class Layer {
           stateService.setEventName(LayerEvents.MoveTitle);
         } else if (selectItem.type == VectorType.Compass) {
           stateService.setEventName(LayerEvents.MoveCompass);
-        }
+        } else if (selectItem.type == VectorType.CustomImage) {
+          stateService.setEventName(LayerEvents.MoveCustomImage);
+        } else if (selectItem.type == VectorType.BgImage) {
+          stateService.setEventName(LayerEvents.MoveBgImage);
+        } 
       } else if (eventName == LayerEvents.AddWall) {
         stateService.setEventName(LayerEvents.AddingWall);
       }
@@ -911,7 +946,8 @@ export default class Layer {
         stateService.setEventName(LayerEvents.AddingArrow);
       } else if (eventName == LayerEvents.AddTable) {
         stateService.clearEventName();
-      } else if (
+      } 
+      else if (
         eventName != LayerEvents.AddWall &&
         eventName != LayerEvents.AddingWall
       ) {
@@ -949,7 +985,7 @@ export default class Layer {
           floorplanService.deleteSign(draggingItem.vectorId);
           stateService.clearItems();
         }
-      }
+      } 
     } else {
       stateService.setEventName(LayerEvents.AddWall);
     }

+ 85 - 5
src/view/case/draw/board/editCAD/ListenLayer.js

@@ -52,6 +52,16 @@ export default class ListenLayer {
             state: null,
         }
 
+        this.customImageInfo = {
+            customImageId: null,
+            state: null,
+        }
+
+        this.bgImageInfo = {
+            bgImageId: null,
+            state: null,
+        }
+
         this.titleInfo = {
             titleId: null,
             state: null,
@@ -254,6 +264,8 @@ export default class ListenLayer {
             iconInfo: {},
             tagInfo: {},
             signInfo: {},
+            customImageInfo: {},
+            bgImageInfo:{},
             titleInfo: {},
             compassInfo: {},
         }
@@ -388,6 +400,30 @@ export default class ListenLayer {
             }
         }
 
+        const customImages = floorplanService.getCustomImages()
+        for (const customImageId in customImages) {
+            const customImage = floorplanService.getCustomImage(customImageId)
+            const location = customImage.isContain(position)
+            if (location) {
+                result.customImageInfo = {
+                    customImageId: customImageId,
+                    state: 'all',
+                }
+                break
+            }
+        }
+
+        const bgImage = floorplanService.getBgImage()
+        if(bgImage && bgImage.url){
+            const location = bgImage.isContain(position)
+            if (location) {
+                result.bgImageInfo = {
+                    bgImageId: bgImage.vectorId,
+                    state: 'all',
+                }
+            }
+        }
+
         const title = floorplanService.getTitle();
         const titleFLag = title.isContain(position)
         if(titleFLag){
@@ -520,7 +556,19 @@ export default class ListenLayer {
             state: nearest.compassInfo.state,
         }
 
-        return flag1 || flag2 || flag3 || flag4  || flag5 || flag6 || flag7 || flag8 || flag9 || flag10 || flag11
+        const flag12 = this.isChanged(nearest.customImageInfo.customImageId, nearest.customImageInfo.state, 12)
+        this.customImageInfo = {
+            customImageId: nearest.customImageInfo.customImageId,
+            state: nearest.customImageInfo.state,
+        }
+
+        const flag13 = this.isChanged(nearest.bgImageInfo.bgImageId, nearest.bgImageInfo.state, 13)
+        this.bgImageInfo = {
+            bgImageId: nearest.bgImageInfo.bgImageId,
+            state: nearest.bgImageInfo.state,
+        }
+
+        return flag1 || flag2 || flag3 || flag4  || flag5 || flag6 || flag7 || flag8 || flag9 || flag10 || flag11 || flag12 || flag13
     }
 
     isChanged(vectorId, state, type, index) {
@@ -640,7 +688,24 @@ export default class ListenLayer {
                 flag = true
             }
         }
-
+        else if (type == 12) {
+            if (state == null && state == this.customImageInfo.state) {
+                flag = false
+            } else if (this.customImageInfo.customImageId == vectorId && state == this.customImageInfo.state) {
+                flag = false
+            } else {
+                flag = true
+            }
+        } 
+        else if (type == 13) {
+            if (state == null && state == this.bgImageInfo.state) {
+                flag = false
+            } else if (this.bgImageInfo.bgImageId == vectorId && state == this.bgImageInfo.state) {
+                flag = false
+            } else {
+                flag = true
+            }
+        } 
         return flag
     }
 
@@ -682,9 +747,6 @@ export default class ListenLayer {
             }
         } else if (this.arrowInfo.arrowId != null && this.arrowInfo.state != null) {
             stateService.setSelectItem(this.arrowInfo.arrowId, VectorType.Arrow, this.arrowInfo.state)
-        } else if (this.signInfo.signsId != null && this.signInfo.state != null) {
-            const sign = floorplanService.getSign(this.signInfo.signsId)
-            stateService.setSelectItem(this.signInfo.signsId, sign.geoType, this.signInfo.state)
         } else if (this.tableInfo.tableId != null && this.tableInfo.state != null) {
             const table = floorplanService.getTable(this.tableInfo.tableId)
             stateService.setSelectItem(this.tableInfo.tableId, table.geoType, this.tableInfo.state)
@@ -694,6 +756,14 @@ export default class ListenLayer {
         } else if (this.compassInfo.compassId != null && this.compassInfo.state != null) {
             const compass = floorplanService.getCompass()
             stateService.setSelectItem(this.compassInfo.compassId, compass.geoType, this.compassInfo.state)
+        } else if (this.customImageInfo.customImageId != null && this.customImageInfo.state != null) {
+            const customImage = floorplanService.getCustomImage(this.customImageInfo.customImageId)
+            stateService.setSelectItem(this.customImageInfo.customImageId, customImage.geoType, this.customImageInfo.state)
+        } else if (this.bgImageInfo.bgImageId != null && this.bgImageInfo.state != null) {
+            const bgImage = floorplanService.getBgImage()
+            if(bgImage.url){
+                stateService.setSelectItem(this.bgImageInfo.bgImageId, bgImage.geoType, this.bgImageInfo.state)
+            }
         }
         else {
             stateService.clearSelectItem()
@@ -746,6 +816,16 @@ export default class ListenLayer {
             state: null,
         }
 
+        this.customImageInfo = {
+            customImageId: null,
+            state: null,
+        }
+
+        this.bgImageInfo = {
+            bgImageId: null,
+            state: null,
+        }
+
         this.modifyPoint = null
     }
 }

+ 34 - 24
src/view/case/draw/board/editCAD/Load.js

@@ -7,15 +7,14 @@ import { circleService } from './Service/CircleService.js'
 import { arrowService } from './Service/ArrowService.js'
 import { iconService } from './Service/IconService.js'
 import { tableService } from './Service/TableService.js'
+import { customImageService } from './Service/CustomImageService.js'
+import { bgImageService } from './Service/BgImageService.js'
 
 export default class Load {
     constructor(layer) {
         this.layer = layer
         this.version = 'v1.1'
         this.vectorsJson = null
-        // 保存当前的数据
-        this.saveFloors = []
-        this.newVectorId = null
     }
 
     async load(floorsData) {
@@ -27,13 +26,13 @@ export default class Load {
             //添加指南针
             const compass = floorplanService.createCompass()
             floorplanService.addCompass(compass)
-            //添加背景图片
-            const bgImage = floorplanService.createBgImage()
-            floorplanService.addBgImage(bgImage)
-            if(bgImage.src){
-                const imageData = await floorplanService.loadImageData(bgImage.src)
-                bgImage.setImageData(imageData)
-            }
+            // //添加背景图片
+            // const bgImage = floorplanService.createBgImage()
+            // floorplanService.addBgImage(bgImage)
+            // if(bgImage.url){
+            //     const imageData = await floorplanService.loadImageData(bgImage.url)
+            //     bgImage.setImageData(imageData)
+            // }
             return;
         }
         floorplanService.setCurrentId(floorsData.currentId)
@@ -45,12 +44,22 @@ export default class Load {
             }
 
             for (let key in floor.walls) {
-                wallService.createWall(floor.walls[key].start, floor.walls[key].end, floor.walls[key].vectorId, i)
+                let wall = wallService.createWall(floor.walls[key].start, floor.walls[key].end, floor.walls[key].vectorId, i)
+                floor.walls[key].color && wall.setColor(floor.walls[key].color)
             }
 
             for (let key in floor.tags) {
                 let tag = tagService.createTag(floor.tags[key].center, floor.tags[key].vectorId, i)
                 tag.setValue(floor.tags[key].value)
+                floor.tags[key].color && tag.setColor(floor.tags[key].color)
+                floor.tags[key].fontSize && tag.setFontSize(floor.tags[key].fontSize)
+            }
+
+            for (let key in floor.customImages) {
+                let customImage = await customImageService.createCustomImage(floor.customImages[key].url,floor.customImages[key].center, floor.customImages[key].vectorId)
+                customImage.setAngle(floor.customImages[key].angle)
+                customImage.setScale(floor.customImages[key].scale)
+                customImage.setRatio(floor.customImages[key].ratio)
             }
 
             for (let key in floor.tables) {
@@ -71,42 +80,43 @@ export default class Load {
             }
 
             for (let key in floor.rectangles) {
-                rectangleService.createRectangle(floor.rectangles[key].points[0], floor.rectangles[key].points[2], floor.rectangles[key].vectorId,i)
+                let rectangle = rectangleService.createRectangle(floor.rectangles[key].points[0], floor.rectangles[key].points[2], floor.rectangles[key].vectorId,i)
+                floor.rectangles[key].color && rectangle.setColor(floor.rectangles[key].color)
             }
 
             for (let key in floor.circles) {
-                circleService.createCircle2(floor.circles[key].center, floor.circles[key].radius, floor.circles[key].vectorId,i)
+                let circle =  circleService.createCircle2(floor.circles[key].center, floor.circles[key].radius, floor.circles[key].vectorId,i)
+                floor.circles[key].color && circle.setColor(floor.circles[key].color)
             }
 
             for (let key in floor.arrows) {
-                arrowService.createArrow(floor.arrows[key].startPoint,floor.arrows[key].endPoint,floor.arrows[key].vectorId,i)
+                let arrow = arrowService.createArrow(floor.arrows[key].startPoint,floor.arrows[key].endPoint,floor.arrows[key].vectorId,i)
+                floor.arrows[key].color && arrow.setColor(floor.arrows[key].color)
             }
 
             for (let key in floor.icons) {
                 iconService.createIcon2(floor.icons[key].center,floor.icons[key].radius,floor.icons[key].value,floor.icons[key].vectorId,i)
             }
-            //要更新value
 
             for (let key in floor.signs) {
                 let sign = signService.createSign(floor.signs[key].center, floor.signs[key].geoType, floor.signs[key].vectorId,i)
                 sign.angle = floor.signs[key].angle
+                floor.signs[key].scale && sign.setScale(floor.signs[key].scale)
             }
 
             const title = floorplanService.createTitle(floor.title.value, floor.title.vectorId, i)
             floorplanService.addTitle(title)
 
-            const bgImage = floorplanService.createBgImage(floor.image.src, floor.image.vectorId, i)
-            floorplanService.addBgImage(bgImage)
-            if(bgImage.src){
-                const imageData = await floorplanService.loadImageData(bgImage.src)
-                bgImage.setImageData(imageData)
-            }
-
-
             const compass = floorplanService.createCompass(floor.compass.angle, floor.compass.vectorId, i)
             floorplanService.addCompass(compass)
             
-  
+            if (floor.bgImage && floor.bgImage.url) {
+                const bgImage = await bgImageService.createBgImage(floor.bgImage.url,floor.bgImage.center)
+                if(floor.bgImage.hasOwnProperty('scale'))
+                {
+                    bgImage.setScale(floor.bgImage.scale)
+                }
+            }
         }
     }
 

+ 221 - 78
src/view/case/draw/board/editCAD/Renderer/Draw.js

@@ -25,16 +25,20 @@ export default class Draw {
         this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
     }
 
-    drawBackGround(color) {
+    drawBackGround() {
         this.context.save()
-        this.context.fillStyle = color
-        this.context.fillRect(0, 0, this.context.canvas.width, this.context.canvas.height)
+        this.context.fillStyle = 'white';
+        this.context.fillRect(0,0,this.context.canvas.width,this.context.canvas.height)
         this.context.restore()
     }
 
-    // setSVGAttr(svgId,width,height){
-
-    // }
+    drawFrame() {
+        this.context.save()
+        this.context.strokeStyle = 'black';
+        const ratio =  this.context.canvas.width/297 ;
+        this.context.strokeRect(ratio * 20, ratio * 20,this.context.canvas.width - 2*ratio * 20,this.context.canvas.height - 2*ratio * 20)
+        this.context.restore()
+    }
 
     drawWall(vector) {
         let start = floorplanService.getPoint(vector.start)
@@ -52,11 +56,9 @@ export default class Draw {
         this.context.save()
         this.context.beginPath()
         this.context.lineCap = 'round' //线段端点的样式
-        //this.context.lineJoin= 'miter';
-        this.context.strokeStyle = Style.Wall.strokeStyle
 
         this.context.lineWidth = Style.Wall.lineWidth * coordinate.ratio
-        this.context.strokeStyle = Style.Wall.strokeStyle
+        this.context.strokeStyle = vector.color
 
         const selectItem = stateService.getSelectItem()
         const draggingItem = stateService.getDraggingItem()
@@ -184,8 +186,8 @@ export default class Draw {
         this.context.save()
 
         this.context.lineWidth = Style.Tag.lineWidth * coordinate.ratio
-        this.context.strokeStyle = Style.Tag.strokeStyle
-        this.context.fillStyle = Style.Tag.fillStyle
+        this.context.strokeStyle = geometry.color
+        this.context.fillStyle = geometry.color
 
         const selectItem = stateService.getSelectItem()
         const draggingItem = stateService.getDraggingItem()
@@ -210,11 +212,18 @@ export default class Draw {
             }
         }
 
-        const fontSize = coordinate.ratio == Constant.ratio ? 36 : 12
-        this.context.font = `400 ${fontSize}px Microsoft YaHei`
+        this.context.font = `400 ${geometry.fontSize* coordinate.ratio}px Microsoft YaHei`
 
         //根据文字的长度,更新标注范围
-        geometry.sideWidth = Math.max(this.context.measureText(geometry.value).width, this.context.measureText(parseFloat(geometry.value).toFixed(2)).width)
+        //geometry.sideWidth = Math.max(this.context.measureText(geometry.value).width, this.context.measureText(parseFloat(geometry.value).toFixed(2)).width)
+        let fontInfo = geometry.setFontLenAndHeight()
+        let sideWidth = 0;
+        for(let i=0;i<fontInfo.textValues.length;++i){
+            sideWidth = Math.max(sideWidth,this.context.measureText(fontInfo.textValues[i]).width, this.context.measureText(parseFloat(fontInfo.textValues[i]).toFixed(2)).width)
+        }
+        geometry.sideWidth = sideWidth
+        geometry.sideThickness = fontInfo.height * (geometry.fontSize)
+        
         geometry.setPoints2d()
 
         let points2d = geometry.points2d
@@ -223,18 +232,41 @@ export default class Draw {
             points[i] = coordinate.getScreenXY({ x: points2d[i].x, y: points2d[i].y })
         }
 
-        let pt = { x: geometry.center.x, y: geometry.center.y }
-        pt = coordinate.getScreenXY({ x: geometry.center.x, y: geometry.center.y })
-        const fontWidth1 = this.context.measureText(geometry.value).width
-        const line1 = mathUtil.createLine1({ x: (points[0].x + points[3].x) / 2, y: (points[0].y + points[3].y) / 2 }, { x: (points[2].x + points[1].x) / 2, y: (points[2].y + points[1].y) / 2 })
-        const fontStart1 = mathUtil.getDisPointsLine(line1, pt, fontWidth1 / 2, fontWidth1 / 2)
-
-        if (fontStart1.newpoint1.x < fontStart1.newpoint2.x) {
-            this.context.fillText(geometry.value, fontStart1.newpoint1.x, fontStart1.newpoint1.y)
-        } else {
-            this.context.fillText(geometry.value, fontStart1.newpoint2.x, fontStart1.newpoint2.y)
+        let pt = coordinate.getScreenXY({ x: geometry.center.x, y: geometry.center.y })
+        const fontWidth1 = geometry.sideWidth
+        //let dy = (points[3].y - points[0].y)/fontInfo.textValues.length/2
+        let dy = geometry.sideThickness/fontInfo.height/2*coordinate.ratio
+        for(let i=0;i<fontInfo.textValues.length;++i){
+            // const line1 = mathUtil.createLine1({ x: (points[0].x + points[3].x) / 2, y: (points[0].y + points[3].y) / 2 }, { x: (points[2].x + points[1].x) / 2, y: (points[2].y + points[1].y) / 2 })
+            // const fontStart1 = mathUtil.getDisPointsLine(line1, pt, fontWidth1 / 2, fontWidth1 / 2)
+    
+            // if (fontStart1.newpoint1.x < fontStart1.newpoint2.x) {
+            //     this.context.fillText(geometry.value, fontStart1.newpoint1.x, fontStart1.newpoint1.y)
+            // } else {
+            //     this.context.fillText(geometry.value, fontStart1.newpoint2.x, fontStart1.newpoint2.y)
+            // }
+            //let count = (2*(i-(fontInfo.textValues.length-1))+1)
+            //let count = 1 * (fontInfo.textValues.length-i)
+            let count = i+1
+            count = 2* count  - fontInfo.textValues.length
+            this.context.fillText(fontInfo.textValues[i], pt.x - fontWidth1/2, pt.y + dy*count)
         }
 
+
+
+        // this.context.beginPath()
+        // this.context.arc(pt.x, pt.y, 2 * coordinate.ratio, 0, Math.PI * 2, true)
+        // this.context.stroke()
+        // this.context.fill()
+
+        // this.context.beginPath()
+        // this.context.moveTo(points[0].x, points[0].y)
+        // this.context.lineTo(points[1].x, points[1].y)
+        // this.context.lineTo(points[2].x, points[2].y)
+        // this.context.lineTo(points[3].x, points[3].y)
+        // this.context.closePath();
+        // this.context.stroke()
+
         this.context.restore()
     }
 
@@ -255,7 +287,6 @@ export default class Draw {
     }
 
     drawCell(geometry,width,height){
-
         this.context.save()
         this.context.lineWidth = Style.Table.lineWidth * coordinate.ratio
         this.context.strokeStyle = Style.Table.strokeStyle
@@ -286,16 +317,13 @@ export default class Draw {
         this.context.translate(width, height)
         this.context.beginPath()
         this.context.moveTo(0,0)
-        this.context.lineTo(geometry.width,0)
-        this.context.lineTo(geometry.width,geometry.height)
-        this.context.lineTo(0,geometry.height)
+        this.context.lineTo(geometry.width* coordinate.ratio,0)
+        this.context.lineTo(geometry.width* coordinate.ratio,geometry.height* coordinate.ratio)
+        this.context.lineTo(0,geometry.height* coordinate.ratio)
         this.context.closePath();
         this.context.stroke()
 
-        const defaultHeight = 24;
-        const defaultWidth = 156;
-
-        this.context.font = '12px Microsoft YaHei'
+        this.context.font = 12*coordinate.ratio+'px Microsoft YaHei'
         let fontWidth = this.context.measureText(geometry.value).width
         //如果是数字或者字母
         const patt = /[A-z0-9]/g;
@@ -307,12 +335,12 @@ export default class Draw {
         
 
         //let rowCount = Math.ceil(geometry.height/defaultHeight)               //分几行写
-        let rowCount = Math.ceil(fontWidth/geometry.width)                        //分几行写,根据要写的文字的长度来判断
+        let rowCount = Math.ceil(fontWidth/(geometry.width* coordinate.ratio))                        //分几行写,根据要写的文字的长度来判断
 
         if(rowCount == 1){
             this.context.textAlign = "center";
             this.context.textBaseline = "middle";
-            this.context.fillText(geometry.value, geometry.width/2, geometry.height/2)
+            this.context.fillText(geometry.value, geometry.width/2* coordinate.ratio, geometry.height/2* coordinate.ratio)
         }
         //大于1行
         else{
@@ -321,7 +349,7 @@ export default class Draw {
             this.context.textAlign = "left";
             for(let i=0;i<rowCount;++i){
                 const value = geometry.value.substr(i*rowFontCount,rowFontCount)
-                this.context.fillText(value, geometry.width/2 - rowWidth/2, 18+18*i)
+                this.context.fillText(value, (geometry.width/2* coordinate.ratio- rowWidth/2), (18+18*i)* coordinate.ratio)
             }
         }
 
@@ -337,10 +365,9 @@ export default class Draw {
         this.context.save()
         this.context.beginPath()
         this.context.lineCap = 'round' //线段端点的样式;
-        this.context.strokeStyle = Style.Rectangle.strokeStyle
 
         this.context.lineWidth = Style.Rectangle.lineWidth * coordinate.ratio
-        this.context.strokeStyle = Style.Rectangle.strokeStyle
+        this.context.strokeStyle = geometry.color
 
         const selectItem = stateService.getSelectItem()
         const draggingItem = stateService.getDraggingItem()
@@ -426,12 +453,14 @@ export default class Draw {
 
     drawCircleGeo(geometry)
     {
-        let radius = geometry.radius * coordinate.res * coordinate.zoom/Constant.defaultZoom
+        let radius = geometry.radius * coordinate.res * coordinate.zoom/Constant.defaultZoom * coordinate.ratio
         const twoPi = Math.PI * 2
         const pt = coordinate.getScreenXY(geometry.center)
 
         this.context.save()
-        this.context.strokeStyle = Style.Circle.strokeStyle
+        
+        this.context.lineWidth = Style.Circle.lineWidth * coordinate.ratio
+        this.context.strokeStyle = geometry.color
 
         const selectItem = stateService.getSelectItem()
         const draggingItem = stateService.getDraggingItem()
@@ -510,7 +539,7 @@ export default class Draw {
 
     drawIcon(geometry)
     {
-        let radius = geometry.radius * coordinate.res * coordinate.zoom/Constant.defaultZoom
+        let radius = geometry.radius * coordinate.res * coordinate.zoom/Constant.defaultZoom * coordinate.ratio
         const twoPi = Math.PI * 2
         const pt = coordinate.getScreenXY(geometry.center)
 
@@ -592,8 +621,8 @@ export default class Draw {
         this.context.save()
         this.setCanvasStyle(Style.Font)
         
-        let fonSize = Math.ceil(radius * 14/20);
-        this.context.font = fonSize + Style.Font.font;   
+        let fontSize = Math.ceil(radius * 14/20);
+        this.context.font = fontSize + Style.Font.font   
         let center = coordinate.getScreenXY(geometry.center);
         this.context.fillText(geometry.value, center.x , center.y)
         this.context.restore()
@@ -604,7 +633,7 @@ export default class Draw {
         this.context.save()
         this.context.beginPath()
         this.context.lineCap = 'round' //线段端点的样式
-        this.context.strokeStyle = Style.Arrow.strokeStyle
+        this.context.strokeStyle = geometry.color
         this.context.lineWidth = Style.Arrow.lineWidth * coordinate.ratio
 
         const selectItem = stateService.getSelectItem()
@@ -809,37 +838,148 @@ export default class Draw {
             this.context.strokeStyle = Style.Focus.Title.strokeStyle
             this.context.fillStyle = Style.Focus.Title.fillStyle
         }
-        //let pt = {}
-        //pt.x = (this.context.canvas.width - this.context.measureText(geometry.value).width)/2
-        this.context.fillText(geometry.value, this.context.canvas.width/2, geometry.height)
+        //this.context.font = `400 ${geometry.fontSize}px Microsoft YaHei`
+        this.context.font = `${24*coordinate.ratio}px Microsoft YaHei`
+        this.context.fillText(geometry.value, this.context.canvas.width/2, geometry.height*coordinate.ratio)
         this.context.restore()
     }
     
     drawBgImage(geometry){
-        if(geometry.src != null){
+        if(geometry.url != null){
             const pt = {
-                x:30,
-                y:150
+                x:geometry.center.x*coordinate.ratio,
+                y:geometry.center.y*coordinate.ratio
             }
 
             this.context.save()
+            this.context.translate(pt.x+geometry.width*coordinate.ratio/2, pt.y+geometry.height*coordinate.ratio/2)
+            this.context.scale(geometry.scale,geometry.scale)
+
             if(geometry.image == null)
             {
                 var img = new Image()
                 img.src = geometry.src;
                 img.crossOrigin=""
                 img.onload = function () {
-                    this.context.drawImage(img, pt.x, pt.y, img.width, img.height)
+                    this.context.drawImage(img, -img.width*coordinate.ratio/2, -img.height*coordinate.ratio/2, img.width*coordinate.ratio, img.height*coordinate.ratio)
                 }.bind(this)
                 geometry.image = img;
             }
             else{
-                this.context.drawImage(geometry.image, pt.x, pt.y, geometry.image.width, geometry.image.height)
+                if(geometry.hasOwnProperty('width')){
+                    this.context.drawImage(geometry.image, -geometry.width*coordinate.ratio/2, -geometry.height*coordinate.ratio/2, geometry.width*coordinate.ratio, geometry.height*coordinate.ratio)
+                }
+                else{
+                    this.context.drawImage(geometry.image, -geometry.width*coordinate.ratio/2, -geometry.height*coordinate.ratio/2, geometry.image.width*coordinate.ratio, geometry.image.height*coordinate.ratio)
+                }   
             }
+
+            const focusItem = stateService.getFocusItem()
+            const selectItem = stateService.getSelectItem()
+            if (focusItem && focusItem.type == VectorType.BgImage) {
+                if (geometry.vectorId == focusItem.vectorId) {
+                    this.context.lineWidth = 2/geometry.scale;
+                    this.context.strokeStyle = Style.Select.Tag.strokeStyle
+                    this.context.fillStyle = Style.Select.Tag.fillStyle
+                    this.context.strokeRect( -geometry.width*coordinate.ratio/2, -geometry.height*coordinate.ratio/2, geometry.width, geometry.height)
+                }
+            }
+            else if (selectItem && selectItem.type == VectorType.BgImage) {
+                if (geometry.vectorId == selectItem.vectorId) {
+                    this.context.lineWidth = 2/geometry.scale;
+                    this.context.strokeStyle = Style.Select.Tag.strokeStyle
+                    this.context.fillStyle = Style.Select.Tag.fillStyle
+                    this.context.strokeRect( -geometry.width*coordinate.ratio/2, -geometry.height*coordinate.ratio/2, geometry.width, geometry.height)
+                }
+            }
+
+            // this.context.beginPath()
+            // this.context.arc(0,0, 2 * coordinate.ratio, 0, Math.PI * 2, true)
+            // this.context.stroke()
+            // this.context.fill()
+
             this.context.restore()
         }
     }
 
+    drawCustomImage(geometry){
+        if(geometry.url != null){
+            const pt = coordinate.getScreenXY(geometry.center)
+            this.context.save()
+
+            this.context.translate(pt.x, pt.y)
+            this.context.scale(geometry.scale,geometry.scale)
+            this.context.translate(-1*geometry.ratio*geometry.width/2*coordinate.ratio, -1*geometry.ratio*geometry.height/2*coordinate.ratio)
+            this.context.rotate((geometry.angle / 180) * Math.PI)
+            this.context.translate(geometry.ratio*geometry.width/2*coordinate.ratio, geometry.ratio*geometry.height/2*coordinate.ratio)
+            
+
+            if(geometry.image == null)
+            {
+                var img = new Image()
+                img.src = geometry.url;
+                img.crossOrigin=""
+                img.onload = function () {
+                    this.context.drawImage(img, -img.width*coordinate.ratio, -img.height*coordinate.ratio)
+                }.bind(this)
+                geometry.image = img;
+            }
+            else{
+                if(geometry.hasOwnProperty('width')){
+                    this.context.drawImage(geometry.image, -geometry.ratio * geometry.width*coordinate.ratio, -geometry.ratio * geometry.height*coordinate.ratio,geometry.ratio * geometry.width*coordinate.ratio, geometry.ratio * geometry.height*coordinate.ratio)
+                }
+                else{
+                    this.context.drawImage(geometry.image, -geometry.ratio * geometry.width*coordinate.ratio, -geometry.ratio * geometry.height*coordinate.ratio,geometry.ratio * geometry.width*coordinate.ratio, geometry.ratio * geometry.height*coordinate.ratio)
+                }
+                
+            }
+            const focusItem = stateService.getFocusItem()
+            const selectItem = stateService.getSelectItem()
+            if (focusItem && focusItem.type == VectorType.CustomImage) {
+                if (geometry.vectorId == focusItem.vectorId) {
+                    this.context.lineWidth = 2/geometry.scale;
+                    this.context.strokeStyle = Style.Select.Tag.strokeStyle
+                    this.context.fillStyle = Style.Select.Tag.fillStyle
+                    this.context.strokeRect( -geometry.ratio * geometry.width, -geometry.ratio * geometry.height, geometry.ratio * geometry.width, geometry.ratio * geometry.height)
+                }
+            }
+            else if (selectItem && selectItem.type == VectorType.CustomImage) {
+                if (geometry.vectorId == selectItem.vectorId) {
+                    this.context.lineWidth = 2/geometry.scale;
+                    this.context.strokeStyle = Style.Select.Tag.strokeStyle
+                    this.context.fillStyle = Style.Select.Tag.fillStyle
+                    this.context.strokeRect( -geometry.ratio * geometry.width, -geometry.ratio * geometry.height, geometry.ratio * geometry.width, geometry.ratio * geometry.height)
+                }
+            }
+
+            this.context.restore()
+
+            // this.context.save()
+            // if(geometry.points.length>1){
+                
+            //     this.context.strokeStyle = 'red'
+            //     this.context.lineWidth = 4
+            //     let p0 = coordinate.getScreenXY(geometry.points[0])
+            //     let p1 = coordinate.getScreenXY(geometry.points[1])
+            //     let p2 = coordinate.getScreenXY(geometry.points[2])
+            //     let p3 = coordinate.getScreenXY(geometry.points[3])
+            //     this.context.beginPath()
+            //     this.context.moveTo(p0.x,p0.y);
+            //     this.context.lineTo(p3.x,p3.y);
+            //     this.context.lineTo(p2.x,p2.y);
+            //     this.context.lineTo(p1.x,p1.y);
+            //     this.context.closePath()
+            //     this.context.stroke();
+            // }
+            // this.context.restore()
+            // this.context.beginPath()
+            // this.context.arc(pt.x-geometry.ratio * geometry.width/4*geometry.scale, pt.y-geometry.ratio * geometry.height/4*geometry.scale, 2 * coordinate.ratio, 0, Math.PI * 2, true)
+            // this.context.stroke()
+            // this.context.fill()
+            // this.context.restore()
+        }
+      }
+
     drawCompass(geometry){
         
         this.context.save()
@@ -869,17 +1009,13 @@ export default class Draw {
             }
         }
 
-        this.context.translate(geometry.center.x,geometry.center.y)
+        this.context.translate(geometry.center.x*coordinate.ratio,geometry.center.y*coordinate.ratio)
+        this.context.scale(coordinate.ratio,coordinate.ratio)
+        this.context.translate(16, 36)
         this.context.rotate((geometry.angle)/180 * Math.PI)
-        if(geometry.angle == 90){
-            this.context.translate( 32/2,  -52)
-        }
-        else if(geometry.angle == 180){
-            this.context.translate( -32,  -52)
-        }
-        else if(geometry.angle == 270){
-            this.context.translate( -52,  -32/2)
-        }
+        this.context.translate(-16, -36)
+        
+        //this.context.translate(18,26)
         this.context.lineWidth = 1
         this.context.miterLimit=4;
         this.context.font="15px ''";
@@ -958,6 +1094,13 @@ export default class Draw {
         this.context.fill();
         this.context.stroke();
         this.context.restore();
+
+        this.context.save()
+        this.context.font = 12*coordinate.ratio+`px Microsoft YaHei`
+        let value = '角度:'+geometry.angle + '°';
+        let fontWidth = this.context.measureText(value).width
+        this.context.fillText(value, (geometry.center.x*coordinate.ratio-fontWidth/5), (geometry.center.y+85)*coordinate.ratio)
+        this.context.restore()
     }
 
     setCanvasStyle(style) {
@@ -1024,7 +1167,7 @@ export default class Draw {
 
         this.context.save()
         this.context.strokeStyle = Style.Sign.strokeStyle
-        this.context.fillStyle = "black";
+        this.context.fillStyle = "rgba(0,0,0,1)";
 
         if (selectItem && selectItem.type == VectorType.Cigaret) {
             if (geometry.vectorId == selectItem.vectorId) {
@@ -1058,7 +1201,7 @@ export default class Draw {
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
 
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.miterLimit = 4
@@ -1132,7 +1275,7 @@ export default class Draw {
         this.context.strokeStyle="rgba(0,0,0,0)";
         this.context.miterLimit=4;
         this.context.font="15px ''";
-        this.context.fillStyle="#FF4D4F";
+        this.context.fillStyle="rgba(255,77,79,1)";
 
         if (selectItem && selectItem.type == VectorType.FirePoint) {
             if (geometry.vectorId == selectItem.vectorId) {
@@ -1165,7 +1308,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32, (geometry.getLen() * geometry.scale * coordinate.res) / 32)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32)
 
         this.context.beginPath();
@@ -1204,7 +1347,7 @@ export default class Draw {
         this.context.strokeStyle="rgba(0,0,0,0)";
         this.context.miterLimit=4;
         this.context.font="15px ''";
-        this.context.fillStyle="black";
+        this.context.fillStyle="rgba(0,0,0,1)";
 
         if (selectItem && selectItem.type == VectorType.LeftFootPrint) {
             if (geometry.vectorId == selectItem.vectorId) {
@@ -1237,7 +1380,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.beginPath();
@@ -1315,7 +1458,7 @@ export default class Draw {
         this.context.strokeStyle="rgba(0,0,0,0)";
         this.context.miterLimit=4;
         this.context.font="15px ''";
-        this.context.fillStyle="black";
+        this.context.fillStyle="rgba(0,0,0,1)";
 
         if (selectItem && selectItem.type == VectorType.RightFootPrint) {
             if (geometry.vectorId == selectItem.vectorId) {
@@ -1348,7 +1491,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.beginPath();
@@ -1425,7 +1568,7 @@ export default class Draw {
         this.context.save()
         this.context.strokeStyle="rgba(0,0,0,0)";
         this.context.miterLimit=4;
-        this.context.fillStyle="black";
+        this.context.fillStyle="rgba(0,0,0,1)";
         this.context.font="   15px ''";
 
         if (selectItem && selectItem.type == VectorType.LeftShoePrint) {
@@ -1459,7 +1602,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.beginPath();
@@ -1497,7 +1640,7 @@ export default class Draw {
         this.context.strokeStyle="rgba(0,0,0,0)";
         this.context.miterLimit=4;
         this.context.font="15px ''";
-        this.context.fillStyle="black";
+        this.context.fillStyle="rgba(0,0,0,1)";
 
         if (selectItem && selectItem.type == VectorType.RightShoePrint) {
             if (geometry.vectorId == selectItem.vectorId) {
@@ -1530,7 +1673,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.beginPath();
@@ -1568,7 +1711,7 @@ export default class Draw {
         this.context.strokeStyle="rgba(0,0,0,0)";
         this.context.miterLimit=4;
         this.context.font="15px ''";
-        this.context.fillStyle="black";
+        this.context.fillStyle="rgba(0,0,0,1)";
 
         if (selectItem && selectItem.type == VectorType.FingerPrint) {
             if (geometry.vectorId == selectItem.vectorId) {
@@ -1601,7 +1744,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.beginPath();
@@ -1755,7 +1898,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.beginPath();
@@ -1832,7 +1975,7 @@ export default class Draw {
         this.context.strokeStyle="rgba(0,0,0,0)";
         this.context.miterLimit=4;
         this.context.font="15px ''";
-        this.context.fillStyle="#FF4D4F";
+        this.context.fillStyle="rgba(255,77,79,1)";
 
         if (selectItem && selectItem.type == VectorType.BloodStain) {
             if (geometry.vectorId == selectItem.vectorId) {
@@ -1865,7 +2008,7 @@ export default class Draw {
         this.context.translate(center.x, center.y)
         this.context.rotate((geometry.angle / 180) * Math.PI)
         this.context.translate(pt.x - center.x, pt.y - center.y)
-        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
+        this.context.scale((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio, (geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom*coordinate.ratio)
         this.context.lineWidth = 1 / ((geometry.getLen() * geometry.scale * coordinate.res) / 32 * coordinate.zoom/Constant.defaultZoom)
 
         this.context.beginPath();

+ 54 - 42
src/view/case/draw/board/editCAD/Renderer/Render.js

@@ -52,8 +52,11 @@ export default class Render {
             case VectorType.Compass:
                 draw.drawCompass(vector)
                 return
+            case VectorType.CustomImage:
+                draw.drawCustomImage(vector)
+                return
         }
-
+        
         if (signService.isSign(vector.geoType)) {
             draw.drawSign(vector)
             return
@@ -138,7 +141,7 @@ export default class Render {
         let title = data.title
         this.drawGeometry(title)
 
-        let bgImage = data.image
+        let bgImage = data.bgImage
         this.drawGeometry(bgImage)
 
         let compass = data.compass
@@ -191,28 +194,44 @@ export default class Render {
             this.drawGeometry(icons[key])
         }
 
+        let customImages = data.customImages
+        for (let key in customImages) {
+            this.drawGeometry(customImages[key])
+        }
+        
+        draw.drawFrame()
         this.redrawElements()
     }
 
     autoRedrawForImg() {
         draw.clear()
-
-        // if (this.displayPanos) {
-        //     this.drawPanos(this.layer.panos[floorplanService.currentFloor])
-        // }
+        draw.drawBackGround()
         let data = floorplanService.getFloorData()
         if (!data) {
             return
         }
+
+        let title = data.title
+        this.drawGeometry(title)
+
+        let bgImage = data.bgImage
+        this.drawGeometry(bgImage)
+
+        let compass = data.compass
+        this.drawGeometry(compass)
+
         let walls = data.walls
         for (let key in walls) {
             this.drawGeometry(walls[key])
         }
 
-        // let points = data.points
-        // for (let key in points) {
-        //     this.drawGeometry(points[key])
-        // }
+        let points = data.points
+        for (let key in points) {
+            this.drawGeometry(points[key])
+        }
+
+        //draw.drawSpecialPoint()
+
         let signs = data.signs
         for (let key in signs) {
             this.drawGeometry(signs[key])
@@ -220,48 +239,41 @@ export default class Render {
 
         let tags = data.tags
         for (let key in tags) {
-            this.drawGeometry(tags[key], null, true)
+            this.drawGeometry(tags[key])
         }
 
-        //this.redrawElements()
-    }
-
-    //下载图片
-    //style表示风格
-    autoRedrawForDownLoadImg(styleType) {
-        draw.clear()
-
-        if (styleType == 'style-1') {
-            draw.drawBackGround('#FFFFFF')
-            this.redrawRooms(floorplanService.getCurrentFloor())
-        } else if (styleType == 'style-2') {
-            draw.drawBackGround('#000000')
-            this.redrawRooms(floorplanService.getCurrentFloor())
-        } else if (styleType == 'style-3') {
-            draw.drawBackGround('#FFFFFF')
-        } else if (styleType == 'style-4') {
-            draw.drawBackGround('#000000')
+        let tables = data.tables
+        for (let key in tables) {
+            this.drawGeometry(tables[key])
         }
 
-        let data = floorplanService.getFloorData()
-        if (!data) {
-            return
+        let rectangles = data.rectangles
+        for (let key in rectangles) {
+            this.drawGeometry(rectangles[key])
         }
-        let walls = data.walls
-        for (let key in walls) {
-            this.drawGeometry(walls[key], styleType)
+
+        let circles = data.circles
+        for (let key in circles) {
+            this.drawGeometry(circles[key])
         }
 
-        let signs = data.signs
-        for (let key in signs) {
-            this.drawGeometry(signs[key])
+        let arrows = data.arrows
+        for (let key in arrows) {
+            this.drawGeometry(arrows[key])
         }
-        let tags = data.tags
-        for (let key in tags) {
-            this.drawGeometry(tags[key], styleType)
+
+        let icons = data.icons
+        for (let key in icons) {
+            this.drawGeometry(icons[key])
         }
 
-        draw.drawCompass(styleType)
+        let customImages = data.customImages
+        for (let key in customImages) {
+            this.drawGeometry(customImages[key])
+        }
+        
+        draw.drawFrame()
+        this.redrawElements()
     }
 
     redrawCore() {

+ 1 - 0
src/view/case/draw/board/editCAD/Service/ArrowService.js

@@ -24,6 +24,7 @@ export default class ArrowService {
         arrow.vectorId = arrowInfo.vectorId
         arrow.startPoint = JSON.parse(JSON.stringify(arrowInfo.startPoint))
         arrow.endPoint = JSON.parse(JSON.stringify(arrowInfo.endPoint))
+        arrow.color = arrowInfo.color
     }
 
     deleteArrow(arrowId, floorNum) {

+ 36 - 0
src/view/case/draw/board/editCAD/Service/BgImageService.js

@@ -0,0 +1,36 @@
+import VectorType from '../enum/VectorType.js'
+import BgImage from '../Geometry/BgImage.js'
+import { mathUtil } from '../MathUtil.js'
+import { floorplanService } from './FloorplanService'
+import Constant from '../Constant'
+
+export default class BgImageService {
+    constructor() { 
+    }
+
+    async createBgImage(url,center,vectorId) {
+        const bgImage = new BgImage(url, center,vectorId)
+        if(bgImage.url){
+            const imageData = await floorplanService.loadImageData(bgImage.url)
+            bgImage.setImageData(imageData)
+        }
+
+        floorplanService.addBgImage(bgImage)
+        return bgImage
+    }
+
+    deleteBgImage() {
+        floorplanService.deleteBgImage()
+    }
+
+    setBgImageInfo(bgImageInfo) {
+        let bgImage = floorplanService.getBgImage(bgImageInfo.vectorId)
+        bgImage.vectorId = bgImageInfo.vectorId
+        bgImage.url = bgImageInfo.url
+        bgImage.scale = bgImageInfo.scale
+        bgImage.center = JSON.parse(JSON.stringify(bgImageInfo.center))
+    }
+}
+
+const bgImageService = new BgImageService()
+export { bgImageService }

+ 1 - 0
src/view/case/draw/board/editCAD/Service/CircleService.js

@@ -36,6 +36,7 @@ export default class CircleService {
         circle.radius = circleInfo.radius
         circle.center = JSON.parse(JSON.stringify(circleInfo.center))
         circle.points = JSON.parse(JSON.stringify(circleInfo.points))
+        circle.color = circleInfo.color
     }
 
     deleteCircle(circleId, floorNum) {

+ 42 - 0
src/view/case/draw/board/editCAD/Service/CustomImageService.js

@@ -0,0 +1,42 @@
+import VectorType from '../enum/VectorType.js'
+import CustomImage from '../Geometry/CustomImage.js'
+import { mathUtil } from '../MathUtil.js'
+import { floorplanService } from './FloorplanService'
+import Constant from '../Constant'
+
+export default class CustomImageService {
+    constructor() {
+        this.defaultPix = 60;  
+    }
+
+    async createCustomImage(url,center,vectorId) {
+        const customImage = new CustomImage(url, center,vectorId)
+        if(customImage.url){
+            const imageData = await floorplanService.loadImageData(customImage.url)
+            customImage.setImageData(imageData)
+            customImage.width = imageData.width;
+            customImage.height = imageData.height;
+            let maxPix = Math.max(imageData.width,imageData.height)
+            customImage.ratio = this.defaultPix/maxPix
+        }
+
+        floorplanService.addCustomImage(customImage)
+        return customImage
+    }
+
+    deleteCustomImage(customImageId, floorNum) {
+        floorplanService.deleteCustomImage(customImageId, floorNum)
+    }
+
+    setCustomImageInfo(customImageInfo) {
+        let customImage = floorplanService.getCustomImage(customImageInfo.vectorId)
+        customImage.vectorId = customImageInfo.vectorId
+        customImage.angle = customImageInfo.angle
+        customImage.url = customImageInfo.url
+        customImage.scale = customImageInfo.scale
+        customImage.center = JSON.parse(JSON.stringify(customImageInfo.center))
+    }
+}
+
+const customImageService = new CustomImageService()
+export { customImageService }

+ 54 - 34
src/view/case/draw/board/editCAD/Service/FloorplanService.js

@@ -284,6 +284,50 @@ export class FloorplanService {
         return floorplanData.floors[floor].tags
     }
 
+    addCustomImage(customImage, floor) {
+        if (floor == null || typeof floor == 'undefined') {
+            floor = this.currentFloor
+        }
+        floorplanData.floors[floor].customImages[customImage.vectorId] = customImage
+    }
+
+    getCustomImage(customImageId, floor) {
+        if (floor == null || typeof floor == 'undefined') {
+            floor = this.currentFloor
+        }
+        return floorplanData.floors[floor].customImages[customImageId]
+    }
+
+    deleteCustomImage(customImageId, floor) {
+        if (floor == null || typeof floor == 'undefined') {
+            floor = this.currentFloor
+        }
+        let customImage = this.getCustomImage(customImageId, floor)
+        customImage = null
+        delete floorplanData.floors[floor].customImages[customImageId]
+    }
+
+    getBgImage(bgImageId,floor) {
+        if (floor == null || typeof floor == 'undefined') {
+            floor = this.currentFloor
+        }
+        return floorplanData.floors[floor].bgImage
+    }
+
+    deleteBgImage(floor) {
+        if (floor == null || typeof floor == 'undefined') {
+            floor = this.currentFloor
+        }
+        floorplanData.floors[floor].bgImage = {}
+    }
+
+    addBgImage(bgImage, floor) {
+        if (floor == null || typeof floor == 'undefined') {
+            floor = this.currentFloor
+        }
+        floorplanData.floors[floor].bgImage = bgImage
+    }
+
     addTable(table, floor) {
         if (floor == null || typeof floor == 'undefined') {
             floor = this.currentFloor
@@ -378,6 +422,13 @@ export class FloorplanService {
         return floorplanData.floors[floor].signs
     }
 
+    getCustomImages(floor){
+        if (floor == null || typeof floor == 'undefined') {
+            floor = this.currentFloor
+        }
+        return floorplanData.floors[floor].customImages
+    }
+
     clear() {
         if (floorplanData.floors[this.currentFloor]) {
             floorplanData.floors[this.currentFloor].points = {}
@@ -388,8 +439,10 @@ export class FloorplanService {
             floorplanData.floors[this.currentFloor].tables = {}
             floorplanData.floors[this.currentFloor].cells = {}
             floorplanData.floors[this.currentFloor].signs = {}
+            floorplanData.floors[this.currentFloor].customImages = {}
             floorplanData.floors[this.currentFloor].arrows = {}
             floorplanData.floors[this.currentFloor].icons = []
+            floorplanData.floors[this.currentFloor].bgImage = {}
         }
     }
 
@@ -426,40 +479,7 @@ export class FloorplanService {
         }
         return floorplanData.floors[floor].title
     }
-
-    createBgImage(value,vectorId,floor){
-        if (floor == null || typeof floor == 'undefined') {
-            floor = this.currentFloor
-        }
-        const image = new BgImage(value,vectorId,floor)
-        return image
-    }
-
-    addBgImage(image, floor) {
-        if (floor == null || typeof floor == 'undefined') {
-            floor = this.currentFloor
-        }
-        floorplanData.floors[floor].image = image
-    }
-
-    async updateBgImage(src){
-        const floor = this.currentFloor
-        const img = floorplanData.floors[floor].image
-        img.setSrc(src)
-        if(img.src){
-            const imageData = await floorplanService.loadImageData(img.src)
-            img.setImageData(imageData)
-        }
-      
-    }
-
-    getBgImage(floor) {
-        if (floor == null || typeof floor == 'undefined') {
-            floor = this.currentFloor
-        }
-        return floorplanData.floors[floor].image
-    }
-
+    
     createCompass(value,vectorId,floor){
         if (floor == null || typeof floor == 'undefined') {
             floor = this.currentFloor

+ 1 - 0
src/view/case/draw/board/editCAD/Service/RectangleService.js

@@ -22,6 +22,7 @@ export default class RectangleService {
         let rectangle = floorplanService.getRectangle(rectangleInfo.vectorId)
         rectangle.vectorId = rectangleInfo.vectorId
         rectangle.angle = rectangleInfo.angle
+        rectangle.color = rectangleInfo.color
         rectangle.points = JSON.parse(JSON.stringify(rectangleInfo.points))
     }
 

+ 1 - 0
src/view/case/draw/board/editCAD/Service/SignService.js

@@ -73,6 +73,7 @@ export default class SignService {
         let sign = floorplanService.getSign(signInfo.vectorId)
         sign.vectorId = signInfo.vectorId
         sign.angle = signInfo.angle
+        sign.scale = signInfo.scale
         sign.center = JSON.parse(JSON.stringify(signInfo.center))
     }
     

+ 8 - 0
src/view/case/draw/board/editCAD/Service/StateService.js

@@ -64,6 +64,14 @@ export default class StateService {
             }
         } else if(type == VectorType.Arrow){
             this.selectItem.selectIndex = state
+        } else if (type == VectorType.CustomImage) {
+            if (state == SelectState.Select) {
+                this.selectItem.selectIndex = SelectState.All
+            }
+        } else if (type == VectorType.BgImage) {
+            if (state == SelectState.Select) {
+                this.selectItem.selectIndex = SelectState.All
+            }
         }
     }
 

+ 2 - 0
src/view/case/draw/board/editCAD/Service/TagService.js

@@ -20,6 +20,8 @@ export default class TagService {
         tag.center = JSON.parse(JSON.stringify(tagInfo.center))
         tag.points2d = JSON.parse(JSON.stringify(tagInfo.points2d))
         tag.value = tagInfo.value
+        tag.color = tagInfo.color
+        tag.fontSize = tagInfo.fontSize
     }
 
     deleteTag(tagId, floorNum) {

+ 1 - 0
src/view/case/draw/board/editCAD/Service/WallService.js

@@ -557,6 +557,7 @@ export class WallService {
         let wall = floorplanService.getWall(wallInfo.vectorId)
         wall.start = wallInfo.start
         wall.end = wallInfo.end
+        wall.color = wallInfo.color
         return wall
     }
 

+ 60 - 60
src/view/case/draw/board/editCAD/Style.js

@@ -1,6 +1,6 @@
 const Style = {
     Wall: {
-        strokeStyle: '#000000',
+        strokeStyle: 'rgba(0,0,0,1)',
         lineWidth: 2,
         error: {
             strokeStyle: 'rgba(255,0,0,0.5)',
@@ -8,191 +8,191 @@ const Style = {
         },
     },
     Point: {
-        strokeStyle: 'green',
-        fillStyle: 'rgb(0, 200, 175)',
+        strokeStyle: 'rgba(0,128,0, 1)',
+        fillStyle: 'rgba(0, 200, 175, 1)',
         radius: 4,
     },
     Tag: {
-        strokeStyle: 'rgb(0,0,0,1)',
-        fillStyle: 'rgb(0,0,0,1)',
+        strokeStyle: 'rgba(0,0,0,1)',
+        fillStyle: 'rgba(0,0,0,1)',
         strokeStyle_adding: 'rgba(243, 255, 0, 0.8)',
         fillStyle_adding: 'rgba(243, 255, 0, 0.8)',
         lineWidth: 1,
     },
     Sign: {
-        strokeStyle: 'rgb(0,0,0,1)',
+        strokeStyle: 'rgba(0,0,0,1)',
         fillStyle: 'rgba(0,0,0,0)',
         lineWidth: 1,
     },
     Title: {
-        strokeStyle: 'rgb(0,0,0,1)',
-        fillStyle: 'rgb(0,0,0,1)',
+        strokeStyle: 'rgba(0,0,0,1)',
+        fillStyle: 'rgba(0,0,0,1)',
         lineWidth: 1,
     },
     Compass: {
-        strokeStyle: 'rgb(0,0,0,1)',
+        strokeStyle: 'rgba(0,0,0,1)',
         fillStyle: 'rgba(0,0,0,0)',
         lineWidth: 1,
     },
     Rectangle:{
-        strokeStyle: '#000000',
+        strokeStyle: 'rgba(0,0,0,1)',
         lineWidth: 2,
     },
     Circle:{
-        strokeStyle: '#000000',
+        strokeStyle: 'rgba(0,0,0,1)',
         lineWidth: 2,
     },
     Icon:{
-        strokeStyle: '#000000',
+        strokeStyle: 'rgba(0,0,0,1)',
         lineWidth: 2,
     },
     Arrow: {
-        strokeStyle: '#000000',
+        strokeStyle: 'rgba(0,0,0,1)',
         lineWidth: 2,
     },
     Table:{
-        strokeStyle: '#000000',
+        strokeStyle: 'rgba(0,0,0,1)',
         lineWidth: 2,
     },
     Select: {
         Wall: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
         },
         Rectangle: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
             fillStyle: 'rgba(243, 255, 0, 0.5)',
         },
         Circle: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
             fillStyle: 'rgba(243, 255, 0, 0.5)',
         },
         Icon: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
             fillStyle: 'rgba(243, 255, 0, 0.5)',
         },
         Arrow: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
         },
         Tag: {
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Table: {
-            //strokeStyle: 'green',
+            //strokeStyle: 'rgba(0,128,0,1)',
             //fillStyle: 'rgba(243, 255, 0, 0.5)',
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Sign: {
             //strokeStyle: 'rgba(243, 255, 0, 0.8)',
             //fillStyle: 'rgba(243, 255, 0, 0.5)',
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Point: {
             radius: 4,
             lineWidth: 2,
-            fillStyle: 'rgb(0, 200, 175)',
-            strokeStyle: 'green',
+            fillStyle: 'rgba(0, 200, 175,1)',
+            strokeStyle: 'rgba(0,128,0,1)',
         },
         Title: {
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Compass: {
             //strokeStyle: 'rgba(243, 255, 0, 0.8)',
             //fillStyle: 'rgba(243, 255, 0, 0.5)',
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
     },
     Focus: {
         Wall: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
         },
         Rectangle: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
             fillStyle: 'rgba(243, 255, 0, 0.5)',
         },
         Circle: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
             fillStyle: 'rgba(243, 255, 0, 0.5)',
         },
         Icon: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
             fillStyle: 'rgba(243, 255, 0, 0.5)',
         },
         Arrow: {
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
         },
         Tag: {
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Table: {
-            //strokeStyle: 'green',
+            //strokeStyle: 'rgba(0,128,0,1)',
             //fillStyle: 'rgba(243, 255, 0, 0.5)',
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Sign: {
             //strokeStyle: 'rgba(243, 255, 0, 0.8)',
             //fillStyle: 'rgba(243, 255, 0, 0.5)',
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Point: {
             radius: 4,
             lineWidth: 2,
             fillStyle: 'rgba(245, 255, 0, 1)',
-            strokeStyle: 'green',
+            strokeStyle: 'rgba(0,128,0,1)',
         },
         Title: {
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
         Compass: {
             //strokeStyle: 'rgba(243, 255, 0, 0.8)',
             //fillStyle: 'rgba(243, 255, 0, 0.5)',
-            strokeStyle: '#00C8AF',
-            fillStyle: '#00C8AF',
+            strokeStyle: 'rgba(0,200,175,1)',
+            fillStyle: 'rgba(0,200,175,1)',
         },
     },
     Element: {
         StartAddWall: {
             radius: 4,
-            fillStyle: 'rgb(0, 200, 175)',
-            strokeStyle: 'green',
+            fillStyle: 'rgba(0, 200, 175, 1)',
+            strokeStyle: 'rgba(0,128,0,1)',
         },
         NewWall: {
             lineWidth: 2,
             strokeStyle: 'rgba(0,0,0,0.3)',
-            errorStrokeStyle: 'rgb(250,63,72,0.3)',
+            errorStrokeStyle: 'rgba(250,63,72,0.3)',
         },
         CheckLinesX: {
             lineWidth: 2,
-            strokeStyle: '#CED806',
+            strokeStyle: 'rgba(206,216,6,1)',
         },
         CheckLinesY: {
             lineWidth: 2,
-            strokeStyle: '#CED806',
+            strokeStyle: 'rgba(206,216,6,1)',
         },
         VCheckLinesX: {
             lineWidth: 2,
-            strokeStyle: '#CED806',
+            strokeStyle: 'rgba(206,216,6,1)',
             //strokeStyle: 'rgba(100,149,237,0.5)',
         },
         VCheckLinesY: {
             lineWidth: 2,
-            strokeStyle: '#CED806',
+            strokeStyle: 'rgba(206,216,6,1)',
             //strokeStyle: 'rgba(100,149,237,0.5)',
         },
     },
     // eslint-disable-next-line no-dupe-keys
     Title: {
         font: '24px Microsoft YaHei',
-        fillStyle: '#000000',
-        strokeStyle: '#000000',
+        fillStyle: 'rgba(0,0,0,1)',
+        strokeStyle: 'rgba(0,0,0,1)',
         textAlign: 'center',
         textBaseline: 'middle',
         miterLimit: 10,
@@ -202,8 +202,8 @@ const Style = {
     Font: {
         //font: '14px Microsoft YaHei',
         font: 'px Microsoft YaHei',
-        fillStyle: '#000000',
-        strokeStyle: '#000000',
+        fillStyle: 'rgba(0,0,0,1)',
+        strokeStyle: 'rgba(0,0,0,1)',
         textAlign: 'center',
         textBaseline: 'middle',
         miterLimit: 10,

+ 7 - 1
src/view/case/draw/board/editCAD/enum/HistoryEvents.js

@@ -39,7 +39,13 @@ const HistoryEvents = {
     DeleteSign: 'deleteSign',
     ModifySign: 'modifySign',
 
-    //ModifyAngle: 'modifyAngle',
+    AddCustomImage: 'addCustomImage',
+    DeleteCustomImage: 'deleteCustomImage',
+    ModifyCustomImage: 'modifyCustomImage',
+
+    AddBgImage: 'addBgImage',
+    DeleteBgImage: 'deleteBgImage',
+    ModifyBgImage: 'modifyBgImage',
 
     ModifyTitle: 'modifyTitle',
     ModifyImage: 'modifyImage',

+ 6 - 0
src/view/case/draw/board/editCAD/enum/LayerEvents.js

@@ -32,6 +32,12 @@ const LayerEvents = {
     AddTable: 'addTable',
     MoveTable: 'moveTable',
 
+    AddCustomImage: 'addCustomImage',
+    MoveCustomImage: 'moveCustomImage',
+
+    AddBgImage: 'addBgImage',
+    MoveBgImage: 'moveBgImage',
+
     AddSign: 'addSign',
     MoveSign: 'moveSign',
 

+ 2 - 0
src/view/case/draw/board/editCAD/enum/UIEvents.js

@@ -6,6 +6,8 @@ const UIEvents = {
     Circle:'Circle',
     Arrow:'Arrow',
     Icon:'Icon',
+    CustomImage:'CustomImage',
+    BgImage:'BgImage',
 
     Cigaret: 'Cigaret', 
     FirePoint: 'FirePoint', 

+ 1 - 0
src/view/case/draw/board/editCAD/enum/VectorType.js

@@ -8,6 +8,7 @@ const VectorType = {
     Rectangle:'Rectangle',
     Circle:'Circle',
     Arrow:'Arrow',
+    CustomImage:'CustomImage',
     Icon:'Icon',
     Cigaret: 'Cigaret',                  //烟头
     FirePoint: 'FirePoint',              //起火点

+ 6 - 1
src/view/case/draw/board/index.d.ts

@@ -20,11 +20,13 @@ import {
   compass,
   title,
   bgImage,
+  customImage,
 } from "./shape";
 
 type Metas = typeof fMetas;
 export type ShapeType =
   | typeof brokenLine
+  | typeof customImage
   | typeof text
   | typeof table
   | typeof rect
@@ -68,10 +70,12 @@ export type Board = {
     storeChange: undefined;
     selectShape: BoardShape | null;
     backDisabled: boolean;
+    exixtsBgImage: boolean
     forwardDisabled: boolean;
   }>;
+  clear(): void;
   unSelectShape(): void;
-  readyAddShape(type: MetaShapeType, onFinish?: () => void): () => void;
+  readyAddShape(type: MetaShapeType, data: any, onFinish?: () => void): () => void;
   back(): void;
   forward(): void;
   viewInit(): void;
@@ -105,6 +109,7 @@ export {
   cigarette,
   fireoint,
   footPrint,
+  customImage,
   shoePrint,
   fingerPrint,
   corpse,

+ 95 - 51
src/view/case/draw/board/index.js

@@ -1,5 +1,5 @@
 import mitt from "mitt";
-import { text, table, compass, title, bgImage } from "./shape";
+import { text, table, compass, title, bgImage, customImage } from "./shape";
 import Layer from "./editCAD/Layer";
 import { history } from "./editCAD/History/History.js";
 import { uploadFile } from "@/store/system";
@@ -19,60 +19,61 @@ export const create = async (store, canvas) => {
     baseMap: null,
   };
 
+  console.log(store)
+  setTimeout(() => {
+    console.log(!!store.floors[0].bgImage)
+    refs.bus.emit('exixtsBgImage', !!store.floors[0].bgImage)
+  }, 100)
+  
   const layer = new Layer();
   await layer.start(canvas, store);
+  const defaultData = {
+    color: "rgba(0,0,0,1)",
+    text: "",
+    fontSize: 12,
+    scale: 1,
+    rotate: 0,
+    content: [
+      { width: 160, height: 60, value: "", colIndex: 0, rowIndex: 0 },
+      { width: 160, height: 60, value: "", colIndex: 1, rowIndex: 0 },
+    ],
+  };
+
   layer.uiControl.bus.on("showAttribute", ({ type, value: data }) => {
+    data =
+      data && typeof data === "object"
+        ? Object.assign({ ...defaultData }, data)
+        : { ...defaultData };
+
+    const method = Object.fromEntries(
+      Object.keys(data).map((key) => [
+        `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`,
+        (value, save = true) => {
+          update({ [key]: value, save });
+        },
+      ])
+    );
+
     const shape = {
-      data: { type },
+      data: { type, ...data },
+      ...method,
       delete: () => {
         layer.uiControl.clearUI();
         layer.uiControl.setAttributes(type, "delete");
+        if (type === bgImage) {
+          refs.bus.emit('exixtsBgImage', false)
+        }
       },
     };
     const update = (newData) => {
-      layer.uiControl.setAttributes(type, "update", newData);
+      layer.uiControl.setAttributes(type, "update", { ...newData, version: 2 });
     };
-    switch (type) {
-      case table: {
-        data = data || [
-          { width: 160, height: 60, value: "", colIndex: 0, rowIndex: 0 },
-          { width: 160, height: 60, value: "", colIndex: 1, rowIndex: 0 },
-        ];
-        shape.data.content = data;
-        shape.setContent = (newData) => {
-          shape.data.content = newData;
-          update(newData);
-        };
-        break;
-      }
-      case title:
-      case text: {
-        console.log(data);
-        data = data || "";
-        shape.data.text = data;
-        shape.setText = (newData) => {
-          shape.data.text = newData;
-          update(newData);
-        };
-        break;
-      }
-      case compass: {
-        data = data || 0;
-        console.log(data);
-        shape.data.rotate = data;
-        shape.setRotate = (newData) => {
-          shape.data.rotate = newData;
-          update(newData);
-        };
-      }
-    }
     refs.bus.emit("selectShape", shape);
   });
   layer.uiControl.bus.on("hideAttribute", () => {
     refs.bus.emit("selectShape", null);
   });
   history.bus.on("undoAvailable", (availabe) => {
-    console.log("0.0.0.0", !availabe);
     refs.bus.emit("backDisabled", !availabe);
   });
   history.bus.on("redoAvailable", (availabe) =>
@@ -85,6 +86,7 @@ export const create = async (store, canvas) => {
     // history.bus.emit('redoAvailable', true)
   }, 100);
 
+  let run = false;
   const board = {
     bus: refs.bus,
     el: canvas,
@@ -94,7 +96,7 @@ export const create = async (store, canvas) => {
       const shapes = [];
 
       if (floor) {
-        const bgImage = floor.image.src;
+        const bgImage = floor.bgImage?.url;
         if (bgImage && bgImage.includes("blob:")) {
           const url = await fetch(bgImage)
             .then((res) => res.blob())
@@ -102,7 +104,7 @@ export const create = async (store, canvas) => {
               uploadFile(new File([blob], (store.id || "image") + ".png"))
             );
 
-          floor.image.src = url;
+          floor.bgImage.url = url;
           console.log(url);
         }
         shapes.push({ type: title, text: floor.title.value });
@@ -130,34 +132,76 @@ export const create = async (store, canvas) => {
     initHistory() {
       history.init();
     },
-    readyAddShape(shapeType, onFine) {
+    readyAddShape(shapeType, data, onFine) {
       layer.uiControl.selectUI = shapeType;
-      layer.uiControl.updateEventNameForSelectUI();
-      const finePack = () => {
-        layer.uiControl.bus.off("hideUI", finePack);
+      if (customImage === shapeType) {
+        layer.uiControl.setAttributes(shapeType, "upload", {
+          url: data,
+          version: 2,
+        });
         onFine();
-      };
-      layer.uiControl.bus.on("hideUI", finePack);
+      } else {
+        layer.uiControl.updateEventNameForSelectUI();
+        const finePack = () => {
+          layer.uiControl.bus.off("hideUI", finePack);
+          onFine();
+        };
+        layer.uiControl.bus.on("hideUI", finePack);
+      }
     },
     back() {
-      history.handleUndo();
+      if (run) return;
+      run = true;
+      console.log("撤销");
+      history.handleUndo().then(() => {
+        console.log("撤销完成");
+        run = false;
+      });
     },
     forward() {
-      history.handleRedo();
+      if (run) return;
+      run = true;
+      console.log("回复");
+      history.handleRedo().then(() => {
+        console.log("回复完成");
+        run = false;
+      });
     },
     setImage(url) {
-      layer.uiControl.setAttributes(bgImage, "update", url);
+      refs.bus.emit('exixtsBgImage', true)
+      layer.uiControl.setAttributes(bgImage, "upload", { url, scale: 1 });
+    },
+    clear() {
+      history.clear()
     },
     export() {
+      return new Promise((resolve) => {
+        layer.uiControl.exportImg(canvas, "cover.jpg", (blob) => {
+              // const file = new File([blob], `asdasd.jpg`)
+              // window.open(URL.createObjectURL(file))
+              // console.log(blob)
+              resolve(blob)
+        });
+      });
       const $canvas = document.createElement("canvas");
       $canvas.width = canvas.width;
       $canvas.height = canvas.height;
 
       const cctx = $canvas.getContext("2d");
       cctx.rect(0, 0, $canvas.width, $canvas.height);
-      cctx.fillStyle = "#fff";
+      cctx.fillStyle = "rgba(255,255,255,1)";
       cctx.fill();
-      cctx.drawImage(canvas, 0, 0, $canvas.width, $canvas.height);
+      cctx.drawImage(
+        canvas,
+        0,
+        0,
+        $canvas.width,
+        $canvas.height,
+        0,
+        0,
+        canvas.width,
+        canvas.height
+      );
 
       return new Promise((resolve) => {
         // resolve(layer.uiControl.menu_screenShot())

+ 1 - 0
src/view/case/draw/board/shape.js

@@ -35,6 +35,7 @@ export const theBlood = "BloodStain";
 export const title = "Title";
 export const bgImage = "BgImage";
 export const compass = "Compass";
+export const customImage = "CustomImage";
 
 export const labels = [brokenLine, text, table, rect, circular, arrow, icon];
 

+ 16 - 2
src/view/case/draw/board/useBoard.ts

@@ -1,4 +1,4 @@
-import boardFactory, { BoardShape, MetaShapeType } from "./";
+import boardFactory, { BoardShape, MetaShapeType, bgImage } from "./";
 import { getCaseInfo } from "@/store/case";
 import { BoardData, getCaseFileImageInfo } from "@/store/caseFile";
 import { Board } from "./";
@@ -90,17 +90,21 @@ export type BoardProps = {
 };
 export type BoardState = {
   backDisabled: boolean;
+  exixtsBgImage: boolean
   forwardDisabled: boolean;
   selectShape: BoardShape | null;
   addShape: MetaShapeType | null;
+  addData: any;
 };
 export const useBoard = (props: Ref<BoardProps | null>) => {
   const board = ref<Board>();
   const state = ref<BoardState>({
     backDisabled: true,
+    exixtsBgImage: false,
     forwardDisabled: true,
     selectShape: null,
     addShape: null,
+    addData: null,
   });
 
   watchEffect(async (onCleanup) => {
@@ -115,6 +119,7 @@ export const useBoard = (props: Ref<BoardProps | null>) => {
       props.value.type
     );
     const boardRaw = (board.value = await boardFactory(store, props.value.dom));
+    state.value.exixtsBgImage = !!(store as any).floors[0].bgImage
     onCleanup(() => {
       boardRaw && boardRaw.destroy();
     });
@@ -152,6 +157,10 @@ export const useBoard = (props: Ref<BoardProps | null>) => {
     const boardRaw = board.value;
 
     console.log("开启监听");
+    boardRaw.bus.on('exixtsBgImage', b => {
+      console.error("a?", b)
+      state.value.exixtsBgImage = b
+    })
     boardRaw.bus.on("backDisabled", (dis) => (state.value.backDisabled = dis));
     boardRaw.bus.on(
       "forwardDisabled",
@@ -174,11 +183,16 @@ export const useBoard = (props: Ref<BoardProps | null>) => {
     if (board.value && state.value.addShape) {
       const cleaup = board.value.readyAddShape(
         state.value.addShape,
-        () => (state.value.addShape = null)
+        state.value.addData || null,
+        () => {
+          state.value.addShape = null;
+          state.value.addData = null;
+        }
       );
       const keyupHandler = (ev: KeyboardEvent) => {
         if (ev.key === "Escape") {
           state.value.addShape = null;
+          state.value.addData = null;
           cleaup();
         }
       };

+ 30 - 0
src/view/case/draw/edit-shape/bgImage.vue

@@ -0,0 +1,30 @@
+<template>
+  <el-form-item label="缩放:">
+    <el-slider
+      style="width: 100px"
+      v-model="scale"
+      :min="0.5"
+      :step="0.01"
+      :max="2"
+      @change="shape.setScale(scale, true)"
+    />
+  </el-form-item>
+  <Del @delete="$emit('delete')" />
+</template>
+<script setup lang="ts">
+import { ref, watchEffect } from "vue";
+import { BoardShape } from "../board";
+import Del from "./delete.vue";
+
+const props = defineProps<{ shape: BoardShape }>();
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "blur"): void;
+  (e: "inputIng", ing: boolean): void;
+}>();
+
+const scale = ref<number>(props.shape.data.scale);
+watchEffect(() => {
+  props.shape.setScale(scale.value, false);
+});
+</script>

+ 43 - 0
src/view/case/draw/edit-shape/compass.vue

@@ -0,0 +1,43 @@
+<template>
+  <el-form-item label="角度:">
+    <div class="last-layout">
+      <el-input-number
+        :model-value="value || 0"
+        placeholder="0"
+        step-strictly
+        :step="1"
+        @update:model-value="val => setRotate(val as number, false)"
+        :min="0"
+        :max="360"
+      >
+      </el-input-number>
+      <span class="last-t">°</span>
+    </div>
+  </el-form-item>
+</template>
+<script setup lang="ts">
+import { ref } from "vue";
+import { BoardShape } from "../board";
+
+const props = defineProps<{ shape: BoardShape }>();
+const emit = defineEmits<{ (e: "blur"): void }>();
+const value = ref<number>(props.shape.data.rotate);
+const setRotate = (edg: number, save: boolean) => {
+  edg = parseInt((edg || 0).toString());
+  if (edg !== value.value) {
+    value.value = edg;
+    props.shape.setRotate(edg, true);
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.last-layout {
+  position: relative;
+}
+.last-t {
+  z-index: 1;
+  position: absolute;
+  transform: translateX(-40px);
+}
+</style>

+ 5 - 0
src/view/case/draw/edit-shape/delete.vue

@@ -0,0 +1,5 @@
+<template>
+  <el-form-item label="删除:">
+    <el-button type="primary" @click="$emit('delete')">删除</el-button>
+  </el-form-item>
+</template>

+ 43 - 0
src/view/case/draw/edit-shape/image.vue

@@ -0,0 +1,43 @@
+<template>
+  <el-form-item label="旋转:">
+    <el-slider
+      style="width: 100px"
+      v-model="rotate"
+      :min="0"
+      :max="360"
+      @change="shape.setRotate(rotate, true)"
+    />
+  </el-form-item>
+  <el-form-item label="缩放:">
+    <el-slider
+      style="width: 100px"
+      v-model="scale"
+      :min="0.5"
+      :step="0.01"
+      :max="5"
+      @change="shape.setScale(scale, true)"
+    />
+  </el-form-item>
+  <Del @delete="$emit('delete')" />
+</template>
+<script setup lang="ts">
+import { ref, watchEffect } from "vue";
+import { BoardShape } from "../board";
+import Del from "./delete.vue";
+
+const props = defineProps<{ shape: BoardShape }>();
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "blur"): void;
+  (e: "inputIng", ing: boolean): void;
+}>();
+
+const rotate = ref<number>(props.shape.data.rotate);
+const scale = ref<number>(props.shape.data.scale);
+watchEffect(() => {
+  props.shape.setRotate(rotate.value, false);
+});
+watchEffect(() => {
+  props.shape.setScale(scale.value, false);
+});
+</script>

+ 25 - 0
src/view/case/draw/edit-shape/index.ts

@@ -0,0 +1,25 @@
+import { markRaw, reactive } from "vue";
+import { images, customImage } from "../board/useBoard";
+
+const componentLoads = import.meta.glob("./*.vue");
+
+export const components: { [key in string]: any } = reactive({});
+
+const map = {
+  label: ["Circle", "Rectangle", "Wall", "Arrow"],
+  image: [...images, customImage],
+  delete: ["Icon"],
+};
+
+Object.entries(componentLoads).map(([name, fn]) => {
+  name = name.substring(2, name.lastIndexOf(".vue"));
+  fn().then((mudule) => {
+    const component = (mudule as any).default;
+    markRaw(component as any);
+    const keys = [name, ...(map[name] ? map[name] : [])];
+    keys.forEach((name) => {
+      components[name] = component;
+      components[name.slice(0, 1).toUpperCase() + name.slice(1)] = component;
+    });
+  });
+});

+ 27 - 0
src/view/case/draw/edit-shape/label.vue

@@ -0,0 +1,27 @@
+<template>
+  <el-form-item label="颜色:">
+    <el-color-picker
+      v-model="value"
+      color-format="rgba"
+      show-alpha
+      :predefine="predefineColors"
+    />
+  </el-form-item>
+  <slot />
+  <Del @delete="$emit('delete')" />
+</template>
+<script setup lang="ts">
+import { ref, watchEffect } from "vue";
+import { BoardShape } from "../board";
+import { ElColorPicker } from "element-plus";
+import Del from "./delete.vue";
+import { predefineColors } from "./preset";
+
+const props = defineProps<{ shape: BoardShape }>();
+const emit = defineEmits<{ (e: "blur"): void; (e: "delete"): void }>();
+const value = ref<string>(props.shape.data.color || "rgba(0,0,0,1)");
+
+watchEffect(() => {
+  props.shape.setColor(value.value);
+});
+</script>

+ 9 - 0
src/view/case/draw/edit-shape/preset.ts

@@ -0,0 +1,9 @@
+export const predefineColors = [
+  "#ff0f00",
+  "#ffbe00",
+  "#1a9bff",
+  "#1aad19",
+  "#000000",
+  "#ffffff",
+  "#666666",
+];

+ 31 - 0
src/view/case/draw/edit-shape/table.vue

@@ -0,0 +1,31 @@
+<template>
+  <el-form-item label="内容:">
+    <el-button type="primary" @click="() => editTable()">编辑</el-button>
+  </el-form-item>
+
+  <Del @delete="$emit('delete')" />
+</template>
+<script setup lang="ts">
+import { BoardShape } from "../board";
+import Del from "./delete.vue";
+import { editEshapeTable } from "@/view/case/quisk";
+
+const props = defineProps<{ shape: BoardShape }>();
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "blur"): void;
+  (e: "inputIng", ing: boolean): void;
+}>();
+
+const editTable = async (track = false) => {
+  emit("inputIng", true);
+  const data = await editEshapeTable({ content: props.shape.data.content, track });
+  if (data) {
+    props.shape.setContent(data);
+  }
+  emit("blur");
+  emit("inputIng", false);
+};
+
+props.shape.autoSet && editTable(true);
+</script>

+ 50 - 0
src/view/case/draw/edit-shape/tag.vue

@@ -0,0 +1,50 @@
+<template>
+  <el-form-item label="内容:">
+    <el-input
+      type="textarea"
+      @focus="$emit('inputIng', true)"
+      @blur="$emit('inputIng', false)"
+      :maxlength="500"
+      style="width: 200px"
+      v-model="text"
+    >
+      <template #append>
+        <el-button type="primary" @click="$emit('blur')">确定</el-button>
+      </template>
+    </el-input>
+  </el-form-item>
+
+  <Label :shape="shape" @blur="emit('blur')" @delete="$emit('delete')">
+    <template v-slot>
+      <el-form-item label="字号:">
+        <el-select v-model="fontSize" placeholder="选择字号" style="width: 200px">
+          <el-option v-for="item in fontSizeOptions" v-bind="item" :key="item.value" />
+        </el-select>
+      </el-form-item>
+    </template>
+  </Label>
+</template>
+<script setup lang="ts">
+import { ref, watchEffect } from "vue";
+import { BoardShape } from "../board";
+import Label from "./label.vue";
+import Del from "./delete.vue";
+
+const props = defineProps<{ shape: BoardShape }>();
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "blur"): void;
+  (e: "inputIng", ing: boolean): void;
+}>();
+
+const fontSizeRange = [8, 30];
+const fontSizeOptions: { value: number; label: string }[] = [];
+for (let i = fontSizeRange[0]; i <= fontSizeRange[1]; i++) {
+  fontSizeOptions.push({ value: i, label: i.toString() });
+}
+
+const text = ref(props.shape.data.text);
+const fontSize = ref(props.shape.data.fontSize);
+watchEffect(() => props.shape.setFontSize(fontSize.value));
+watchEffect(() => props.shape.setText(text.value));
+</script>

+ 29 - 0
src/view/case/draw/edit-shape/title.vue

@@ -0,0 +1,29 @@
+<template>
+  <el-form-item label="内容:">
+    <el-input
+      @focus="$emit('inputIng', true)"
+      @blur="$emit('inputIng', false)"
+      :maxlength="50"
+      style="width: 220px"
+      v-model="value"
+    >
+      <template #append>
+        <el-button type="primary" @click="$emit('blur')">确定</el-button>
+      </template>
+    </el-input>
+  </el-form-item>
+</template>
+<script setup lang="ts">
+import { ref, watchEffect } from "vue";
+import { BoardShape } from "../board";
+
+const props = defineProps<{ shape: BoardShape }>();
+const emit = defineEmits<{
+  (e: "delete"): void;
+  (e: "blur"): void;
+  (e: "inputIng", ing: boolean): void;
+}>();
+
+const value = ref(props.shape.data.text);
+watchEffect(() => props.shape.setText(value.value));
+</script>

+ 190 - 52
src/view/case/draw/editEshapeTable.vue

@@ -1,42 +1,74 @@
 <template>
-  <table class="content-table" ref="tableRef">
-    <tr class="header">
-      <th v-for="(col, i) in rows[0]">列 {{ i + 1 }}</th>
-      <th>删除</th>
-    </tr>
-    <tr v-for="(row, rowIndex) in rows" class="row" :key="rowIndex">
-      <td
-        v-for="(col, colIndex) in row"
-        class="col"
-        :key="colIndex"
-        @click="inputPos = [rowIndex, colIndex]"
-      >
-        <input
-          :ref="(dom: any) => inputRef = dom"
-          @blur="inputPos = null"
-          v-if="inputPos && rowIndex === inputPos[0] && colIndex === inputPos[1]"
-          :value="col"
-          @change="ev => setColValue(rowIndex, colIndex, (ev.target as any).value)"
-        />
-        <span v-else>{{ col }}</span>
-      </td>
-      <td>
-        <el-button type="primary" plain size="small" @click="delRow(rowIndex)">
-          <el-icon><Minus /></el-icon>
-        </el-button>
-      </td>
-    </tr>
-  </table>
+  <div class="content-table-layer">
+    <table class="content-table" ref="tableRef">
+      <tr class="header">
+        <th
+          class="sel-th"
+          v-for="(col, colIndex) in rows[0]"
+          @click="select = { col: colIndex }"
+          :class="{ active: colIndex === select.col }"
+        >
+          <el-button type="primary" plain size="small" @click.stop="delColumn(colIndex)">
+            <el-icon><Minus /></el-icon>
+          </el-button>
+        </th>
+        <td></td>
+      </tr>
+      <tr v-for="(row, rowIndex) in rows" class="row" :key="rowIndex">
+        <td
+          v-for="(col, colIndex) in row"
+          class="col"
+          :key="colIndex"
+          @click="inputPos = [rowIndex, colIndex]"
+        >
+          <input
+            :ref="(dom: any) => inputRef = dom"
+            @blur="inputPos = null"
+            v-if="inputPos && rowIndex === inputPos[0] && colIndex === inputPos[1]"
+            :value="col"
+            :style="getBound(rowIndex, colIndex)"
+            @change="ev => setColValue(rowIndex, colIndex, (ev.target as any).value)"
+          />
+          <span v-else :style="getBound(rowIndex, colIndex)">{{ col || "" }}&nbsp;</span>
+        </td>
+        <th
+          class="sel-th del-col"
+          @click="select = { row: rowIndex }"
+          :class="{ active: rowIndex === select.row }"
+        >
+          <div style="width: 100px"></div>
+          <el-button type="primary" plain size="small" @click="delRow(rowIndex)">
+            <el-icon><Minus /></el-icon>
+          </el-button>
+        </th>
+      </tr>
+    </table>
+  </div>
+  <div class="setter" v-if="selectValue">
+    <el-form-item :label="selectValue.label">
+      <el-input-number
+        :modelValue="selectValue.value"
+        @update:modelValue="changeSelectValue"
+        :min="24"
+        :max="1000"
+        size="small"
+        controls-position="right"
+      />
+    </el-form-item>
+  </div>
   <div class="add-row-layout">
     <el-button type="primary" @click="addRow">
       <el-icon><Plus /></el-icon> 行
     </el-button>
+    <el-button type="primary" @click="addCloumn">
+      <el-icon><Plus /></el-icon> 列
+    </el-button>
   </div>
 </template>
 
 <script setup lang="ts">
 import { ElMessage } from "element-plus";
-import { computed, ref, watchEffect } from "vue";
+import { computed, nextTick, ref, watchEffect } from "vue";
 import { QuiskExpose } from "@/helper/mount";
 
 export type EshapeTableContent = {
@@ -51,6 +83,53 @@ const props = defineProps<{ content: EshapeTableContent; track?: boolean }>();
 const bindContent = ref(props.content.map((item) => ({ ...item })));
 const inputPos = ref<[number, number] | null>(null);
 const inputRef = ref<HTMLInputElement>();
+const select = ref<{ col?: number; row?: number }>({});
+
+const selectValue = computed(() => {
+  if (Object.keys(select.value).length === 0) return;
+  const isCol = "col" in select.value;
+  const item = bindContent.value.find((item) =>
+    isCol ? item.colIndex === select.value.col : item.rowIndex === select.value.row
+  )!;
+  if (item) {
+    return isCol
+      ? { label: "列宽", value: item.width }
+      : { label: "行高", value: item.height };
+  }
+});
+
+const changeSelectValue = (val: number) => {
+  const isCol = "col" in select.value;
+  bindContent.value
+    .filter((item) =>
+      isCol ? item.colIndex === select.value.col : item.rowIndex === select.value.row
+    )
+    .forEach((item) => {
+      if (isCol) {
+        item.width = val;
+      } else {
+        item.height = val;
+      }
+    });
+};
+
+const getBound = (rowIndex, colIndex) => {
+  const item = bindContent.value.find(
+    (item) => item.rowIndex === rowIndex && item.colIndex === colIndex
+  );
+  const bound = [170, 25 - 2];
+  if (item && item.width && item.height) {
+    bound[0] = item.width;
+    bound[1] = item.height;
+  } else if (colIndex === 0) {
+    bound[0] = 90;
+  }
+
+  return {
+    width: bound[0] + "px",
+    "min-height": bound[1] + "px",
+  };
+};
 
 watchEffect(
   () => {
@@ -77,6 +156,24 @@ const delRow = (rowIndex: number) => {
   }
 };
 
+const delColumn = (columnIndex: number) => {
+  if (rows.value[0].length === 1) {
+    ElMessage.error("表格最少需要保留一列!");
+  } else {
+    console.log(columnIndex, bindContent.value);
+    bindContent.value = bindContent.value
+      .filter((item) => item.colIndex !== columnIndex)
+      .map((item) => {
+        if (item.colIndex > columnIndex) {
+          return { ...item, colIndex: item.colIndex - 1 };
+        } else {
+          return item;
+        }
+      });
+    console.log(columnIndex, bindContent.value);
+  }
+};
+
 const addRow = () => {
   const colSize = rows.value[0].length;
   const rowSize = rows.value.length;
@@ -89,6 +186,30 @@ const addRow = () => {
       value: "",
     });
   }
+  nextTick(updateTableContent);
+};
+const addCloumn = () => {
+  const colSize = rows.value[0].length;
+  const rowSize = rows.value.length;
+  for (let i = 0; i < rowSize; i++) {
+    bindContent.value.push({
+      width: 0,
+      height: 0,
+      colIndex: colSize,
+      rowIndex: i,
+      value: "",
+    });
+  }
+  nextTick(updateTableContent);
+  // for (let i = 0; i < colSize; i++) {
+  //   bindContent.value.push({
+  //     width: 0,
+  //     height: 0,
+  //     colIndex: i,
+  //     rowIndex: rowSize,
+  //     value: "",
+  //   });
+  // }
 };
 
 const setColValue = (rowIndex: number, colIndex: number, val: string) => {
@@ -108,22 +229,26 @@ const rows = computed(() => {
 });
 
 const tableRef = ref<HTMLTableElement>();
+const updateTableContent = () => {
+  const dom = tableRef.value!;
+  const rows = Array.from(dom.querySelectorAll(".row"));
+  for (let i = 0; i < rows.length; i++) {
+    const cols = Array.from(rows[i].querySelectorAll(".col"));
+
+    for (let j = 0; j < cols.length; j++) {
+      const col = cols[j] as HTMLElement;
+      const item = bindContent.value.find(
+        (item) => item.rowIndex === i && item.colIndex === j
+      )!;
+      item.width = col.offsetWidth;
+      item.height = col.offsetHeight;
+    }
+  }
+};
+
 defineExpose<QuiskExpose>({
   submit() {
-    const dom = tableRef.value!;
-    const rows = Array.from(dom.querySelectorAll(".row"));
-    for (let i = 0; i < rows.length; i++) {
-      const cols = Array.from(rows[i].querySelectorAll(".col"));
-
-      for (let j = 0; j < cols.length; j++) {
-        const col = cols[j] as HTMLElement;
-        const item = bindContent.value.find(
-          (item) => item.rowIndex === i && item.colIndex === j
-        )!;
-        item.width = col.offsetWidth;
-        item.height = col.offsetHeight;
-      }
-    }
+    updateTableContent();
     return bindContent.value;
   },
 });
@@ -131,11 +256,11 @@ defineExpose<QuiskExpose>({
 
 <style lang="scss" scoped>
 .content-table {
-  width: 100%;
-  height: 100%;
+  margin: 0 auto;
   border-collapse: collapse;
   --border-color: #f0f2f5;
   margin-bottom: 10px;
+  table-layout: fixed;
 
   th {
     background: #fafafb;
@@ -158,13 +283,6 @@ defineExpose<QuiskExpose>({
     text-align: center;
   }
 
-  .col:nth-child(1) {
-    width: 90px;
-  }
-  .col:nth-child(2) {
-    width: 170px;
-  }
-
   .col {
     cursor: pointer;
 
@@ -183,13 +301,33 @@ defineExpose<QuiskExpose>({
       text-align: center;
       line-height: 24px;
     }
+    span {
+      word-break: break-all;
+    }
   }
   .col:hover {
     background-color: rgba(133, 194, 255, 0.1);
   }
+  .sel-th {
+    cursor: pointer;
+  }
+  .active {
+    box-shadow: 0 0 0 1px var(--el-color-primary) inset;
+  }
 }
 
 .add-row-layout {
   text-align: center;
 }
+
+.content-table-layer {
+  overflow: auto;
+  text-align: center;
+}
+.setter {
+  text-align: center;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
 </style>

+ 45 - 78
src/view/case/draw/eshape.vue

@@ -1,104 +1,50 @@
 <template>
-  <div class="def-shape-edit">
+  <div class="def-shape-edit" v-if="editComponent">
     <el-icon class="def-close-shape-edit" @click="emit('update:shape', null)">
       <Close />
     </el-icon>
-    <el-form inline>
-      <el-form-item label="内容:" v-if="textType.includes(type)">
-        <el-input
-          @focus="inputIng = true"
-          @blur="inputIng = false"
-          :maxlength="type === 'Tag' ? 500 : 50"
-          style="width: 220px"
-          v-model="meta!.value"
-          @change="meta!.update"
-        >
-          <template #append>
-            <el-button type="primary" @click="meta!.update(true)">确定</el-button>
-          </template>
-        </el-input>
-      </el-form-item>
-
-      <el-form-item label="内容:" v-if="ContentType.includes(type)">
-        <el-button type="primary" @click="() => editTable()">编辑</el-button>
-      </el-form-item>
-
-      <el-form-item label="方向:" v-if="CompassType.includes(type)">
-        <el-button type="primary" @click="meta!.update()"> 旋转 </el-button>
-      </el-form-item>
-
-      <el-form-item label="删除:" v-if="!nDelType.includes(type)">
-        <el-button type="primary" @click="delHandler">删除</el-button>
-      </el-form-item>
+    <el-form class="def-shape-edit-form" label-width="60px">
+      <component
+        :key="props.shape"
+        v-if="editComponent"
+        :is="editComponent"
+        :shape="props.shape"
+        @delete="delHandler"
+        @inputIng="(bol) => (inputIng = bol)"
+        @blur="emit('update:shape', null)"
+      />
     </el-form>
   </div>
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, onUnmounted, reactive, ref, watchEffect } from "vue";
+import { computed, onMounted, onUnmounted, ref } from "vue";
 import { BoardShape, compass, title } from "./board";
-import { editEshapeTable } from "@/view/case/quisk";
+import { components } from "./edit-shape";
 
 const props = defineProps<{ shape: BoardShape }>();
 const emit = defineEmits<{
   (e: "update:shape", value: BoardShape | null): void;
 }>();
-
+const editComponent = computed(() => {
+  const type = props.shape.data.type;
+  console.log(type);
+  if (type && components[type]) {
+    return components[type];
+  }
+});
 const inputIng = ref(false);
-const type = computed(() => props.shape.data.type);
-const textType = ["Tag", title];
-const CompassType = [compass];
-const nDelType = [title, compass];
-const ContentType = ["Table"];
 
 const delHandler = () => {
   props.shape.delete();
   emit("update:shape", null);
 };
-
-const editTable = async (track = false) => {
-  inputIng.value = true;
-  const data = await editEshapeTable({ content: props.shape.data.content, track });
-  if (data) {
-    props.shape.setContent(data);
-  }
-  emit("update:shape", null);
-  inputIng.value = false;
-};
-
-if (props.shape.autoSet && ContentType.includes(type.value)) {
-  editTable(true);
-}
-
-const meta = computed(() => {
-  if (textType.includes(type.value)) {
-    const data = reactive({
-      value: props.shape.data.text,
-      update: (quit?: boolean) => {
-        props.shape.setText(data.value);
-        if (quit) {
-          emit("update:shape", null);
-        }
-      },
-    });
-    return data;
-  } else if (CompassType.includes(type.value)) {
-    return reactive({
-      value: props.shape.data.rotate,
-      update: () => {
-        props.shape.setRotate((props.shape.data.rotate + 90) % 360);
-        emit("update:shape", null);
-      },
-    });
-  }
-});
-
 // del快捷键删除
 const keydownHandler = (ev: KeyboardEvent) => {
   if (
     !inputIng.value &&
     ["Backspace", "Delete"].includes(ev.key) &&
-    !nDelType.includes(type)
+    ![title, compass].includes(props.shape.data.type)
   ) {
     delHandler();
   }
@@ -122,7 +68,7 @@ onUnmounted(() =>
   display: flex;
   align-items: center;
   justify-content: center;
-  padding: 15px;
+  padding: 15px 25px 0 10px;
   width: fit-content;
 }
 .def-close-shape-edit {
@@ -130,8 +76,8 @@ onUnmounted(() =>
   font-size: 14px;
   color: rgba(0, 0, 0, 0.85);
   position: absolute;
-  right: 15px;
-  top: 15px;
+  right: 5px;
+  top: 5px;
   cursor: pointer;
 }
 </style>
@@ -146,4 +92,25 @@ onUnmounted(() =>
     }
   }
 }
+
+.def-shape-edit-form {
+  max-width: 500px;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  .el-form-item {
+    margin-left: 10px;
+    margin-right: 10px;
+    flex: 0 0 auto;
+    display: flex;
+    align-items: center;
+  }
+
+  // .el-form-item:nth-child(2n - 1) {
+  //   width: 70%;
+  // }
+  // .el-form-item:nth-child(2n) {
+  //   width: 30%;
+  // }
+}
 </style>

+ 36 - 12
src/view/case/draw/index.vue

@@ -3,7 +3,7 @@
     <Header
       class="df-header"
       :type="props.type"
-      @back-page="router.back"
+      @back-page="backPageHandler"
       @back="board.back()"
       @forward="board.forward()"
       @view-init="board.viewInit()"
@@ -19,15 +19,19 @@
       <div class="df-sider">
         <Slider
           :type="props.type"
-          v-model:add-shape="state.addShape"
+          :add-shape="state.addShape"
+          :exists-bg-image="state.exixtsBgImage"
+          @update:add-shape="updateAddShape"
           @track-image="trackImage"
           @selectImage="setBackImage"
           v-if="props"
         />
       </div>
       <div class="df-content">
-        <div class="df-board">
-          <canvas ref="dom" />
+        <div class="df-content-layout">
+          <div class="df-board">
+            <canvas ref="dom" />
+          </div>
         </div>
       </div>
     </div>
@@ -51,6 +55,7 @@ import {
   TitleShapeData,
   saveCaseFileImageInfo,
 } from "@/store/caseFile";
+import { uploadFile } from "@/store/system";
 
 const dom = ref<HTMLCanvasElement>();
 const props = computed(() => {
@@ -70,10 +75,22 @@ const props = computed(() => {
   }
 });
 
+const backPageHandler = () => {
+  board.value && board.value.clear();
+  router.back();
+};
+
 const setBackImage = (blob: Blob) => {
   board.value!.setImage(URL.createObjectURL(blob));
 };
 
+const updateAddShape = async (s, d) => {
+  if (d) {
+    state.value.addData = await uploadFile(d);
+  }
+  state.value.addShape = s;
+};
+
 const trackImage = async () => {
   const data =
     props.value!.type === BoardType.scene
@@ -113,11 +130,10 @@ const saveHandler = async () => {
   const body: SaveCaseFileImageInfo = {
     caseId: args.caseId,
     imgType: args.type,
-    file: new File([blob], `${args.type}_${args.fileId}.png`),
+    file: new File([blob], `${args.type}_${args.fileId}.jpg`),
     filesTitle: titleShape?.text || `${args.caseId}_${BoardTypeDesc[args.type]}`,
     content: store,
   };
-
   args.inAdd || (body.filesId = props.value!.fileId);
 
   const data = await saveCaseFileImageInfo(body);
@@ -138,7 +154,7 @@ const saveHandler = async () => {
 const exportHandler = async () => {
   const { titleShape } = await getStore();
   const blob = await board.value!.export();
-  saveAs(blob, `${titleShape.text}.png`);
+  saveAs(blob, `${titleShape.text}.jpg`);
 };
 </script>
 
@@ -170,17 +186,25 @@ const exportHandler = async () => {
 
 .df-content {
   flex: 1;
-  display: flex;
+  display: grid;
   align-items: center;
   justify-content: center;
   height: 100%;
+  overflow: auto;
+}
+
+.df-content-layout {
+  --w: 297px;
+  --h: 210px;
+  --padding: 0;
+  --calc: 3.5;
+  border: calc(var(--padding) * var(--calc)) solid #fff;
 }
 
 .df-board {
-  border: 1px solid #000;
-  outline: 10px solid #fff;
-  width: 940px;
-  height: 670px;
+  // border: 1px solid #000;
+  width: calc(var(--w) * var(--calc));
+  height: calc(var(--h) * var(--calc));
   box-sizing: border-box;
   canvas {
     background: #fff;

+ 15 - 6
src/view/case/draw/selectFuseImage.vue

@@ -90,8 +90,11 @@ const refreshBlob = async () => {
   if (!iframeRef.value) {
     return;
   }
-  const width = 500;
-  const fuseImage = await getFuseImage(iframeRef.value, width, width);
+  const scale = 1.564;
+  const width = Math.ceil(540 * scale);
+  const height = Math.ceil(390 * scale);
+  const fuseImage = await getFuseImage(iframeRef.value, width, height);
+
   if (fuseImage?.blob) {
     imageBlob.value =
       fuseImage.type !== FuseImageType.FUSE
@@ -100,7 +103,9 @@ const refreshBlob = async () => {
             iframeRef.value,
             fuseImage.blob,
             selectTaggings.value,
-            width
+            width,
+            height,
+            scale
           );
   }
 };
@@ -161,17 +166,21 @@ watchEffect(async (onClanup) => {
 
 <style lang="scss" scoped>
 .house-layout {
+  --w: calc(540 / 390 * var(--h));
+  --h: 610px;
   display: flex;
-  height: 556px;
+  height: var(--h);
 }
 
 .iframe-layout {
   height: 100%;
-  flex: 0 0 1040px;
+  flex: 0 0 var(--w);
+  display: flex;
+  align-items: center;
   iframe {
     border: none;
     width: 100%;
-    height: 100%;
+    height: var(--h);
   }
 }
 

+ 102 - 38
src/view/case/draw/slider.vue

@@ -9,51 +9,88 @@
         :multiple="false"
         :limit="1"
         :show-file-list="false"
+        id="coverupload"
         :http-request="() => {}"
-        :disabled="!!percentage"
-        :before-upload="upload"
-        :accept="accept"
-        :file-list="fileList"
+        :disabled="!!cover.percentage"
+        :before-upload="cover.upload"
+        :accept="cover.accept"
+        :file-list="cover.fileList"
       >
         <el-button
+          @click.stop="coverUploadHandler"
           ghost
           type="primary"
-          :class="{ dispable: percentage }"
+          :class="{ dispable: cover.percentage }"
           :style="{ width: '100%' }"
         >
-          {{ percentage ? "文件上传中" : "上传" + fileDesc[type] }}
+          {{ cover.percentage ? "文件上传中" : "上传" + fileDesc[type] }}
         </el-button>
       </el-upload>
     </div>
-    <template v-for="typeShapes in typesShapes">
-      <h3>{{ typeShapes.name }}</h3>
-      <div class="df-shape-layout">
-        <div
-          v-for="label in typeShapes.shapes"
-          :key="label"
-          class="df-slide-shape"
-          @click="emit('update:addShape', label)"
-          :class="{ active: addShape === label }"
-        >
-          <img :src="shapes[label]" />
-          <p>{{ metas[label].desc }}</p>
-        </div>
+    <h3>标注</h3>
+    <div class="df-shape-layout">
+      <div
+        v-for="label in labels"
+        :key="label"
+        class="df-slide-shape"
+        @click="emit('update:addShape', label)"
+        :class="{ active: addShape === label }"
+      >
+        <img :src="shapes[label]" />
+        <p>{{ metas[label].desc }}</p>
       </div>
-    </template>
+    </div>
+
+    <h3>图例</h3>
+    <el-upload
+      :multiple="false"
+      :limit="1"
+      :show-file-list="false"
+      :http-request="() => {}"
+      :disabled="!!imageLabel.percentage"
+      :before-upload="imageLabel.upload"
+      :accept="imageLabel.accept"
+      :file-list="imageLabel.fileList"
+    >
+      <el-button
+        ghost
+        type="primary"
+        :class="{ dispable: imageLabel.percentage }"
+        :style="{ width: '100%' }"
+      >
+        {{ imageLabel.percentage ? "文件上传中" : "上传图例" }}
+      </el-button>
+    </el-upload>
+    <div class="df-shape-layout">
+      <div
+        v-for="label in images"
+        :key="label"
+        class="df-slide-shape"
+        @click="emit('update:addShape', label)"
+        :class="{ active: addShape === label }"
+      >
+        <img :src="shapes[label]" />
+        <p>{{ metas[label].desc }}</p>
+      </div>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { metas, labels, images, shapes, MetaShapeType } from "./board";
+import { metas, labels, images, shapes, MetaShapeType, customImage } from "./board";
 import { BoardType } from "@/store/caseFile";
 import { maxFileSize } from "@/constant/caseFile";
 import { useUpload } from "@/hook/upload";
-import { computed, watchEffect } from "vue";
+import { reactive, ref, watchEffect } from "vue";
 import { imageCropper } from "@/view/system/quisk";
-import { fixImageSize } from "@/util/image-rotate";
+import { coverImageSize } from "@/util/image-rotate";
+import { uploadFile } from "@/store/system";
+import { ElMessage } from "element-plus";
+import { confirm } from "@/helper/message";
 
-defineProps<{
+const props = defineProps<{
   type: BoardType;
+  existsBgImage: boolean;
   addShape: MetaShapeType | null;
 }>();
 
@@ -61,28 +98,55 @@ const fileDesc = {
   [BoardType.scene]: "户型图",
   [BoardType.map]: "方位图",
 };
-const typesShapes = [
-  { name: "标注", shapes: labels },
-  { name: "图例", shapes: images },
-];
-
 const emit = defineEmits<{
-  (e: "update:addShape", val: MetaShapeType | null): void;
+  (e: "update:addShape", val: MetaShapeType | null, data?: any): void;
   (e: "trackImage"): void;
   (e: "selectImage", val: Blob): void;
 }>();
 
-const { percentage, upload, file, fileList, removeFile, accept } = useUpload({
-  maxSize: maxFileSize,
-  formats: [".jpg", ".png"],
-});
+const cover = reactive(
+  useUpload({
+    maxSize: maxFileSize,
+    formats: [".jpg", ".png"],
+  })
+);
 
+const coverUploadHandler = async () => {
+  if (
+    props.existsBgImage &&
+    (await confirm("重新上传将替换当前图体,确定要上传吗?", "继续上传"))
+  ) {
+    // await cover.upload(file)
+  }
+  const input = document
+    .querySelector("#coverupload")!
+    .querySelector("input[type=file]") as HTMLInputElement;
+  input.click();
+};
 watchEffect(async () => {
-  if (file.value) {
-    const newFile = (await fixImageSize(file.value, 500, 500, false)) || file.value;
-    const data = await imageCropper({ img: newFile, fixed: [500, 500] });
+  if (cover.file) {
+    const coverImage = (await coverImageSize(cover.file, 540, 390, false)) || cover.file;
+    const data = await imageCropper({
+      img: coverImage.blob,
+      fixed: [coverImage.width, coverImage.height],
+    });
     data && emit("selectImage", data);
-    removeFile();
+    cover.removeFile();
+  }
+});
+
+const imageLabel = reactive(
+  useUpload({
+    maxSize: maxFileSize,
+    formats: [".jpg", ".png"],
+  })
+);
+
+watchEffect(async () => {
+  if (imageLabel.file) {
+    emit("update:addShape", customImage, imageLabel.file);
+    imageLabel.removeFile();
+    // ElMessage.info("请前往右边画板选择为止单击添加图例");
   }
 });
 </script>

+ 18 - 11
src/view/case/help.ts

@@ -182,20 +182,22 @@ export type FuseImageRet = { type: FuseImageType; blob: Blob | null };
 export const getFuseImage = async (
   iframe: HTMLIFrameElement,
   width: number = 500,
-  height: number = width
+  height: number = 390
 ) => {
   const typeMap = await getIframeSceneType(iframe);
   let blob: Blob | null = null;
 
   switch (typeMap?.type) {
     case FuseImageType.FUSE:
-      const dataURL = await typeMap.sdk.screenshot(width, height);
+      console.error(width, height);
+      const dataURL = await typeMap.sdk.screenshot(width, height, 0);
       const res = await fetch(dataURL);
       blob = await res.blob();
       break;
     case FuseImageType.KANKAN:
+      console.error("截图尺寸", width, height);
       const result = await typeMap.sdk.Camera.screenshot(
-        [{ width: width, height: width, name: "2k" }],
+        [{ width: width, height: height, name: "2k", bgOpacity: 0 }],
         false
       );
       blob = base64ToBlob(result[0].data);
@@ -203,7 +205,7 @@ export const getFuseImage = async (
     case FuseImageType.LASER:
       blob = await new Promise<Blob | null>((resolve) => {
         typeMap.sdk.scene
-          .screenshot(width, height)
+          .screenshot(width, height, 0)
           .done((data: { dataUrl: string }) =>
             resolve(base64ToBlob(data.dataUrl))
           );
@@ -218,8 +220,11 @@ export const fuseImageJoinHot = async (
   iframe: HTMLIFrameElement,
   rawBlob: Blob,
   showTags: CaseTagging[],
-  width = 500
+  width = 500,
+  height = 390,
+  scale = 1
 ) => {
+  console.log(width, showTags);
   // fuse场景需要特别处理
   const img = new Image();
   img.src = URL.createObjectURL(rawBlob);
@@ -243,14 +248,16 @@ export const fuseImageJoinHot = async (
     );
     if (index !== -1) {
       const bound = hot.getBoundingClientRect();
-      const size = (img.width / width) * 32;
+      const hscale = img.width / contentDoc.body.offsetWidth;
+      const wsize = hscale * 32;
+      const hsize = hscale * 32;
 
-      const left = bound.left + size / 2;
-      const top = bound.top + size / 2;
+      const left = bound.left * hscale + wsize / 2;
+      const top = bound.top * hscale + hsize / 2;
       ctx.save();
       ctx.translate(left, top);
       ctx.beginPath();
-      ctx.arc(0, 0, size / 2, 0, 2 * Math.PI);
+      ctx.arc(0, 0, hsize / 2, 0, 2 * Math.PI);
       ctx.strokeStyle = "#000";
       ctx.fillStyle = "#fff";
       ctx.stroke();
@@ -259,7 +266,7 @@ export const fuseImageJoinHot = async (
       ctx.fillStyle = "#000";
       ctx.textAlign = "center";
       ctx.textBaseline = "middle";
-      ctx.font = `normal ${size / 2}px serif`;
+      ctx.font = `normal ${hsize / 2}px serif`;
       ctx.fillText((index + 1).toString(), 0, 0);
       ctx.restore();
     }
@@ -267,7 +274,7 @@ export const fuseImageJoinHot = async (
 
   const $ccanvas = document.createElement("canvas");
   $ccanvas.width = width;
-  $ccanvas.height = width;
+  $ccanvas.height = height;
   const cctx = $ccanvas.getContext("2d")!;
   drawImage(
     cctx,

+ 1 - 2
src/view/case/quisk.ts

@@ -22,7 +22,6 @@ export const addCaseScenes = quiskMountFactory(AddScenes, {
 
 const editEshapeTableRaw = quiskMountFactory(EditEshapeTable, {
   title: "表格内容编辑",
-  width: 460,
 })<EshapeTableContent>;
 export const editEshapeTable = (
   props: Parameters<typeof editEshapeTableRaw>["0"]
@@ -40,7 +39,7 @@ export const showCaseScenes = quiskMountFactory(SceneList, {
 
 export const selectFuseImage = quiskMountFactory(SelectFuseImage, {
   title: "选择户型图",
-  width: 1500,
+  width: 1300,
 })<FuseImage>;
 
 export const selectMapImage = quiskMountFactory(SelectMapImage, {

+ 3 - 0
src/view/home/index.vue

@@ -14,6 +14,9 @@
 <script setup lang="ts">
 import comHead from "@/components/head/index.vue";
 import { appConstant } from "@/app";
+import * as echarts from 'echarts';
+
+
 </script>
 
 <style lang="scss" scoped>

+ 1 - 1
src/view/statistics/statisticsInject.ts

@@ -139,7 +139,7 @@ export const statisticsConfigs: ConfigItem[] = reactive([
     },
   },
   {
-    title: "起火场所",
+    title: "起火场所统计",
     data: {
       tooltip: {
         trigger: "axis",

+ 115 - 35
src/view/system/imageCropper.vue

@@ -1,23 +1,25 @@
 <template>
-  <div
-    class="vue-crop-layout"
-    :style="{ height: fixed[1] + 40 + 'px', width: fixed[0] + 40 + 'px' }"
-  >
-    <VueCropper
-      ref="cropperRef"
-      :img="url"
-      :outputSize="1"
-      canScale
-      autoCrop
-      :autoCropWidth="fixed[0]"
-      :autoCropHeight="fixed[1]"
-      centerBox
-      :fixed="!!fixed"
-      :fixedNumber="fixed"
-      fixedBox
-      :canMoveBox="false"
-      @realTime="realTimeHandler"
-    />
+  <div>
+    <div
+      class="vue-crop-layout"
+      ref="layoutRef"
+      :style="{ width: sWidth + 'px', height: sHeight + 'px' }"
+    >
+      <VueCropper
+        class="cropper-cls"
+        ref="cropperRef"
+        :img="url"
+        :outputSize="1"
+        canScale
+        autoCrop
+        centerBox
+        :fixed="!!fixed"
+        :fixedNumber="fixed"
+      />
+    </div>
+    <div class="control">
+      <el-button type="primary" @click="cropperRef.rotateRight()"> 旋转 </el-button>
+    </div>
   </div>
 </template>
 
@@ -26,6 +28,8 @@ import "vue-cropper/dist/index.css";
 import { ref, computed } from "vue";
 import { VueCropper } from "vue-cropper";
 import { QuiskExpose } from "@/helper/mount";
+import { getDomMatrix } from "@/util";
+import { inverse, multiply, positionTransform, rotateZ, translate } from "@/util/mt4";
 
 type CropperProps = {
   img: Blob | string;
@@ -33,34 +37,110 @@ type CropperProps = {
 };
 const props = defineProps<CropperProps>();
 
-const url = computed(() =>
-  typeof props.img === "string" ? props.img : URL.createObjectURL(props.img)
-);
-const scale = ref(1);
+// 样式控制
+const sWidth = 500;
+const sHeight = (props.fixed[1] / props.fixed[0]) * sWidth;
+
+const realImage = new Image();
+const url = computed(() => {
+  const url = typeof props.img === "string" ? props.img : URL.createObjectURL(props.img);
+  realImage.src = url;
+  return url;
+});
+const layoutRef = ref<HTMLDivElement>();
 const cropperRef = ref<any>();
-const realTimeHandler = (data: any) => {
-  const transform = data.img.transform as string;
-  if (transform) {
-    const result = transform.match(/scale\((\d+(\.\d+)?)\)/);
-    if (result) {
-      scale.value = Number(result[1]);
-    }
+
+const getDrawInfo = () => {
+  const imgDom = layoutRef.value?.querySelector(".cropper-box-canvas") as HTMLElement;
+  const cropDom = layoutRef.value?.querySelector(".cropper-crop-box") as HTMLElement;
+
+  const imgMatrix = getDomMatrix(imgDom);
+  const cropMatrix = getDomMatrix(cropDom);
+  const cropSize = [cropperRef.value.cropW, cropperRef.value.cropH];
+
+  // 屏幕位置
+  const cropBox = [
+    positionTransform([-cropSize[0] / 2, -cropSize[1] / 2, 0], cropMatrix),
+    positionTransform([cropSize[0] / 2, cropSize[1] / 2, 0], cropMatrix),
+  ];
+
+  const scale = [
+    realImage.width / imgDom.offsetWidth,
+    realImage.height / imgDom.offsetHeight,
+  ];
+
+  const invImageMatrix = inverse(imgMatrix);
+  const lt = positionTransform(cropBox[0], invImageMatrix);
+  const rb = positionTransform(cropBox[1], invImageMatrix);
+  const imgBound = [
+    lt[0] * scale[0] + realImage.width / 2,
+    lt[1] * scale[1] + realImage.height / 2,
+    rb[0] * scale[0] + realImage.width / 2,
+    rb[1] * scale[1] + realImage.height / 2,
+  ];
+
+  const realBound = {
+    left: Math.round(imgBound[0]),
+    top: Math.round(imgBound[1]),
+    right: Math.round(imgBound[2]),
+    bottom: Math.round(imgBound[3]),
+  };
+  // 旋转过
+  if (realBound.left > realBound.right) {
+    [realBound.left, realBound.right] = [realBound.right, realBound.left];
+  }
+  if (realBound.top > realBound.bottom) {
+    [realBound.top, realBound.bottom] = [realBound.bottom, realBound.top];
   }
+
+  return {
+    ...realBound,
+    rotate: (cropperRef.value.rotate * Math.PI) / 2,
+  };
+};
+
+const clipImage = () => {
+  const data = getDrawInfo();
+  const canvas = document.createElement("canvas");
+  const ctx = canvas.getContext("2d")!;
+  const w = data.right - data.left;
+  const h = data.bottom - data.top;
+
+  const boxMatrix = multiply(
+    translate(-w / 2, -h / 2, 0),
+    rotateZ(data.rotate),
+    translate(w / 2, h / 2, 0)
+  );
+  const start = positionTransform([0, 0, 0], boxMatrix);
+  const end = positionTransform([w, h, 0], boxMatrix);
+  const cw = (canvas.width = Math.abs(end[0] - start[0]));
+  const ch = (canvas.height = Math.abs(end[1] - start[1]));
+  ctx.translate(cw / 2, ch / 2);
+  ctx.rotate(data.rotate);
+  ctx.drawImage(realImage, data.left, data.top, w, h, -w / 2, -h / 2, w, h);
+
+  return new Promise<Blob | null>((resolve) => canvas.toBlob(resolve));
 };
 
 defineExpose<QuiskExpose>({
-  submit: () => new Promise((resolve) => cropperRef.value.getCropBlob(resolve)),
+  submit: clipImage,
 });
 </script>
 
 <style lang="scss" scoped>
 .vue-crop-layout {
-  height: 300px;
+  width: 100%;
+}
+.control {
+  margin-top: 20px;
+  text-align: center;
 }
 </style>
 
-<style>
-.vue-crop-layout .cropper-view-box {
-  outline-color: var(--el-color-primary);
+<style lang="scss">
+.vue-crop-layout {
+  .cropper-view-box {
+    outline-color: var(--el-color-primary);
+  }
 }
 </style>

+ 2 - 0
src/view/vrmodel/quisk.ts

@@ -1,4 +1,6 @@
+import { QuoteScene, SceneType } from "@/store/scene";
 import EditModel from "./editModel.vue";
+import SceneDownload from "./sceneDownload.vue";
 import { quiskMountFactory } from "@/helper/mount";
 import SceneDownload from "./sceneDownload.vue";
 import { QuoteScene, SceneType } from "@/store/scene";

+ 5 - 2
src/view/vrmodel/sceneContent.vue

@@ -69,9 +69,9 @@
       </span>
       <span
         class="oper-span"
+        @click="sceneDownloadHandler(row)"
         v-pdpath="['download']"
-        @click="sceneDownload({ scene: row })"
-        v-if="row.num"
+        v-if="row.num && row.status === QuoteSceneStatus.SUCCESS"
       >
         下载
       </span>
@@ -101,4 +101,7 @@ const delSceneHandler = async (scene: QuoteScene) => {
     props.pagging.refresh();
   }
 };
+const sceneDownloadHandler = (scene: QuoteScene) => {
+  sceneDownload({ scene });
+};
 </script>

+ 1 - 0
vite.config.ts

@@ -41,6 +41,7 @@ export default defineConfig({
   ],
   server: {
     port: 5173,
+    host: "0.0.0.0",
     proxy: {
       "/api": {
         target: dev