Browse Source

Merge branch 'master' into v.1.7.0

bill 11 tháng trước cách đây
mục cha
commit
fab4f410cd
99 tập tin đã thay đổi với 4666 bổ sung1401 xóa
  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. BIN
      src/assets/icon/fuse/iconfont.ttf
  13. BIN
      src/assets/icon/fuse/iconfont.woff
  14. BIN
      src/assets/icon/fuse/iconfont.woff2
  15. BIN
      src/assets/image/criminal-32.png
  16. BIN
      src/assets/image/criminal.ico
  17. BIN
      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

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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";
 }

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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",

BIN
src/assets/icon/fuse/iconfont.ttf


BIN
src/assets/icon/fuse/iconfont.woff


BIN
src/assets/icon/fuse/iconfont.woff2


BIN
src/assets/image/criminal-32.png


BIN
src/assets/image/criminal.ico


BIN
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