1
0
Просмотр исходного кода

Merge branch 'master' into offline

tangning 6 дней назад
Родитель
Сommit
d2e0715289
48 измененных файлов с 179080 добавлено и 690 удалено
  1. 7 0
      package.json
  2. BIN
      public/SourceHanSansSC-Regular.otf
  3. 2417 0
      public/images/loader/KTX2Loader/basis/basis_transcoder.js
  4. BIN
      public/images/loader/KTX2Loader/basis/basis_transcoder.wasm
  5. 67894 0
      public/kankan-sdk-deps.js
  6. 100696 0
      public/kankan-sdk.js
  7. 1 0
      public/kankan-sdk.js.map
  8. 2417 0
      public/loader/KTX2Loader/basis/basis_transcoder.js
  9. BIN
      public/loader/KTX2Loader/basis/basis_transcoder.wasm
  10. 76 0
      public/model.html
  11. 3 2
      src/App.vue
  12. 348 3
      src/assets/font/demo_index.html
  13. 63 3
      src/assets/font/iconfont.css
  14. 1 1
      src/assets/font/iconfont.js
  15. 105 0
      src/assets/font/iconfont.json
  16. BIN
      src/assets/font/iconfont.ttf
  17. BIN
      src/assets/font/iconfont.woff
  18. BIN
      src/assets/font/iconfont.woff2
  19. 4 5
      src/assets/style/public.scss
  20. 11 1
      src/request/urls.ts
  21. 36 0
      src/store/case.ts
  22. 2 2
      src/store/user.ts
  23. 57 41
      src/util/index.ts
  24. 11 0
      src/util/localUtil.ts
  25. 66 25
      src/view/abstract/index.vue
  26. 2021 0
      src/view/case/photos/canvas-photo-editor.js
  27. 653 503
      src/view/case/photos/index.vue
  28. 298 0
      src/view/case/photos/style.scss
  29. 150 0
      src/view/case/photos/usePDF.js
  30. 11 3
      src/view/case/records/index.vue
  31. 2 2
      src/view/layout/index.vue
  32. 3 2
      src/view/layout/slide/index.vue
  33. 1 12
      src/view/layout/top/index.vue
  34. 95 0
      src/view/material/exportPhotos.vue
  35. 11 0
      src/view/material/quisk.ts
  36. 550 78
      src/view/material/sceneImg.vue
  37. 104 0
      src/view/material/showpages.vue
  38. 43 0
      src/view/material/tableScene/list.vue
  39. 144 0
      src/view/material/tableScene/modelContent.vue
  40. 62 0
      src/view/material/tableScene/pagging.ts
  41. 98 0
      src/view/material/tableScene/sceneContent.vue
  42. 52 0
      src/view/material/tableScene/tableModel.vue
  43. 200 0
      src/view/vrmodel/addCaseFile.vue
  44. 8 4
      src/view/vrmodel/list.vue
  45. 5 1
      src/view/vrmodel/quisk.ts
  46. 1 1
      src/view/vrmodel/tableModel.vue
  47. 5 0
      vite.config.ts
  48. 348 1
      yarn.lock

+ 7 - 0
package.json

@@ -13,17 +13,24 @@
   "dependencies": {
   "dependencies": {
     "@amap/amap-jsapi-loader": "^1.0.1",
     "@amap/amap-jsapi-loader": "^1.0.1",
     "@element-plus/icons-vue": "^2.1.0",
     "@element-plus/icons-vue": "^2.1.0",
+    "@pdf-lib/fontkit": "^1.1.1",
     "@types/qs": "^6.9.7",
     "@types/qs": "^6.9.7",
     "axios": "^1.4.0",
     "axios": "^1.4.0",
     "echarts": "^5.4.3",
     "echarts": "^5.4.3",
     "element-plus": "^2.3.8",
     "element-plus": "^2.3.8",
+    "file-saver": "^2.0.5",
+    "fontkit": "^2.0.4",
     "html2canvas": "^1.4.1",
     "html2canvas": "^1.4.1",
+    "html2pdf.js": "^0.14.0",
     "js-base64": "^3.7.5",
     "js-base64": "^3.7.5",
+    "jspdf": "^4.2.1",
+    "jszip": "^3.10.1",
     "leaflet": "^1.9.4",
     "leaflet": "^1.9.4",
     "leaflet.chinatmsproviders": "^3.0.6",
     "leaflet.chinatmsproviders": "^3.0.6",
     "mime": "^3.0.0",
     "mime": "^3.0.0",
     "mitt": "^3.0.1",
     "mitt": "^3.0.1",
     "ollama": "^0.5.14",
     "ollama": "^0.5.14",
+    "pdf-lib": "^1.17.1",
     "province-city-china": "^8.5.8",
     "province-city-china": "^8.5.8",
     "qs": "^6.11.2",
     "qs": "^6.11.2",
     "sass": "^1.64.2",
     "sass": "^1.64.2",

BIN
public/SourceHanSansSC-Regular.otf


Разница между файлами не показана из-за своего большого размера
+ 2417 - 0
public/images/loader/KTX2Loader/basis/basis_transcoder.js


BIN
public/images/loader/KTX2Loader/basis/basis_transcoder.wasm


Разница между файлами не показана из-за своего большого размера
+ 67894 - 0
public/kankan-sdk-deps.js


Разница между файлами не показана из-за своего большого размера
+ 100696 - 0
public/kankan-sdk.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
public/kankan-sdk.js.map


Разница между файлами не показана из-за своего большого размера
+ 2417 - 0
public/loader/KTX2Loader/basis/basis_transcoder.js


BIN
public/loader/KTX2Loader/basis/basis_transcoder.wasm


+ 76 - 0
public/model.html

@@ -0,0 +1,76 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+    <style>
+      html,
+      body {
+        width: 100%;
+        height: 100%;
+        margin: 0;
+        overflow: hidden;
+      }
+      .scene {
+        width: 100%;
+        height: 100%;
+      }
+      .controls {
+        position: absolute;
+        left: 0;
+        bottom: 0;
+        width: 100%;
+        z-index: 999;
+      }
+    </style>
+    <!-- <script src="/sdk/kankan-sdk-deps.js"></script>
+    <script src="/sdk/kankan-sdk.js"></script> -->
+    <script src="/sdk/kankan-sdk-deps.js?v=4.12.1-alpha.18-1774943964518"></script>
+    <script src="/sdk/kankan-sdk.js?v=4.12.1-alpha.18-1774943964518"></script>
+
+  </head>
+  <body>
+    <div class="scene"></div>
+    <div class="controls">
+      <button onclick="__sdk.Camera.panorama()">漫游</button>
+      <button onclick="__sdk.Camera.floorplan()">平面</button>
+      <button onclick="__sdk.Camera.dollhouse()">模型</button>
+    </div>
+    <script>
+       let urlHasValue = function(key, isGetValue) {  
+        let querys = window.location.search.substr(1).split("&")
+        if (isGetValue) {
+            for (let i = 0; i < querys.length; i++) {
+                let keypair = querys[i].split("=")
+                if (keypair.length === 2 && keypair[0] === key) {
+                    return keypair[1]
+                }
+            }
+            return ""
+        } else { 
+            for (let i = 0; i < querys.length; i++) {
+                let keypair = querys[i].split("=")
+                if (keypair[0] == key) {
+                    return true
+                }
+            }
+            return false
+        }
+    } 
+    
+    
+    
+      let num = urlHasValue('m',true);
+    
+      window.__sdk = new KanKan({
+        dom: ".scene",
+        num,
+        server: "/",
+        resource: "/oss/",
+        isRouteSnap : true 
+      });
+      __sdk.render();
+    </script>
+  </body>
+</html>

+ 3 - 2
src/App.vue

@@ -14,8 +14,9 @@ import { getPackageData, show, httpData } from "@/store/case";
 import { strToParams } from "@/util";
 import { strToParams } from "@/util";
 import { ElMessageBox } from "element-plus";
 import { ElMessageBox } from "element-plus";
 import { browser } from "@/util/browser.ts";
 import { browser } from "@/util/browser.ts";
-const params = strToParams(window.location.search);
-console.log("params", params);
+const url = window.location.href.split('?')[1];
+const urlWithoutHash = window.location.href.split('#')[0]
+const params = strToParams(urlWithoutHash);
 if (params.token) {
 if (params.token) {
   setToken(params.token);
   setToken(params.token);
 }
 }

+ 348 - 3
src/assets/font/demo_index.html

@@ -55,6 +55,96 @@
           <ul class="icon_lists dib-box">
           <ul class="icon_lists dib-box">
           
           
             <li class="dib">
             <li class="dib">
+              <span class="icon iconfont">&#xe649;</span>
+                <div class="name">AI抓拍</div>
+                <div class="code-name">&amp;#xe649;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe740;</span>
+                <div class="name">new_left</div>
+                <div class="code-name">&amp;#xe740;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe741;</span>
+                <div class="name">new_right</div>
+                <div class="code-name">&amp;#xe741;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe742;</span>
+                <div class="name">new</div>
+                <div class="code-name">&amp;#xe742;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe743;</span>
+                <div class="name">layout</div>
+                <div class="code-name">&amp;#xe743;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe73f;</span>
+                <div class="name">delete</div>
+                <div class="code-name">&amp;#xe73f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7f5;</span>
+                <div class="name">backout</div>
+                <div class="code-name">&amp;#xe7f5;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7f6;</span>
+                <div class="name">redo</div>
+                <div class="code-name">&amp;#xe7f6;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7f7;</span>
+                <div class="name">screen_f</div>
+                <div class="code-name">&amp;#xe7f7;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7f8;</span>
+                <div class="name">del</div>
+                <div class="code-name">&amp;#xe7f8;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7f9;</span>
+                <div class="name">download</div>
+                <div class="code-name">&amp;#xe7f9;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7fa;</span>
+                <div class="name">indexes</div>
+                <div class="code-name">&amp;#xe7fa;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7fb;</span>
+                <div class="name">layout_two</div>
+                <div class="code-name">&amp;#xe7fb;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7fc;</span>
+                <div class="name">layout_one</div>
+                <div class="code-name">&amp;#xe7fc;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe7fd;</span>
+                <div class="name">layout_h</div>
+                <div class="code-name">&amp;#xe7fd;</div>
+              </li>
+          
+            <li class="dib">
               <span class="icon iconfont">&#xe7c8;</span>
               <span class="icon iconfont">&#xe7c8;</span>
                 <div class="name">Upload</div>
                 <div class="name">Upload</div>
                 <div class="code-name">&amp;#xe7c8;</div>
                 <div class="code-name">&amp;#xe7c8;</div>
@@ -258,9 +348,9 @@
 <pre><code class="language-css"
 <pre><code class="language-css"
 >@font-face {
 >@font-face {
   font-family: 'iconfont';
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1756902711273') format('woff2'),
-       url('iconfont.woff?t=1756902711273') format('woff'),
-       url('iconfont.ttf?t=1756902711273') format('truetype');
+  src: url('iconfont.woff2?t=1776255409821') format('woff2'),
+       url('iconfont.woff?t=1776255409821') format('woff'),
+       url('iconfont.ttf?t=1776255409821') format('truetype');
 }
 }
 </code></pre>
 </code></pre>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -287,6 +377,141 @@
         <ul class="icon_lists dib-box">
         <ul class="icon_lists dib-box">
           
           
           <li class="dib">
           <li class="dib">
+            <span class="icon iconfont icon-AIzhuapai"></span>
+            <div class="name">
+              AI抓拍
+            </div>
+            <div class="code-name">.icon-AIzhuapai
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-new_left"></span>
+            <div class="name">
+              new_left
+            </div>
+            <div class="code-name">.icon-new_left
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-new_right"></span>
+            <div class="name">
+              new_right
+            </div>
+            <div class="code-name">.icon-new_right
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-new"></span>
+            <div class="name">
+              new
+            </div>
+            <div class="code-name">.icon-new
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-layout"></span>
+            <div class="name">
+              layout
+            </div>
+            <div class="code-name">.icon-layout
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-delete"></span>
+            <div class="name">
+              delete
+            </div>
+            <div class="code-name">.icon-delete
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-backout"></span>
+            <div class="name">
+              backout
+            </div>
+            <div class="code-name">.icon-backout
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-redo"></span>
+            <div class="name">
+              redo
+            </div>
+            <div class="code-name">.icon-redo
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-screen_f"></span>
+            <div class="name">
+              screen_f
+            </div>
+            <div class="code-name">.icon-screen_f
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-del"></span>
+            <div class="name">
+              del
+            </div>
+            <div class="code-name">.icon-del
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-download"></span>
+            <div class="name">
+              download
+            </div>
+            <div class="code-name">.icon-download
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-indexes"></span>
+            <div class="name">
+              indexes
+            </div>
+            <div class="code-name">.icon-indexes
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-layout_two"></span>
+            <div class="name">
+              layout_two
+            </div>
+            <div class="code-name">.icon-layout_two
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-layout_one"></span>
+            <div class="name">
+              layout_one
+            </div>
+            <div class="code-name">.icon-layout_one
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icon-layout_h"></span>
+            <div class="name">
+              layout_h
+            </div>
+            <div class="code-name">.icon-layout_h
+            </div>
+          </li>
+          
+          <li class="dib">
             <span class="icon iconfont icon-Upload"></span>
             <span class="icon iconfont icon-Upload"></span>
             <div class="name">
             <div class="name">
               Upload
               Upload
@@ -594,6 +819,126 @@
           
           
             <li class="dib">
             <li class="dib">
                 <svg class="icon svg-icon" aria-hidden="true">
                 <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-AIzhuapai"></use>
+                </svg>
+                <div class="name">AI抓拍</div>
+                <div class="code-name">#icon-AIzhuapai</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-new_left"></use>
+                </svg>
+                <div class="name">new_left</div>
+                <div class="code-name">#icon-new_left</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-new_right"></use>
+                </svg>
+                <div class="name">new_right</div>
+                <div class="code-name">#icon-new_right</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-new"></use>
+                </svg>
+                <div class="name">new</div>
+                <div class="code-name">#icon-new</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-layout"></use>
+                </svg>
+                <div class="name">layout</div>
+                <div class="code-name">#icon-layout</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-delete"></use>
+                </svg>
+                <div class="name">delete</div>
+                <div class="code-name">#icon-delete</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-backout"></use>
+                </svg>
+                <div class="name">backout</div>
+                <div class="code-name">#icon-backout</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-redo"></use>
+                </svg>
+                <div class="name">redo</div>
+                <div class="code-name">#icon-redo</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-screen_f"></use>
+                </svg>
+                <div class="name">screen_f</div>
+                <div class="code-name">#icon-screen_f</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-del"></use>
+                </svg>
+                <div class="name">del</div>
+                <div class="code-name">#icon-del</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-download"></use>
+                </svg>
+                <div class="name">download</div>
+                <div class="code-name">#icon-download</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-indexes"></use>
+                </svg>
+                <div class="name">indexes</div>
+                <div class="code-name">#icon-indexes</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-layout_two"></use>
+                </svg>
+                <div class="name">layout_two</div>
+                <div class="code-name">#icon-layout_two</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-layout_one"></use>
+                </svg>
+                <div class="name">layout_one</div>
+                <div class="code-name">#icon-layout_one</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icon-layout_h"></use>
+                </svg>
+                <div class="name">layout_h</div>
+                <div class="code-name">#icon-layout_h</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
                   <use xlink:href="#icon-Upload"></use>
                   <use xlink:href="#icon-Upload"></use>
                 </svg>
                 </svg>
                 <div class="name">Upload</div>
                 <div class="name">Upload</div>

+ 63 - 3
src/assets/font/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
 @font-face {
   font-family: "iconfont"; /* Project id 4789215 */
   font-family: "iconfont"; /* Project id 4789215 */
-  src: url('iconfont.woff2?t=1756902711273') format('woff2'),
-       url('iconfont.woff?t=1756902711273') format('woff'),
-       url('iconfont.ttf?t=1756902711273') format('truetype');
+  src: url('iconfont.woff2?t=1776255409821') format('woff2'),
+       url('iconfont.woff?t=1776255409821') format('woff'),
+       url('iconfont.ttf?t=1776255409821') format('truetype');
 }
 }
 
 
 .iconfont {
 .iconfont {
@@ -13,6 +13,66 @@
   -moz-osx-font-smoothing: grayscale;
   -moz-osx-font-smoothing: grayscale;
 }
 }
 
 
+.icon-AIzhuapai:before {
+  content: "\e649";
+}
+
+.icon-new_left:before {
+  content: "\e740";
+}
+
+.icon-new_right:before {
+  content: "\e741";
+}
+
+.icon-new:before {
+  content: "\e742";
+}
+
+.icon-layout:before {
+  content: "\e743";
+}
+
+.icon-delete:before {
+  content: "\e73f";
+}
+
+.icon-backout:before {
+  content: "\e7f5";
+}
+
+.icon-redo:before {
+  content: "\e7f6";
+}
+
+.icon-screen_f:before {
+  content: "\e7f7";
+}
+
+.icon-del:before {
+  content: "\e7f8";
+}
+
+.icon-download:before {
+  content: "\e7f9";
+}
+
+.icon-indexes:before {
+  content: "\e7fa";
+}
+
+.icon-layout_two:before {
+  content: "\e7fb";
+}
+
+.icon-layout_one:before {
+  content: "\e7fc";
+}
+
+.icon-layout_h:before {
+  content: "\e7fd";
+}
+
 .icon-Upload:before {
 .icon-Upload:before {
   content: "\e7c8";
   content: "\e7c8";
 }
 }

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
src/assets/font/iconfont.js


+ 105 - 0
src/assets/font/iconfont.json

@@ -6,6 +6,111 @@
   "description": "",
   "description": "",
   "glyphs": [
   "glyphs": [
     {
     {
+      "icon_id": "37288684",
+      "name": "AI抓拍",
+      "font_class": "AIzhuapai",
+      "unicode": "e649",
+      "unicode_decimal": 58953
+    },
+    {
+      "icon_id": "47237723",
+      "name": "new_left",
+      "font_class": "new_left",
+      "unicode": "e740",
+      "unicode_decimal": 59200
+    },
+    {
+      "icon_id": "47237722",
+      "name": "new_right",
+      "font_class": "new_right",
+      "unicode": "e741",
+      "unicode_decimal": 59201
+    },
+    {
+      "icon_id": "47237721",
+      "name": "new",
+      "font_class": "new",
+      "unicode": "e742",
+      "unicode_decimal": 59202
+    },
+    {
+      "icon_id": "47237720",
+      "name": "layout",
+      "font_class": "layout",
+      "unicode": "e743",
+      "unicode_decimal": 59203
+    },
+    {
+      "icon_id": "47237652",
+      "name": "delete",
+      "font_class": "delete",
+      "unicode": "e73f",
+      "unicode_decimal": 59199
+    },
+    {
+      "icon_id": "47190607",
+      "name": "backout",
+      "font_class": "backout",
+      "unicode": "e7f5",
+      "unicode_decimal": 59381
+    },
+    {
+      "icon_id": "47190606",
+      "name": "redo",
+      "font_class": "redo",
+      "unicode": "e7f6",
+      "unicode_decimal": 59382
+    },
+    {
+      "icon_id": "47190605",
+      "name": "screen_f",
+      "font_class": "screen_f",
+      "unicode": "e7f7",
+      "unicode_decimal": 59383
+    },
+    {
+      "icon_id": "47190604",
+      "name": "del",
+      "font_class": "del",
+      "unicode": "e7f8",
+      "unicode_decimal": 59384
+    },
+    {
+      "icon_id": "47190603",
+      "name": "download",
+      "font_class": "download",
+      "unicode": "e7f9",
+      "unicode_decimal": 59385
+    },
+    {
+      "icon_id": "47190602",
+      "name": "indexes",
+      "font_class": "indexes",
+      "unicode": "e7fa",
+      "unicode_decimal": 59386
+    },
+    {
+      "icon_id": "47190581",
+      "name": "layout_two",
+      "font_class": "layout_two",
+      "unicode": "e7fb",
+      "unicode_decimal": 59387
+    },
+    {
+      "icon_id": "47190580",
+      "name": "layout_one",
+      "font_class": "layout_one",
+      "unicode": "e7fc",
+      "unicode_decimal": 59388
+    },
+    {
+      "icon_id": "47190579",
+      "name": "layout_h",
+      "font_class": "layout_h",
+      "unicode": "e7fd",
+      "unicode_decimal": 59389
+    },
+    {
       "icon_id": "45434743",
       "icon_id": "45434743",
       "name": "Upload",
       "name": "Upload",
       "font_class": "Upload",
       "font_class": "Upload",

BIN
src/assets/font/iconfont.ttf


BIN
src/assets/font/iconfont.woff


BIN
src/assets/font/iconfont.woff2


+ 4 - 5
src/assets/style/public.scss

@@ -326,10 +326,9 @@ body {
   display: none;
   display: none;
 }
 }
 
 
-// .el-message-box__message {
-//   min-height: 50px;
-//   padding: 0 30px;
-// }
+.el-message-box__message {
+  white-space: pre-line;
+}
 
 
 // .el-message-box__status {
 // .el-message-box__status {
 //   top: -13px !important;
 //   top: -13px !important;
@@ -467,7 +466,7 @@ body {
   justify-content: center;
   justify-content: center;
 }
 }
 .el-message-box__content {
 .el-message-box__content {
-  padding: 40px 0;
+  padding: 40px 20px;
   border-top: 1px solid #f5f5f5;
   border-top: 1px solid #f5f5f5;
   border-bottom: 1px solid #f5f5f5;
   border-bottom: 1px solid #f5f5f5;
 }
 }

+ 11 - 1
src/request/urls.ts

@@ -186,6 +186,9 @@ export const deleteCaseFile = "/fusion/caseFiles/delete";
 export const updateCaseFile = "/fusion/caseFiles/updateTitle";
 export const updateCaseFile = "/fusion/caseFiles/updateTitle";
 export const newFileupload = "/service/manage/common/upload/fileNew";
 export const newFileupload = "/service/manage/common/upload/fileNew";
 export const newupload = "/service/manage/common/upload/files";
 export const newupload = "/service/manage/common/upload/files";
+export const uploadSceneCheck = '/service/manage/scene/uploadSceneCheck'
+export const uploadSceneOrig = '/service/manage/scene/uploadScene'
+
 //勘验笔录信息
 //勘验笔录信息
 export const caseInquestInfoOld = "/fusion/caseInquestCriminal/info";
 export const caseInquestInfoOld = "/fusion/caseInquestCriminal/info";
 export const caseInquestOpt = "/fusion/caseInquestCriminal/saveOrUpdate";
 export const caseInquestOpt = "/fusion/caseInquestCriminal/saveOrUpdate";
@@ -274,7 +277,14 @@ export const caseaddOrUpdate = '/service/manage/case/addOrUpdate';
 export const addFusionIds = '/fusion/case/addFusionIds';
 export const addFusionIds = '/fusion/case/addFusionIds';
 export const getByImage = '/fusion/ai/getByImage';
 export const getByImage = '/fusion/ai/getByImage';
 export const getFloor = "/fusion/ai/getFloor/";
 export const getFloor = "/fusion/ai/getFloor/";
-//地图相关
+
+// 照片制卷
+export const casePhotoRollList = "/fusion/casePhotoRoll/getByCaseId";
+export const getAllPhoto = "/fusion/casePhotoRoll/getAllPhoto";
+export const casePhotoRollUpdate = "/fusion/casePhotoRoll/addOrUpdate";
+export const casePhotoRolldel = "/fusion/casePhotoRoll/del";
+export const casePhotoRollgenImage = "/fusion/casePhotoRoll/genImage";
+
 const VITE_VIBE = import.meta.env.VITE_VIBE;
 const VITE_VIBE = import.meta.env.VITE_VIBE;
 let getTipss = "/s/api/gettips";
 let getTipss = "/s/api/gettips";
 let getTipsNames = "/s/api/gettips_name";
 let getTipsNames = "/s/api/gettips_name";

+ 36 - 0
src/store/case.ts

@@ -11,6 +11,8 @@ import {
   repCaseScenes,
   repCaseScenes,
   setCasePsw,
   setCasePsw,
   addFusionIds,
   addFusionIds,
+  uploadSceneCheck,
+  uploadSceneOrig,
   syncInfo,
   syncInfo,
   updateCaseFile,
   updateCaseFile,
   caseInquestInfo,
   caseInquestInfo,
@@ -53,6 +55,11 @@ import {
   caseOverviewList,
   caseOverviewList,
   caseOverviewExport,
   caseOverviewExport,
   packageData,
   packageData,
+  casePhotoRollList,
+  getAllPhoto,
+  casePhotoRollUpdate,
+  casePhotoRolldel,
+  casePhotoRollgenImage,
 } from "@/request";
 } from "@/request";
 import { strToParams, isEmpty, appendParamsToUrl } from "@/util";
 import { strToParams, isEmpty, appendParamsToUrl } from "@/util";
 import { router } from "@/router";
 import { router } from "@/router";
@@ -224,6 +231,22 @@ export const getcaseLists = async (caseId: number): Promise<Scene[]> => {
   return (await axiosGet(isdyrh, { params: { caseId } })).data;
   return (await axiosGet(isdyrh, { params: { caseId } })).data;
 };
 };
 
 
+export const getCasePhotoRollList = async (caseId = router.currentRoute.value?.params?.caseId): Promise<Scene[]> => {
+  return (await axios.get(casePhotoRollList, { params: { caseId } })).data;
+};
+export const getAllPhotoList = async (filesTypeId): Promise<Scene[]> => {
+  return (await axios.get(getAllPhoto, { params: { caseId: router.currentRoute.value?.params?.caseId, filesTypeId } })).data;
+};
+export const casePhotoUpdate = (params) =>
+  axios.post(casePhotoRollUpdate, params);
+export const casePhotoUpImage = (params) =>
+  axios.post(casePhotoRollgenImage, params);
+export const casePhotodel = (params) => {
+  console.log(params)
+  return  axios.post(casePhotoRolldel, params);
+}
+  
+
 export const updateByTreeFileLists = async (caseId = router.currentRoute.value?.params?.caseId): Promise<Scene[]> => {
 export const updateByTreeFileLists = async (caseId = router.currentRoute.value?.params?.caseId): Promise<Scene[]> => {
   console.log('updateByTreeFileLists1');
   console.log('updateByTreeFileLists1');
   let list = (await axiosGet(getByTree, { params: { caseId:caseId } })).data
   let list = (await axiosGet(getByTree, { params: { caseId:caseId } })).data
@@ -340,6 +363,19 @@ export const uploadFiles = (data) => axios<undefined>({
   data: data,
   data: data,
 });
 });
 
 
+export const SceneCheck = (data) => axios<undefined>({
+  method: "POST",
+  url: uploadSceneCheck,
+  data: data,
+});
+
+export const SceneOrig = (data) => axios<undefined>({
+  method: "POST",
+  url: uploadSceneOrig,
+  data: data,
+});
+
+
 export type CaseImg = {
 export type CaseImg = {
   id: number;
   id: number;
   caseId: number;
   caseId: number;

+ 2 - 2
src/store/user.ts

@@ -1,4 +1,4 @@
-import { getLocal, changSaveLocal } from "@/util/localUtil";
+import { getLocal, changSaveLocal, getCookieToken } from "@/util/localUtil";
 import { ref, watchEffect } from "vue";
 import { ref, watchEffect } from "vue";
 import {
 import {
   PaggingReq,
   PaggingReq,
@@ -49,7 +49,7 @@ export const getUsers = async (deptId?: string) =>
   (await axios.get<UserInfo[]>(getUserListSelect, { params: { deptId } })).data;
   (await axios.get<UserInfo[]>(getUserListSelect, { params: { deptId } })).data;
 // 当前用户的信息
 // 当前用户的信息
 export const user = ref({
 export const user = ref({
-  token: getLocal("token", false) || "",
+  token: getLocal("token", false) || getCookieToken('token'),
   info: getLocal("info", {} as UserInfo),
   info: getLocal("info", {} as UserInfo),
 });
 });
 export const setToken = (token: string) => {
 export const setToken = (token: string) => {

+ 57 - 41
src/util/index.ts

@@ -57,30 +57,22 @@ export const debounce = <T extends (...args: any) => any>(
     }, delay);
     }, delay);
   };
   };
 };
 };
-export const throttle = <Args extends any[]>(
-  fn: (...args: Args) => void,
-  deley: number
-) => {
-  let valib = false;
-  let lastCtx: { self: any; args: Args } | null = null;
-
-  return function (this: any, ...args: Args) {
-    lastCtx = {
-      args,
-      self: this,
-    };
-    if (valib) {
-      return;
+export function throttle<T extends (...args: any[]) => Promise<any> | any>(
+  func: T,
+  delay: number
+): T {
+  let lastTime = 0;
+
+  return function (...args: Parameters<T>) {
+    const now = Date.now();
+
+    // 立即执行模式:距离上次执行超过延迟时间就立刻跑
+    if (now - lastTime >= delay) {
+      lastTime = now;
+      return func.apply(this, args);
     }
     }
-    const currentCtx = lastCtx;
-    valib = true;
-
-    setTimeout(() => {
-      fn.apply(currentCtx.self, currentCtx.args);
-      valib = false;
-    }, deley);
-  };
-};
+  } as T;
+}
 
 
 function randomWord(randomFlag: boolean, min: number, max?: number) {
 function randomWord(randomFlag: boolean, min: number, max?: number) {
   let str = "";
   let str = "";
@@ -327,24 +319,23 @@ export const mixEnum = <T, K>(enum1: T, enum2: K): T | K => {
   };
   };
 };
 };
 
 
-// 字符串转params对象
-export const strToParams = (str: string) => {
-  if (str[0] === "?") {
-    str = str.substr(1);
-  }
-
-  const result: { [key: string]: string } = {};
-  const splitRG = /([^=&]+)(?:=([^&]*))?&?/;
-
-  let rgRet;
-  while ((rgRet = str.match(splitRG))) {
-    result[rgRet[1]] = rgRet[2] === undefined ? "" : rgRet[2];
-    str = str.substr(rgRet[0].length);
-  }
-
-  return result;
-};
-
+/**
+ * 解析 URL 中的所有参数,返回对象
+ * @param {string} url 可选,不传默认取当前页面 url
+ * @returns {object} 参数对象
+ */
+export const strToParams = (url = window.location.href) => {
+  // 取出 ? 后面的参数部分
+  const queryStr = url.split('?')[1] || ''
+  if (!queryStr) return {}
+
+  // 解析成键值对
+  const params = {}
+  new URLSearchParams(queryStr).forEach((val, key) => {
+    params[key] = val
+  })
+  return params
+}
 export const getDomMatrix = (dom: HTMLElement) => {
 export const getDomMatrix = (dom: HTMLElement) => {
   const str = getComputedStyle(dom, null).getPropertyValue("transform");
   const str = getComputedStyle(dom, null).getPropertyValue("transform");
   const matrix2d = str
   const matrix2d = str
@@ -573,3 +564,28 @@ export function appendParamsToUrl(url, params = {}) {
   // 判断 URL 已有 ?:有则加 &,无则加 ?
   // 判断 URL 已有 ?:有则加 &,无则加 ?
   return `${url}${url.includes("?") ? "&" : "?"}${queryStr}`;
   return `${url}${url.includes("?") ? "&" : "?"}${queryStr}`;
 }
 }
+  /**
+ * 从 URL 中提取文件名
+ * @param {string} url - 完整的 URL 地址
+ * @returns {string} 提取到的文件名,无文件名时返回空字符串
+ */
+  export function getFileNameFromUrl(url) {
+  try {
+    // 1. 解析 URL,分离出路径部分(去除参数和锚点)
+    const parsedUrl = new URL(url);
+    let path = parsedUrl.pathname;
+
+    // 2. 去除路径末尾的斜杠(如果有)
+    path = path.replace(/\/$/, '');
+
+    // 3. 提取最后一个斜杠后的部分(即文件名)
+    const fileName = path.split('/').pop();
+
+    // 4. 如果提取到的是目录名(无扩展名),返回空字符串
+    return fileName.includes('.') ? fileName : '';
+  } catch (error) {
+    // 处理无效 URL 的情况
+    console.error('URL 格式错误:', error);
+    return '';
+  }
+}

+ 11 - 0
src/util/localUtil.ts

@@ -24,3 +24,14 @@ export const changSaveLocal = <T>(key: string, get: () => T) => {
     deep: true,
     deep: true,
   });
   });
 };
 };
+
+export const getCookieToken = <T>(name: string) => {
+    const cookies = document.cookie.split(';'); // 将Cookie拆分为数组
+    for (let i = 0; i < cookies.length; i++) {
+        const pair = cookies[i].trim().split('='); // 拆分键和值
+        if (pair[0] === name) {
+            return pair[1]; // 返回token值
+        }
+    }
+    return null; // 未找到返回null
+};

+ 66 - 25
src/view/abstract/index.vue

@@ -42,11 +42,17 @@
           </el-col>
           </el-col>
           <el-col :span="8">
           <el-col :span="8">
             <div class="item textellipsis">
             <div class="item textellipsis">
+              <span>负责人:</span>
+              <span :title="bindExample.commander">{{ bindExample.commander }}</span>
+            </div>
+          </el-col>
+          <el-col :span="4">
+            <div class="item textellipsis">
               <span>是否命案:</span>
               <span>是否命案:</span>
               <span>{{ caseInfoData.homicideCase?'是':'否' }}</span>
               <span>{{ caseInfoData.homicideCase?'是':'否' }}</span>
             </div>
             </div>
           </el-col>
           </el-col>
-          <el-col :span="8">
+          <el-col :span="4">
             <div class="item textellipsis">
             <div class="item textellipsis">
               <span>是否刑案:</span>
               <span>是否刑案:</span>
               <span>{{ caseInfoData.criminalCase?'是':'否' }}</span>
               <span>{{ caseInfoData.criminalCase?'是':'否' }}</span>
@@ -71,32 +77,38 @@
           </el-col>
           </el-col>
           <el-col :span="8">
           <el-col :span="8">
             <div class="item textellipsis">
             <div class="item textellipsis">
-              <span>接警时间:</span>
-              <span>{{ ruleForm.alarmTime }}</span>
+              <span>指派/报告单位:</span>
+              <span :title="ruleForm.assignDept">{{ ruleForm.assignDept }}</span>
             </div>
             </div>
           </el-col>
           </el-col>
           <el-col :span="8">
           <el-col :span="8">
             <div class="item textellipsis">
             <div class="item textellipsis">
-              <span>报警人:</span>
-              <span :title="ruleForm.alarmName">{{ ruleForm.alarmName }}</span>
+              <span>指派方式:</span>
+              <span>{{ ruleForm.assignType }}</span>
             </div>
             </div>
           </el-col>
           </el-col>
           <el-col :span="8">
           <el-col :span="8">
             <div class="item textellipsis">
             <div class="item textellipsis">
-              <span>现场勘验单位:</span>
-              <span :title="ruleForm.inquestDept">{{ ruleForm.inquestDept }}</span>
+              <span>接警时间:</span>
+              <span>{{ ruleForm.alarmTime }}</span>
             </div>
             </div>
           </el-col>
           </el-col>
           <el-col :span="8">
           <el-col :span="8">
             <div class="item textellipsis">
             <div class="item textellipsis">
-              <span>指派/报告单位:</span>
-              <span :title="ruleForm.assignDept">{{ ruleForm.assignDept }}</span>
+              <span>报警人:</span>
+              <span :title="ruleForm.alarmName">{{ ruleForm.alarmName }}</span>
             </div>
             </div>
           </el-col>
           </el-col>
           <el-col :span="8">
           <el-col :span="8">
             <div class="item textellipsis">
             <div class="item textellipsis">
-              <span>指派方式:</span>
-              <span>{{ ruleForm.assignType }}</span>
+              <span>报警信息:</span>
+              <span :title="ruleForm.alarmMsg">{{ ruleForm.alarmMsg }}</span>
+            </div>
+          </el-col>
+          <el-col :span="8">
+            <div class="item textellipsis">
+              <span>现场勘验单位:</span>
+              <span :title="ruleForm.inquestDept">{{ ruleForm.inquestDept }}</span>
             </div>
             </div>
           </el-col>
           </el-col>
           <el-col :span="8">
           <el-col :span="8">
@@ -210,6 +222,22 @@
               </el-form-item>
               </el-form-item>
             </el-col>
             </el-col>
 
 
+            <el-col :span="8">
+              <el-form-item label="负责人">
+                <el-input
+                  v-model="bindExample.commander"
+                  placeholder="请输入"
+                  clearable
+                  @blur="submit"
+                  maxlength="100"
+                  disabled
+                >
+                  <template #append>
+                    <el-button :icon="Search" @click="searchAMapAddress" />
+                  </template>
+                </el-input>
+              </el-form-item>
+            </el-col>
             <el-col :span="4">
             <el-col :span="4">
               <el-form-item label="是否命案" prop="region">
               <el-form-item label="是否命案" prop="region">
                 <el-select
                 <el-select
@@ -276,6 +304,26 @@
                 /> </el-form-item
                 /> </el-form-item
             ></el-col>
             ></el-col>
             <el-col :span="8"
             <el-col :span="8"
+              ><el-form-item label="指派/报告单位">
+                <el-input
+                  v-model="ruleForm.assignDept"
+                  @blur="submit"
+                  placeholder="请输入"
+                  show-word-limit
+                  maxlength="100"
+                /> </el-form-item
+            ></el-col>
+            <el-col :span="8"
+              ><el-form-item label="指派方式">
+                <el-input
+                  v-model="ruleForm.assignType"
+                  @blur="submit"
+                  placeholder="请输入"
+                  show-word-limit
+                  maxlength="100"
+                /> </el-form-item
+            ></el-col>
+            <el-col :span="8"
               ><el-form-item label="接警时间">
               ><el-form-item label="接警时间">
                 <el-date-picker
                 <el-date-picker
                   v-model="ruleForm.alarmTime"
                   v-model="ruleForm.alarmTime"
@@ -298,9 +346,9 @@
                 /> </el-form-item
                 /> </el-form-item
             ></el-col>
             ></el-col>
             <el-col :span="8"
             <el-col :span="8"
-              ><el-form-item label="现场勘验单位">
+              ><el-form-item label="报警信息">
                 <el-input
                 <el-input
-                  v-model="ruleForm.inquestDept"
+                  v-model="ruleForm.alarmMsg"
                   @blur="submit"
                   @blur="submit"
                   placeholder="请输入"
                   placeholder="请输入"
                   show-word-limit
                   show-word-limit
@@ -308,19 +356,9 @@
                 /> </el-form-item
                 /> </el-form-item
             ></el-col>
             ></el-col>
             <el-col :span="8"
             <el-col :span="8"
-              ><el-form-item label="指派/报告单位">
-                <el-input
-                  v-model="ruleForm.assignDept"
-                  @blur="submit"
-                  placeholder="请输入"
-                  show-word-limit
-                  maxlength="100"
-                /> </el-form-item
-            ></el-col>
-            <el-col :span="8"
-              ><el-form-item label="指派方式">
+              ><el-form-item label="现场勘验单位">
                 <el-input
                 <el-input
-                  v-model="ruleForm.assignType"
+                  v-model="ruleForm.inquestDept"
                   @blur="submit"
                   @blur="submit"
                   placeholder="请输入"
                   placeholder="请输入"
                   show-word-limit
                   show-word-limit
@@ -413,6 +451,7 @@ const ruleForm = ref({
   times: [],
   times: [],
   resource: "",
   resource: "",
   desc: "",
   desc: "",
+  alarmMsg: ''
 });
 });
 const bindExample = ref({
 const bindExample = ref({
   caseTitle: "",
   caseTitle: "",
@@ -428,6 +467,8 @@ const bindExample = ref({
   latAndLong: "",
   latAndLong: "",
   latAndLongs: "",
   latAndLongs: "",
   criminalType: "",
   criminalType: "",
+  commander:'',
+  alarmMsg:'',
 });
 });
 const criminalType = [
 const criminalType = [
   "杀人",
   "杀人",

Разница между файлами не показана из-за своего большого размера
+ 2021 - 0
src/view/case/photos/canvas-photo-editor.js


Разница между файлами не показана из-за своего большого размера
+ 653 - 503
src/view/case/photos/index.vue


+ 298 - 0
src/view/case/photos/style.scss

@@ -0,0 +1,298 @@
+
+.phoneContent{
+  canvas:focus {
+  outline: none;
+  border: none;
+}
+  .header{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 32px 48px 24px 48px;
+    background-color: #fff;
+    border: 1px solid #E6E6E6;
+    .header-left{
+      font-weight: 400;
+      font-size: 32px;
+      color: rgba(0,0,0,0.85);
+      line-height: 32px;
+      max-width: 50%;
+        white-space: nowrap;    /* 不换行 */
+  overflow: hidden;       /* 超出隐藏 */
+  text-overflow: ellipsis;/* 显示 ... */
+
+    }
+    .header-main{
+      font-size: 24px;
+      .border-icon{
+        border-left: 1px solid #e5e7eb;
+        border-right: 1px solid #e5e7eb;
+        display: inline-block;
+        line-height: 16px;
+        height: 20px;
+        margin: 0 24px;
+        padding: 0 24px;
+      }
+      i{
+        cursor: pointer;
+      }
+      .avtive{
+        background: rgba(16,155,224,0.3);
+      }
+      .el-dropdown{
+        width: 20px !important;
+        margin-right: 20px;
+      }
+      .disable{
+            cursor: not-allowed;
+      }
+    }
+    .header-right{
+      .view {
+        height: 40px;
+        width: 88px;
+        text-align: center;
+        line-height: 40px;
+        box-shadow: 0px 2px 0px 0px rgba(0, 0, 0, 0.04);
+        border-radius: 2px;
+        border: 1px solid #26559b;
+        font-size: 14px;
+        color: #26559b;
+        cursor: pointer;
+      }
+    }
+  }
+}
+.photo-editor {
+  display: flex;
+  height: calc(100vh - 98px);
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+  overflow: hidden;
+}
+
+/* 左侧面板 */
+.left-panel {
+  width: 510px;
+  min-width: 510px;
+  background: #fff;
+  border-right: 1px solid #e5e7eb;
+  overflow-y: auto;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  box-sizing: border-box;
+  position: relative;
+  &-title{
+    font-weight: bold;
+    padding: 30px 0 12px 0;
+    font-size: 16px;
+    color: rgba(0,0,0,0.85);
+    line-height: 22px;
+  }
+  &-top{
+    padding: 0 48px 100px 48px;
+    max-height: calc(100vh - 98px);
+    overflow-y: scroll;
+    scrollbar-width: none; /* Firefox */
+    min-height: calc(100vh - 270px);
+    ::-webkit-scrollbar { display: none; }
+  }
+}
+
+.photo-list {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
+}
+.zwsj{
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    text-align: center;
+    width: 400px;
+    margin: 0 auto;
+}
+.photo-item {
+  position: relative;
+  border: 2px solid transparent;
+  border-radius: 4px;
+  cursor: pointer;
+  overflow: hidden;
+}
+
+.photo-item.selected {
+  border-color: #3b82f6;
+}
+
+.photo-item img {
+  width: 128px;
+  height: 128px;
+  display: block;
+  object-fit: cover;
+}
+
+.used-tag {
+  position: absolute;
+  top: 8px;
+  left: 8px;
+  width: 24px;
+  height: 24px;
+  background: #ffffff;border: 1px solid #B2B2B2;
+  font-size: 18px;
+  text-align: center;
+  line-height: 28px;
+  border-radius: 50%;
+}
+.selectImg{
+  display: inline-block;
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 22px;
+  font-family: Roboto, Roboto;
+  font-weight: 400;
+  font-size: 12px;
+  color: #E6F7FF;
+  line-height: 22px;
+  text-align: center;
+  background: #26559B;
+  border-radius:10px 0 0 0 ;
+  padding: 0 4px 0 8px;
+}
+
+.layout-setting {
+  padding: 16px;
+  background: #ffffff;
+  border-radius: 6px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.radio-label {
+  display: block;
+  margin: 8px 0;
+  cursor: pointer;
+}
+
+.auto-layout-bar button {
+  width: 100%;
+  padding: 10px;
+  background: #3b82f6;
+  color: white;
+  border: none;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.auto-layout-bar button:hover {
+  background: #2563eb;
+}
+.auto-layout-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 25px 48px;
+  position: absolute;
+  border-top: 1px solid #E6E6E6;
+  background: #fff;
+  bottom: 0;
+width: 100%;
+        .view {
+        height: 40px;
+        width: 88px;
+        text-align: center;
+        line-height: 40px;
+        box-shadow: 0px 2px 0px 0px rgba(0, 0, 0, 0.04);
+        border-radius: 2px;
+        border: 1px solid #26559b;
+        font-size: 14px;
+        color: #26559b;
+        cursor: pointer;
+      }
+}
+/* 右侧面板 */
+.right-panel {
+  flex: 1;
+  display: flex;
+  max-width: calc(100vw - 510px);
+  flex-direction: column;
+  padding: 20px;
+  background-color: #f3f4f6;
+}
+
+.page-control-bar {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  margin-bottom: 16px;
+  padding: 12px;
+  background: #fff;
+  border-radius: 6px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.page-control-bar 
+button {
+  padding: 8px 16px;
+  background: #3b82f6;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: background 0.2s;
+}
+
+.page-control-bar button:disabled {
+  background: #9ca3af;
+  cursor: not-allowed;
+}
+
+.page-control-bar button:hover:not(:disabled) {
+  background: #2563eb;
+}
+
+/* Canvas容器 */
+.canvas-container {
+  flex: 1;
+  border: 1px solid #e5e7eb;
+  background-color: #efefef;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+  cursor: grab;
+}
+
+.canvas-content {
+  display: block;
+  transition: transform 0.05s ease;
+  cursor: grab;
+}
+
+.canvas-content:active {
+  cursor: grabbing;
+}
+
+.control-bar {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 20px;
+  margin-top: 16px;
+  padding: 12px;
+  background: #fff;
+  border-radius: 6px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.control-bar button {
+  padding: 8px 16px;
+  border: 1px solid #d1d5db;
+  border-radius: 4px;
+  background: white;
+  cursor: pointer;
+}
+
+.control-bar button:hover {
+  background-color: #f3f4f6;
+}

+ 150 - 0
src/view/case/photos/usePDF.js

@@ -0,0 +1,150 @@
+// composables/usePDF.js
+import { ref } from 'vue'
+import html2pdf from 'html2pdf.js'
+import { PDFDocument, rgb } from 'pdf-lib'
+import fontkit from '@pdf-lib/fontkit' // 关键:导入适配版fontkit
+
+export function usePDF() {
+  const loading = ref(false)
+  const error = ref(null)
+
+  // 基础HTML转PDF(单页)
+  const generateSinglePDF = async (element, options = {}) => {
+    const defaultOptions = {
+      margin: [10, 10, 10, 10],
+      image: { type: 'jpeg', quality: 0.98 },
+      html2canvas: {
+        scale: 2,
+        letterRendering: true,
+        useCORS: true
+      },
+      jsPDF: {
+        unit: 'mm',
+        format: 'a4',
+        orientation: 'portrait'
+      },
+      filename: 'temp.pdf',
+      save: false
+    }
+
+    const mergedOptions = { ...defaultOptions, ...options }
+    const pdfBlob = await html2pdf().set(mergedOptions).from(element).output('blob')
+    return pdfBlob
+  }
+
+  // 加载中文字体(修复fontkit注册问题)
+  const loadChineseFont = async (pdfDoc) => {
+    // 1. 注册fontkit到PDFDocument(核心修复)
+    pdfDoc.registerFontkit(fontkit)
+
+    // 2. 加载中文字体文件(思源黑体,确保路径正确)
+    const fontUrl = '/SourceHanSansSC-Regular.otf' 
+    // try {
+      const fontResponse = await fetch(fontUrl)
+      if (!fontResponse.ok) throw new Error('字体文件加载失败')
+      const fontArrayBuffer = await fontResponse.arrayBuffer()
+      
+      // 3. 嵌入中文字体
+      const font = await pdfDoc.embedFont(fontArrayBuffer)
+      return font
+    // } catch (err) {
+    //   console.error('字体加载失败:', err)
+    //   throw new Error('中文字体加载失败,请检查字体文件路径')
+    // }
+  }
+
+  // 物理分页生成PDF(最终完整版)
+  const generateWithPagination = async (pageElements, options = {}) => {
+    loading.value = true
+    error.value = null
+
+    try {
+      const { filename = '中文分页文档.pdf' } = options
+      
+      // 1. 创建PDF文档实例
+      const finalPdfDoc = await PDFDocument.create()
+
+      // 2. 加载中文字体
+      const chineseFont = await loadChineseFont(finalPdfDoc)
+
+      // 3. 遍历所有分页节点,逐个生成并合并页面
+      for (let i = 0; i < pageElements.length; i++) {
+        const element = pageElements[i]
+        if (!element) continue // 跳过空节点
+
+        // 生成当前页的PDF Blob
+        const pageBlob = await generateSinglePDF(element, options)
+        const pageArrayBuffer = await new Response(pageBlob).arrayBuffer()
+        
+        // 加载临时PDF并复制页面
+        const pagePdfDoc = await PDFDocument.load(pageArrayBuffer)
+        const [copiedPage] = await finalPdfDoc.copyPages(pagePdfDoc, [0])
+
+        // 绘制中文页码(支持中文渲染)
+        const { width, height } = copiedPage.getSize()
+        copiedPage.drawText(`第 ${i + 1} 页 / 共 ${pageElements.length} 页`, {
+          x: width / 2 - 60, // 微调位置适配中文字符
+          y: 12,             // 页码在页面底部
+          size: 12,          // 字体大小
+          font: chineseFont, // 使用嵌入的中文字体
+          color: rgb(0.3, 0.3, 0.3) // 灰色页码
+        })
+
+        // 添加到最终PDF(物理分页核心)
+        finalPdfDoc.addPage(copiedPage)
+      }
+
+      // 4. 生成并下载PDF
+      const finalPdfBytes = await finalPdfDoc.save()
+      const finalPdfBlob = new Blob([finalPdfBytes], { type: 'application/pdf' })
+      
+      // 原生下载(无需file-saver)
+      const downloadLink = document.createElement('a')
+      downloadLink.href = URL.createObjectURL(finalPdfBlob)
+      downloadLink.download = filename
+      downloadLink.click()
+      URL.revokeObjectURL(downloadLink.href)
+
+      return true
+    } catch (err) {
+      error.value = `PDF生成失败:${err.message}`
+      console.error('分页PDF生成错误:', err)
+      return false
+    } finally {
+      loading.value = false
+    }
+  }
+
+  // 原有HTML转PDF方法(保持兼容)
+  const generateFromHTML = async (element, options = {}) => {
+    loading.value = true
+    error.value = null
+
+    try {
+      const defaultOptions = {
+        margin: [10, 10, 10, 10],
+        filename: 'document.pdf',
+        image: { type: 'jpeg', quality: 0.98 },
+        html2canvas: { scale: 2, letterRendering: true, useCORS: true },
+        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
+      }
+
+      const mergedOptions = { ...defaultOptions, ...options }
+      await html2pdf().set(mergedOptions).from(element).save()
+      return true
+    } catch (err) {
+      error.value = err.message
+      console.error('PDF生成失败:', err)
+      return false
+    } finally {
+      loading.value = false
+    }
+  }
+
+  return {
+    loading,
+    error,
+    generateFromHTML,
+    generateWithPagination // 物理分页核心方法
+  }
+}

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

@@ -26,6 +26,7 @@
       <div class="headerRight flex" style="align-items: center">
       <div class="headerRight flex" style="align-items: center">
         <div
         <div
           @click="handleShowAi"
           @click="handleShowAi"
+          v-if="showAI"
           style="
           style="
             color: #26559b;
             color: #26559b;
             cursor: pointer;
             cursor: pointer;
@@ -553,6 +554,7 @@
                 v-for="item in aiImgData.list"
                 v-for="item in aiImgData.list"
                 :key="item.num"
                 :key="item.num"
                 :label="item.title"
                 :label="item.title"
+                :disabled="!item.viewAuth"
                 :value="item.url"
                 :value="item.url"
               />
               />
             </el-select>
             </el-select>
@@ -652,6 +654,7 @@ let sseClient = null;
 console.log("router.currentRoute", router.currentRoute.value?.params);
 console.log("router.currentRoute", router.currentRoute.value?.params);
 const fileId = computed(() => router.currentRoute.value?.params?.fileId);
 const fileId = computed(() => router.currentRoute.value?.params?.fileId);
 const caseId = computed(() => router.currentRoute.value?.params?.caseId);
 const caseId = computed(() => router.currentRoute.value?.params?.caseId);
+const showAI = computed(() => router.currentRoute.value?.query?.show);
 const isDisableExport = ref(false);
 const isDisableExport = ref(false);
 const aiImgShow = ref(false);
 const aiImgShow = ref(false);
 const isOption = ref(false);
 const isOption = ref(false);
@@ -749,10 +752,11 @@ const handleCopy = () => {
 const handleShowAi = async () => {
 const handleShowAi = async () => {
   const list = await getFloorList(caseId.value);
   const list = await getFloorList(caseId.value);
   aiImgData.value.list = list.filter((i) => i.url);
   aiImgData.value.list = list.filter((i) => i.url);
+  let item = list.find(ele => ele.viewAuth) || {}
   aiImgData.value.src = aiImgData.value.src
   aiImgData.value.src = aiImgData.value.src
     ? aiImgData.value.src
     ? aiImgData.value.src
-    : list[0]?.url;
-  aiImgData.value.Aisrc = list[0]?.freeSpaceUrl
+    : item.url;
+  aiImgData.value.Aisrc = item.freeSpaceUrl
   aiImgData.value.loading = false;
   aiImgData.value.loading = false;
   aiImgShow.value = true;
   aiImgShow.value = true;
   isOption.value = false;
   isOption.value = false;
@@ -856,11 +860,11 @@ const initSSE = (url) => {
     console.log('message连接成功', data);
     console.log('message连接成功', data);
     if(data && data.text){
     if(data && data.text){
       aiImgData.value.result += data.text
       aiImgData.value.result += data.text
+      aiImgData.value.loading = false
     }
     }
     if(outputs && outputs.text){
     if(outputs && outputs.text){
       aiImgData.value.result = outputs.text
       aiImgData.value.result = outputs.text
     }
     }
-    aiImgData.value.loading = false
   });
   });
 
 
   // // 监听自定义事件(服务器指定 event: customEvent)
   // // 监听自定义事件(服务器指定 event: customEvent)
@@ -873,6 +877,10 @@ const initSSE = (url) => {
     isOption.value = false;
     isOption.value = false;
     messages.value.push({ type: '错误', data: err.message });
     messages.value.push({ type: '错误', data: err.message });
     console.error('POST SSE 错误:', err);
     console.error('POST SSE 错误:', err);
+    ElMessage.error("AI服务未开通,该功能暂不可用");
+    aiImgData.value.loading = false
+    aiImgData.value.result = false;
+    aiImgData.value.result = ''
   });
   });
 
 
   // 监听连接关闭
   // 监听连接关闭

+ 2 - 2
src/view/layout/index.vue

@@ -108,9 +108,9 @@ updateByTreeFileLists();
 const hiddenSlide = computed(
 const hiddenSlide = computed(
   () => !menuRouteNames.includes(router.currentRoute.value.name as string)
   () => !menuRouteNames.includes(router.currentRoute.value.name as string)
 );
 );
-
+console.log(router.currentRoute.value, "hiddenSlide");
 const hiddenTop = computed(
 const hiddenTop = computed(
-  () => router.currentRoute.value.name != 'records' && router.currentRoute.value.name != 'login' && router.currentRoute.value.name != 'register' && router.currentRoute.value.name != 'forgetpassword'
+  () => router.currentRoute.value.name != 'drawCasePhotos' && router.currentRoute.value.name != 'records' && router.currentRoute.value.name != 'login' && router.currentRoute.value.name != 'register' && router.currentRoute.value.name != 'forgetpassword'
 
 
 );
 );
 const handleCommand = (command) => {
 const handleCommand = (command) => {

+ 3 - 2
src/view/layout/slide/index.vue

@@ -29,7 +29,7 @@ import subMenu from "./submenu.vue";
 import { getPermissionRoutes } from "@/store/permission";
 import { getPermissionRoutes } from "@/store/permission";
 import { router } from "@/router";
 import { router } from "@/router";
 import { recursiveSearch } from "@/util/index.ts";
 import { recursiveSearch } from "@/util/index.ts";
-import { updateByTreeFileLists, show, getCaseList } from "@/store/case";
+import { updateByTreeFileLists, show, getCaseList, getCasePhotoRollList } from "@/store/case";
 
 
 const props = defineProps<{ names: string[] }>();
 const props = defineProps<{ names: string[] }>();
 const activeName = ref(router.currentRoute.value.name || "scene");
 const activeName = ref(router.currentRoute.value.name || "scene");
@@ -48,13 +48,14 @@ function getList() {
     console.log('recursiveSearch', routes.value, props.names)
     console.log('recursiveSearch', routes.value, props.names)
     let homeList = await getCaseList({caseId: caseId.value, type: 'scene'})
     let homeList = await getCaseList({caseId: caseId.value, type: 'scene'})
     let diversityList = await getCaseList({caseId: caseId.value, type: 'fusion'})
     let diversityList = await getCaseList({caseId: caseId.value, type: 'fusion'})
+    let casePhotoList = await getCasePhotoRollList(caseId.value) || []
     let showList = ['scene','diversity', 'originalPhoto','photos', 'sceneimg', 'dossier']
     let showList = ['scene','diversity', 'originalPhoto','photos', 'sceneimg', 'dossier']
     let showObj = {
     let showObj = {
       'scene': homeList.length > 0,
       'scene': homeList.length > 0,
       'diversity': diversityList.length > 0,
       'diversity': diversityList.length > 0,
       'originalPhoto': recursiveSearch(res.find(ele => ele.filesTypeName == '原始照片').childrenList),
       'originalPhoto': recursiveSearch(res.find(ele => ele.filesTypeName == '原始照片').childrenList),
       'photos': recursiveSearch(res.find(ele => ele.filesTypeName == '痕迹物证').childrenList),
       'photos': recursiveSearch(res.find(ele => ele.filesTypeName == '痕迹物证').childrenList),
-      'sceneimg': recursiveSearch(res.find(ele => ele.filesTypeName == '三录材料').childrenList),
+      'sceneimg': recursiveSearch(res.find(ele => ele.filesTypeName == '三录材料').childrenList) || casePhotoList.length,
       'dossier': recursiveSearch(res.find(ele => ele.filesTypeName == '案件卷宗').childrenList),
       'dossier': recursiveSearch(res.find(ele => ele.filesTypeName == '案件卷宗').childrenList),
     }
     }
     console.log('recursiveSearch', showObj, res)
     console.log('recursiveSearch', showObj, res)

+ 1 - 12
src/view/layout/top/index.vue

@@ -191,15 +191,4 @@ const updatePwdHandler = async () => {
     cursor: pointer;
     cursor: pointer;
   }
   }
 }
 }
-</style>
-<style lang="scss">
-.promptClass {
-  .el-message-box__header {
-    padding-left: 0 !important;
-    padding-bottom: 0 !important;
-  }
-  .el-message-box__content {
-    padding-left: 32px !important;
-  }
-}
-</style>
+</style>

+ 95 - 0
src/view/material/exportPhotos.vue

@@ -0,0 +1,95 @@
+<template>
+  <el-form
+    ref="form"
+    :model="caseFile"
+    label-width="90px"
+    class="camera-from dispatch-file-from jm-file-upload"
+  >
+    <el-form-item label="排版:">
+      <el-radio-group v-model="exportInfo.paperType">
+        <el-radio value="four">4联卡纸</el-radio>
+        <el-radio value="a4">A4纸</el-radio>
+        <el-radio value="a3">A3纸</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="格式:">
+      <el-radio-group v-model="exportInfo.fileType">
+        <el-radio value="pdf">pdf</el-radio>
+        <el-radio value="jpg">jpg</el-radio>
+      </el-radio-group>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { maxFileSize } from "@/constant/caseFile";
+import { useUpload } from "@/hook/upload";
+import { ElMessage, ElLoading } from "element-plus";
+import { computed, ref, watchEffect, onMounted } from "vue";
+import { addCaseScenes } from "./quisk";
+import { QuiskExpose } from "@/helper/mount";
+import { updateSelectByTreeFileLists } from "@/store/case";
+
+const props = defineProps<{
+  showPagesRef: any;
+  title: string
+}>();
+const exportInfo = ref({
+  paperType: "four",
+  fileType: "pdf",
+})
+onMounted(async () => {
+});
+const submit = () => {
+    if (!props.showPagesRef) {
+      ElMessage.error("加载异常,请刷新浏览器");
+      throw "加载异常,请刷新浏览器";
+    }else{
+       const loading = ElLoading.service({ lock: true, text: "正在导出" });
+        // 延迟执行,让 loading 先显示出来
+      setTimeout(async () => {
+        try {
+          await props.showPagesRef.exportToPDF(
+            exportInfo.value.paperType, props.title, exportInfo.value.fileType
+          );
+          return true;
+        } finally {
+          loading.close();
+        }
+      }, 40);
+      // props.showPagesRef.exportToPDF(exportInfo.value.paperType,props.title, exportInfo.value.fileType)
+      console.log('正在导出2');
+      // loading.close();
+    }
+  };
+defineExpose<QuiskExpose>({
+  submit,
+});
+</script>
+
+<style scoped lang="scss">
+.upload-demo {
+  overflow: hidden;
+}
+
+.file {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  > div {
+    display: flex;
+    align-items: center;
+  }
+
+  .name {
+    margin-left: 10px;
+  }
+}
+.jm-file-upload {
+  // .mtk {
+  //   position: absolute;
+  //   right: 0;
+  //   top: 0;
+  // }
+}
+</style>

+ 11 - 0
src/view/material/quisk.ts

@@ -1,6 +1,8 @@
 import AddCaseFile from "./addCaseFile.vue";
 import AddCaseFile from "./addCaseFile.vue";
+import exportPhotos from "./exportPhotos.vue";
 import AddLibrary from "./addLibrary.vue";
 import AddLibrary from "./addLibrary.vue";
 import AddScenes from "./addScenes.vue";
 import AddScenes from "./addScenes.vue";
+import tableModel from "./tableScene/tableModel.vue";
 import rollMakingList from "./rollMakingList.vue";
 import rollMakingList from "./rollMakingList.vue";
 import AddScenesImg from "./AddScenesImg.vue";
 import AddScenesImg from "./AddScenesImg.vue";
 import setType from "./setType.vue";
 import setType from "./setType.vue";
@@ -12,6 +14,11 @@ export const addCaseFile = quiskMountFactory(AddCaseFile, {
   width: 570,
   width: 570,
 });
 });
 
 
+export const exportCasePhotos = quiskMountFactory(exportPhotos, {
+  title: "导出",
+  enterText: "下 载",
+  width: 570,
+});
 export const addSceneImg1 = quiskMountFactory(AddScenesImg, {
 export const addSceneImg1 = quiskMountFactory(AddScenesImg, {
   title: "导入方位图",
   title: "导入方位图",
   width: 955,
   width: 955,
@@ -30,6 +37,10 @@ export const addrollMaking = quiskMountFactory(rollMakingList, {
   width: 1000,
   width: 1000,
 });
 });
 
 
+export const tableModelScene = quiskMountFactory(tableModel, {
+  title: "实景三维",
+  width: 800,
+});
 
 
 export const addLibraryFile = quiskMountFactory(AddLibrary, {
 export const addLibraryFile = quiskMountFactory(AddLibrary, {
   title: "上传文件",
   title: "上传文件",

Разница между файлами не показана из-за своего большого размера
+ 550 - 78
src/view/material/sceneImg.vue


+ 104 - 0
src/view/material/showpages.vue

@@ -0,0 +1,104 @@
+<template>
+    <div class="canvas-container" ref="parentElement">
+      <canvas
+        ref="canvas"
+        class="canvas-content"
+        @click="handleCanvasClick"
+        @drop="handleDrop"
+        @dragover="handleDragOver"
+      ></canvas>
+    </div>
+</template>
+
+<script setup>
+import { ref, watch, onMounted } from 'vue';
+import { CanvasPhotoEditor } from "@/view/case/photos/canvas-photo-editor.js";
+
+const props = defineProps({
+  // SVG图片的URL
+  content: {
+    type: String,
+    required: true
+  },
+  photos: {
+    type: Array,
+    required: true
+  },
+  pageCount: {
+    type: Number,
+  }
+}); 
+// --- 响应式数据 ---
+const canvas = ref(null);
+const parentElement = ref(null);
+const editor = ref(null);
+
+// 监听属性变化并重新加载/处理SVG
+watch(() => props.content, ()=>{
+  if(editor.value && props.content){
+        let content = typeof props.content === 'string'?JSON.parse(props.content):props.content
+        console.log("newcontent", content, props);
+        editor.value.pages = content.pages;
+        editor.value.pageCount = props.pageCount;
+        editor.value.indexingLineList = content.indexingLineList;
+        editor.value.drawAllPages(props.photos);
+        editor.value.resetPosition(); // 移除scrollToCenter,用resetPosition
+  }
+});
+// --- 初始化 ---
+onMounted(() => {
+  if (canvas.value) {
+    // 创建编辑器实例
+    editor.value = new CanvasPhotoEditor(canvas.value, {
+      pageWidth: 520,
+      pageHeight: 840,
+      canvasBgColor: "#efefef",
+      pageBgColor: "#ffffff",
+      photos: props.photos,
+      scale: 0.5,
+      show: true,
+      updata: (value) => {
+        // selectedPageItem.value = value.selectedPageItem;
+        // selectedPageIndex.value = value.selectedPageIndex;
+        // historylength.value = value.historylength;
+        // currentIndex.value = value.currentIndex;
+        console.log("updata", value);
+      },
+    });
+
+    // 绑定容器(用于居中计算)
+    editor.value.bindScrollWrapper(parentElement.value);
+    console.log("updata", parentElement.value);
+    if(props.content){
+        let content = typeof props.content === 'string'?JSON.parse(props.content):props.content
+        editor.value.pages = content.pages;
+        editor.value.indexingLineList = content.indexingLineList;
+        editor.value.drawAllPages(props.photos);  
+    }
+    // 初始化绘制
+    editor.value.resetPosition(); // 移除scrollToCenter,用resetPosition
+
+    // 同步初始数据
+    // scale.value = editor.value.scale;
+  }
+});
+const exportToPDF = async (paperType, name, fileType = 'pdf') => {
+
+  if(fileType == 'pdf'){
+    await editor.value.exportPagesToPDF(paperType, name);
+  }else{
+    await editor.value.exportPagesAsImages(paperType, name);
+  }
+}
+defineExpose({
+  exportToPDF
+})
+</script>
+
+<style scoped>
+.canvas-container {
+  width: 100%;
+  height: 100%;
+}
+/* 可以根据需要添加更多样式 */
+</style>

+ 43 - 0
src/view/material/tableScene/list.vue

@@ -0,0 +1,43 @@
+<template>
+  <com-head :options="headOptions" v-model="params.pagging.state.query.isObj">
+    <el-form label-width="84px" inline>
+      <slot name="header" />
+      <el-form-item class="searh-btns" style="grid-area: 1 / 6 / 2 / 4">
+        <el-button style="visibility: hidden;" type="primary" @click="params.pagging.refresh">查询</el-button>
+        <el-button class="float-right" type="primary" plain @click="params.pagging.queryReset"
+          >重置</el-button
+        >
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <slot name="content" />
+    <com-pagination
+      @size-change="params.pagging.changPageSize"
+      @current-change="params.pagging.changPageCurrent"
+      :current-page="params.pagging.state.pag.currentPage"
+      :page-size="params.pagging.state.pag.size"
+      :total="params.pagging.state.pag.total"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import comHead from "@/components/head/index.vue";
+import comPagination from "@/components/pagination/index.vue";
+import { SceneType } from "@/store/scene";
+import { SceneTypeDesc } from "@/constant/scene";
+import { useScenePaggingParams } from "./pagging";
+const props = defineProps<{ params: ReturnType<typeof useScenePaggingParams>, upload: boolean }>();
+
+const headOptions = [
+  // { value: SceneType.SWKK, name: SceneTypeDesc[SceneType.SWKK] },
+  // { value: SceneType.SWKJ, name: SceneTypeDesc[SceneType.SWKJ] },
+  // { value: SceneType.SWSS, name: SceneTypeDesc[SceneType.SWSS] },
+  // { value: SceneType.SWSSMX, name: SceneTypeDesc[SceneType.SWSSMX] },
+  // { value: SceneType.SWYDSS, name: SceneTypeDesc[SceneType.SWYDSS] },
+  // { value: SceneType.SWYDMX, name: SceneTypeDesc[SceneType.SWYDMX] },
+  // { value: SceneType.SWMX, name: SceneTypeDesc[SceneType.SWMX] },
+];
+</script>

+ 144 - 0
src/view/material/tableScene/modelContent.vue

@@ -0,0 +1,144 @@
+<template>
+  <div class="body-head">
+    <el-tooltip
+      class="item"
+      effect="dark"
+      :content="`请上传${format}(支持obj/ply/las/osgb/b3dm格式的数据),大小在${size}以内 `"
+      placement="bottom-start"
+      ><el-upload
+        class="upload-demo"
+        :multiple="false"
+        :limit="1"
+        :accept="accept"
+        :show-file-list="false"
+        :http-request="() => {}"
+        :file-list="fileList"
+        :disabled="percentage || !operateIsPermissionByPath('sync')"
+        :before-upload="uploadCheck"
+      >
+        <el-button v-pdpath="'sync'" type="primary">
+          <el-icon><Upload /></el-icon>{{ percentage ? "文件上传中" : "上传数据" }}
+        </el-button>
+      </el-upload>
+    </el-tooltip>
+  </div>
+
+  <el-table
+    :data="pagging.state.table.rows"
+    tooltip-effect="dark"
+    style="width: 100%"
+    size="large"
+  >
+    <el-table-column label="序号" width="70" v-slot:default="{ $index }">
+      <div style="text-align: center">
+        {{ pagging.state.pag.size * (pagging.state.pag.currentPage - 1) + $index + 1 }}
+      </div>
+    </el-table-column>
+    <el-table-column label="标题" prop="modelTitle"></el-table-column>
+    <el-table-column label="原始数据格式" prop="modelDateType"></el-table-column>
+    <el-table-column label="大小" prop="modelSize"></el-table-column>
+    <el-table-column label="上传时间" v-slot:default="{ row }: { row: ModelScene }">
+      {{ getStatusText(row) }}
+    </el-table-column>
+    <el-table-column label="所属架构" prop="deptName"></el-table-column>
+  </el-table>
+
+  <el-dialog
+    :model-value="!!percentage"
+    :show-close="false"
+    title="文件上传中"
+    :close-on-click-modal="false"
+  >
+    <el-progress :percentage="percentage" />
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import {
+  ModelSceneStatus,
+  ModelScene,
+  cancelUploadModelScene,
+  uploadModelScene,
+  delModelScene,
+  getModelSceneStatus,
+} from "@/store/scene";
+import {
+  ModelMaxSize,
+  ModelSceneStatusDesc,
+  ModelSupportFormats,
+  SceneTypePaths,
+} from "@/constant/scene";
+import { confirm } from "@/helper/message";
+import { useUpload } from "@/hook/upload";
+import { ScenePagging } from "./pagging";
+import { watchPolling } from "@/hook/watchPolling";
+import { operateIsPermissionByPath } from "@/directive/permission";
+
+const props = defineProps<{ pagging: ScenePagging }>();
+
+const getStatusText = (scene: ModelScene) => {
+  let desc = ModelSceneStatusDesc[scene.createStatus];
+  if (scene.createStatus === ModelSceneStatus.RUN && scene.progress) {
+    desc += ` ${scene.progress}% `;
+  } else if (scene.createStatus === ModelSceneStatus.SUCCESS) {
+    desc = scene.createTime;
+  }
+  return desc;
+};
+
+const delOrCancel = async (scene: ModelScene) => {
+  const isDel = scene.createStatus !== ModelSceneStatus.RUN;
+  const msg = isDel ? "确定要删除此数据?" : "确定要取消上传吗?";
+
+  if (await confirm(msg)) {
+    isDel ? await delModelScene(scene) : await cancelUploadModelScene(scene);
+    props.pagging.refresh();
+  }
+};
+
+const editHanlder = async (scene: ModelScene) => {
+  if (await editModelScene({ model: scene })) {
+    props.pagging.refresh();
+  }
+};
+
+const {
+  percentage,
+  upload: uploadCheck,
+  fileList,
+  size,
+  format,
+  removeFile,
+  accept,
+} = useUpload({
+  maxSize: ModelMaxSize,
+  formats: ModelSupportFormats,
+  upload: async (file, onPercentage) => {
+    try {
+      await uploadModelScene(file, onPercentage);
+      props.pagging.refresh();
+    } catch {}
+    removeFile();
+  },
+});
+
+// 处理后台正在处理的模型类
+const refreshStatus = (models: ModelScene[]) => {
+  const refreshStatusAll = models.map(async (scene) => {
+    const { status, progress } = await getModelSceneStatus(scene);
+    scene.createStatus = status;
+    scene.progress = progress;
+    if (status == ModelSceneStatus.SUCCESS) {
+      props.pagging.refresh();
+    }
+  });
+  return Promise.all(refreshStatusAll);
+};
+
+watchPolling(() => {
+  const payload = (props.pagging.state.table.rows as ModelScene[]).filter(
+    (item) => item.createStatus === ModelSceneStatus.RUN
+  );
+  return { start: payload.length > 0, payload };
+}, refreshStatus);
+</script>

+ 62 - 0
src/view/material/tableScene/pagging.ts

@@ -0,0 +1,62 @@
+import { usePagging } from "@/hook/pagging";
+import { SceneType, getScenePagging } from "@/store/scene";
+import { computed, reactive, watch, watchEffect } from "vue";
+
+export const useScenePaggingParams = () => {
+  const pagging = usePagging({
+    get: getScenePagging,
+    paramsTemlate: {
+      isObj: 1,
+      sceneType: 0, //SceneType.SWKK,
+      sceneName: "",
+      modelTitle: "",
+      deptId: "",
+      snCode: "",
+    },
+  });
+
+  const isSwmx = computed(() => pagging.state.query.type === SceneType.SWMX);
+  const keyword = computed({
+    get: () =>
+      isSwmx.value
+        ? pagging.state.query.modelTitle
+        : pagging.state.query.sceneName,
+    set: (val: string) => {
+      pagging.state.query.modelTitle = val;
+      pagging.state.query.sceneName = val;
+    },
+  });
+  const sceneType = computed({
+    get: () => pagging.state.query.sceneType,
+    set: (val) => {
+      pagging.state.query.sceneType = val;
+    },
+  });
+
+  let oldSnCode = pagging.state.query.snCode;
+  watchEffect(() => {
+    if (isSwmx.value) {
+      oldSnCode = pagging.state.query.snCode;
+      pagging.state.query.snCode = "";
+    } else {
+      pagging.state.query.snCode = oldSnCode;
+    }
+  });
+
+  watch(
+    () => pagging.state.query.type,
+    () => {
+      pagging.state.pag.currentPage = 1;
+    }
+  );
+
+  const queryResetRaw = pagging.queryReset;
+  pagging.queryReset = () => {
+    const type = pagging.state.query.type;
+    queryResetRaw();
+    pagging.state.query.type = type;
+  };
+
+  return reactive({ pagging, keyword, isSwmx, sceneType });
+};
+export type ScenePagging = ReturnType<typeof useScenePaggingParams>["pagging"];

+ 98 - 0
src/view/material/tableScene/sceneContent.vue

@@ -0,0 +1,98 @@
+<template>
+  <el-table
+    class="mybody-head myhideselecTable"
+    :data="pagging.state.table.rows"
+    ref="tableRef"
+    tooltip-effect="dark"
+    style="width: 100%;height: 250px;"
+    :height="250"
+    size="large"
+    @selection-change="changeSelection"
+    @select="handleSelect"
+  >
+    <!-- -1 计算失败  0 计算中 1 计算成功并可以外网访问,不能编辑 2计算成功只能内网,能编辑 -->
+    <el-table-column type="selection" :selectable="selectable" width="55" />
+    <!-- <el-table-column label="序号" width="70" v-slot:default="{ $index }">
+      <div style="text-align: center">
+        {{ pagging.state.pag.size * (pagging.state.pag.currentPage - 1) + $index + 1 }}
+      </div>
+    </el-table-column> -->
+    <el-table-column width="450" label="场景标题" show-overflow-tooltip prop="sceneName"></el-table-column>
+    <!-- <el-table-column label="案件名称" prop="snCode"></el-table-column> -->
+    <!-- <el-table-column label="浏览数量" prop="viewCount"></el-table-column> -->
+    <el-table-column label="创建时间" prop="createTime" v-slot:default="{ row }">
+      {{ row.createTime.substr(0, 16) }}
+    </el-table-column>
+    <!-- <el-table-column label="状态" v-slot:default="{ row }: { row: QuoteScene }">
+      {{ row.statusString }}
+    </el-table-column> -->
+  </el-table>
+</template>
+
+<script setup lang="ts">
+
+import { getSceneListTree } from "@/store/case";
+import { QuoteSceneStatusDesc } from "@/constant/scene";
+import { confirm } from "@/helper/message";
+import { router } from "@/router";
+import { useScenePaggingParams, ScenePagging } from "./pagging";
+import { QuiskExpose } from "@/helper/mount";
+import { onMounted, ref, watch, watchEffect, computed, nextTick } from "vue";
+// const params = useScenePaggingParams();
+const props = defineProps<{ pagging: ScenePagging, numList: Array<string>, noEditList: Array<string> }>();
+const caseId = computed(() => (router.currentRoute.value?.params?.caseId));
+const tableRef = ref(null);
+const numList = ref([]);
+
+const pagScenes = props.pagging.state.table.rows;
+// caseScenes.value = getSceneListTree()
+const submit = async () => {
+  return numList.value
+  }
+defineExpose<QuiskExpose>({
+  submit
+});
+
+
+
+const isObj = ref(props.pagging.state.query?.isObj);
+const selectable = (row) => {
+  // let selectlist = selectList.find(item => item.type == params.pagging.state.query?.isObj)?.numList;
+  // if (row.inCase) return false;
+  if (props.numList.includes(row.num)) return false;
+  return row.statusString == '计算成功'// && !selectlist.includes(row.num)
+}
+
+let changIng = false;
+
+watchEffect(() => {
+  const type = props.pagging.state.query.isObj;
+  if (!tableRef.value) return;
+}, { flush: 'post' });
+
+// 勾选时:先清空,再选中当前行
+const handleSelect = (selection, row) => {
+  if (!tableRef.value) return;
+  tableRef.value.clearSelection()
+  tableRef.value.toggleRowSelection(row, true)
+  numList.value = row
+}
+const changeSelection = async (selectScenes) => {
+  numList.value = selectScenes[0] || null
+};
+</script>
+<style scoped lang="scss">
+.mybody-head{
+    flex: auto !important;
+}
+</style>
+
+<style lang="scss">
+.myhideselecTable {
+    .el-table__header-wrapper {
+      .el-checkbox__input {
+        display: none !important;
+      }
+    }
+  }
+</style>

+ 52 - 0
src/view/material/tableScene/tableModel.vue

@@ -0,0 +1,52 @@
+<template>
+  <List :params="params" upload>
+    <template v-slot:header>
+      <el-form-item label="场景名称:" style="width: 250px">
+        <el-input v-model="params.keyword" placeholder="请输入"></el-input>
+      </el-form-item>
+    </template>
+    <template v-slot:content>
+      <component
+        :is="component"
+        :numList="[]"
+        :noEditList="noEditList"
+        :pagging="params.pagging"
+        ref="contentRef"
+      />
+    </template>
+  </List>
+</template>
+
+<script setup lang="ts">
+import comSelect from "@/components/company-select/index.vue";
+import List from "./list.vue";
+import SceneContent from "./sceneContent.vue";
+import ModelContent from "./modelContent.vue";
+import { useScenePaggingParams } from "./pagging";
+import { QuiskExpose } from "@/helper/mount";
+import { computed, ref } from "vue";
+defineProps<{ numList: Array<string>, noEditList: Array<string> }>();
+const options = [
+  {
+    value: 0,
+    label: "我的场景",
+  },
+  {
+    value: 1,
+    label: "共享场景",
+  },
+];
+
+const params = useScenePaggingParams();
+console.log("params.isSwmx", params.isSwmx, "ModelContent");
+const component = computed(() => (params.isSwmx ? ModelContent : SceneContent));
+const contentRef = ref(null);
+const submit = async () => {
+  // replaceCaseScenes(props.caseId, caseScenes.value)
+  return await contentRef.value?.submit();
+  // console.log("submit", caseScenes.value);
+};
+defineExpose<QuiskExpose>({
+  submit,
+});
+</script>

+ 200 - 0
src/view/vrmodel/addCaseFile.vue

@@ -0,0 +1,200 @@
+<template>
+  <el-form
+    ref="form"
+    :model="caseFile"
+    label-width="90px"
+    class="camera-from dispatch-file-from jm-file-upload"
+  >
+    <el-form-item label="分类:" class="mandatory">
+      <el-radio-group v-model="caseFile.sourceType">
+        <el-radio value="orig">原始数据</el-radio>
+        <el-radio value="offline">离线包</el-radio>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item label="操作:" class="mandatory uploadFile">
+      <el-upload
+        v-model:file-list="caseFile.file"
+        class="upload-demo"
+        action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
+        multiple
+        :before-upload="upload"
+        :http-request="uploadFiles" 
+        :on-preview="handlePreview"
+        :on-remove="handleRemove"
+        accept=".zip"
+        :limit="1"
+        :on-exceed="handleExceed"
+      >
+        <el-button type="primary">上传</el-button>
+      </el-upload>
+    </el-form-item>
+    <div style="padding-left: 82px">
+      <span v-if="caseFile.sourceType == 'orig'"
+        >‌支持采集完成后,U 盘导出的场景原始数据上传计算。</span
+      >
+      <div style="width: 360px" v-else>
+        <div>重要提醒:</div>
+        <div
+          >数据上传规则‌:只接收‌其他单位/部门私有化部署的系统‌(比如XX分局自建的平台)下载的数据,本平台已有的场景‌‌不能重复上传‌;</div
+        >
+        <div
+          >‌版本号核对‌:请确保来源平台和本平台的版本号完全一致,如来源平台是
+          v2.2.0,本平台也必须是 v2.2.0。</div
+        >
+      </div>
+      </div>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { uploadFiles, addByMediaLiBrary, SceneCheck, SceneOrig } from "@/store/case";
+import viewImg from "@/components/viewImg/index.vue"
+import {
+  DrawFormatDesc,
+  DrawFormats,
+  FileDrawType,
+  OtherFormatDesc,
+  OtherFormats,
+} from "@/constant/caseFile";
+import { maxFileSize } from "@/constant/caseFile";
+import { useUpload } from "@/hook/upload";
+import { CaseFile, addCaseFile } from "@/store/caseFile";
+import { ElMessage, UploadFile, ElMessageBox } from "element-plus";
+import { computed, ref, watchEffect, onMounted } from "vue";
+import { QuiskExpose } from "@/helper/mount";
+import { updateSelectByTreeFileLists } from "@/store/case";
+
+const props = defineProps<{
+  caseId: number;
+  fileType: number;
+  filesTypeName: [string];
+  fileInfo?: Object;
+}>();
+const mtkList = ref([]);
+onMounted(async () => {
+});
+
+const caseFile = ref({
+  sourceType: "orig",
+  caseId: props.caseId,
+  file:[],
+  filesTypeId: props.fileType,
+  filesTitle: "",
+  dictId: '',
+  uploadId: '',
+  filesType: 1,
+});
+console.log('caseFile', props.fileInfo);
+// const { size, fileList, upload, removeFile, previewFile, file, accept } = useUpload({
+//   maxSize:  5 * 1024 * 1024 * 1024,
+//   formats:  [".zip"],
+// });
+
+  const upload = async (file: File, fileLists) => {
+    if(!file.name) return false;
+    const fileType = file.name && file.name.substring(file.name.lastIndexOf(".")).toUpperCase();
+    console.log('uploadts', file, fileType);
+    if (fileType !== '.ZIP') {
+      ElMessage.error(`请上传zip文件`);
+      return false;
+    } else if (file.size >  5 * 1024 * 1024 * 1024 ) {
+      ElMessage.error(`请上传5GB以内的文件`);
+      return false;
+    } else {
+      return true;
+    }
+  };
+
+const formatDesc = computed(() => {
+  return 'zip上传'
+});
+
+// 上传请求
+const handleSuccess = (option) => {
+  console.log('handleSuccess', option);
+}
+const handleExceed = (option) => {
+  ElMessage.error("只能上传1个文件!");
+}
+
+const handleItem = (type, item) => {
+  console.log("handleItem", type, item);
+  if(type == 'delete'){
+    mtkList.value = mtkList.value.filter(ele => ele.id != item.id)
+  }
+};
+const Submit = async (filePath) => {
+    const res = await SceneOrig({ filePath, sourceType: caseFile.value.sourceType });
+    // loading.value = false;
+    console.log('res', res, filePath);
+    ElMessage.success('上传成功。');
+    caseFile.value.file = [];
+};
+defineExpose<QuiskExpose>({
+  async submit() {
+    let filesTypeId = caseFile.value.filesTypeId && Array.isArray(caseFile.value.filesTypeId)?caseFile.value.filesTypeId.slice(-1):caseFile.value.filesTypeId
+    let file = caseFile.value.file[0];
+    let filePath = ''
+    if (!file) {
+      ElMessage.error("请上传文件");
+      throw "请上传文件";
+    }else{
+      filePath = file?.response?.data
+    }
+    const {data} = await SceneCheck({ filePath, sourceType: caseFile.value.sourceType });
+    let resCheck = data;
+    console.log('caseFile', caseFile.value, resCheck);
+    if (
+     resCheck.code == 60042 && await ElMessageBox.confirm(`此场景原属于${resCheck.data},继续上传将重新计算并覆盖原场景:\n归属权转移至当前账号;\n清除原所有者的权限设置;\n清空场景内由用户手动添加的空间模型。\n确定继续吗?`, '提示')
+    ) {
+       return Submit(filePath);
+    } 
+    if (
+     resCheck.code == 60043 && await ElMessageBox.confirm(`此场景此前已上传过‌,继续上传将重新计算并覆盖原场景,场景内由用户手动添加的空间模型也会清空,确定继续吗?`, '提示')
+    ) {
+       return Submit(filePath);
+    } 
+    
+    if (
+     resCheck.code == 60051 && await ElMessageBox.confirm(resCheck.data, '提示')
+    ) {
+       return Submit(filePath);
+    } 
+        
+    if (
+     resCheck.code == 60051 && await ElMessageBox.confirm(resCheck.data, '提示')
+    ) {
+       return Submit(filePath);
+    } 
+    return Submit(filePath);
+  },
+});
+</script>
+
+<style scoped lang="scss">
+.upload-demo {
+  overflow: hidden;
+  width: 100%;
+}
+
+.file {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  > div {
+    display: flex;
+    align-items: center;
+  }
+
+  .name {
+    margin-left: 10px;
+  }
+}
+.jm-file-upload {
+  // .mtk {
+  //   position: absolute;
+  //   right: 0;
+  //   top: 0;
+  // }
+}
+</style>

+ 8 - 4
src/view/vrmodel/list.vue

@@ -2,8 +2,9 @@
   <com-head :options="headOptions" v-model="params.pagging.state.query.isObj">
   <com-head :options="headOptions" v-model="params.pagging.state.query.isObj">
     <el-form label-width="84px" inline>
     <el-form label-width="84px" inline>
       <slot name="header" />
       <slot name="header" />
-      <el-form-item class="searh-btns" style="grid-area: 1 / 4 / 2 / 4">
+      <el-form-item class="searh-btns" style="grid-area: 1 / 6 / 2 / 4">
         <el-button style="visibility: hidden;" type="primary" @click="params.pagging.refresh">查询</el-button>
         <el-button style="visibility: hidden;" type="primary" @click="params.pagging.refresh">查询</el-button>
+        <el-button v-if="upload" type="primary" plain @click="addUploadSecen">上传</el-button>
         <el-button class="float-right" type="primary" plain @click="params.pagging.queryReset"
         <el-button class="float-right" type="primary" plain @click="params.pagging.queryReset"
           >重置</el-button
           >重置</el-button
         >
         >
@@ -29,9 +30,12 @@ import comPagination from "@/components/pagination/index.vue";
 import { SceneType } from "@/store/scene";
 import { SceneType } from "@/store/scene";
 import { SceneTypeDesc } from "@/constant/scene";
 import { SceneTypeDesc } from "@/constant/scene";
 import { useScenePaggingParams } from "./pagging";
 import { useScenePaggingParams } from "./pagging";
-
-defineProps<{ params: ReturnType<typeof useScenePaggingParams> }>();
-
+import { addModelScene } from "./quisk" 
+const props = defineProps<{ params: ReturnType<typeof useScenePaggingParams>, upload: boolean }>();
+const addUploadSecen = async () => {
+  await addModelScene();
+  props.params.pagging.refresh();
+}
 const headOptions = [
 const headOptions = [
   // { value: SceneType.SWKK, name: SceneTypeDesc[SceneType.SWKK] },
   // { value: SceneType.SWKK, name: SceneTypeDesc[SceneType.SWKK] },
   // { value: SceneType.SWKJ, name: SceneTypeDesc[SceneType.SWKJ] },
   // { value: SceneType.SWKJ, name: SceneTypeDesc[SceneType.SWKJ] },

+ 5 - 1
src/view/vrmodel/quisk.ts

@@ -4,7 +4,7 @@ import tableModel from "./tableModel.vue";
 import SceneDownload from "./sceneDownload.vue";
 import SceneDownload from "./sceneDownload.vue";
 import { quiskMountFactory } from "@/helper/mount";
 import { quiskMountFactory } from "@/helper/mount";
 import { axios, checkHasDownload } from "@/request";
 import { axios, checkHasDownload } from "@/request";
-
+import addCaseFile from "./addCaseFile.vue";
 export const editModelScene = quiskMountFactory(EditModel, {
 export const editModelScene = quiskMountFactory(EditModel, {
   title: "编辑模型",
   title: "编辑模型",
   width: 500,
   width: 500,
@@ -13,6 +13,10 @@ export const tableModelScene = quiskMountFactory(tableModel, {
   title: "导入实景三维",
   title: "导入实景三维",
   width: 1000,
   width: 1000,
 });
 });
+export const addModelScene = quiskMountFactory(addCaseFile, {
+  title: "上传",
+  width: 500,
+});
 export type SceneDpwnloadProps = { scene: QuoteScene };
 export type SceneDpwnloadProps = { scene: QuoteScene };
 export const sceneDownload = async(props: SceneDpwnloadProps) => {
 export const sceneDownload = async(props: SceneDpwnloadProps) => {
   const params = {
   const params = {

+ 1 - 1
src/view/vrmodel/tableModel.vue

@@ -1,5 +1,5 @@
 <template>
 <template>
-  <List :params="params">
+  <List :params="params" upload>
     <template v-slot:header>
     <template v-slot:header>
       <el-form-item label="场景名称:" style="width: 250px">
       <el-form-item label="场景名称:" style="width: 250px">
         <el-input v-model="params.keyword" placeholder="请输入"></el-input>
         <el-input v-model="params.keyword" placeholder="请输入"></el-input>

+ 5 - 0
vite.config.ts

@@ -67,6 +67,11 @@ export default defineConfig({
         changeOrigin: true,
         changeOrigin: true,
         secure: false,
         secure: false,
       },
       },
+      "/sdk": {
+        target: url,
+        changeOrigin: true,
+        secure: false,
+      },
       "/service": {
       "/service": {
         target: url,
         target: url,
         changeOrigin: true,
         changeOrigin: true,

+ 348 - 1
yarn.lock

@@ -29,6 +29,11 @@
   dependencies:
   dependencies:
     "@babel/types" "^7.26.3"
     "@babel/types" "^7.26.3"
 
 
+"@babel/runtime@^7.12.5", "@babel/runtime@^7.28.6":
+  version "7.28.6"
+  resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz#d267a43cb1836dc4d182cce93ae75ba954ef6d2b"
+  integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==
+
 "@babel/types@^7.25.8", "@babel/types@^7.26.3":
 "@babel/types@^7.25.8", "@babel/types@^7.26.3":
   version "7.26.3"
   version "7.26.3"
   resolved "https://mirrors.cloud.tencent.com/npm/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
   resolved "https://mirrors.cloud.tencent.com/npm/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
@@ -292,6 +297,27 @@
     "@parcel/watcher-win32-ia32" "2.5.0"
     "@parcel/watcher-win32-ia32" "2.5.0"
     "@parcel/watcher-win32-x64" "2.5.0"
     "@parcel/watcher-win32-x64" "2.5.0"
 
 
+"@pdf-lib/fontkit@^1.1.1":
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/@pdf-lib/fontkit/-/fontkit-1.1.1.tgz#f18473892b65e3253eb73f4569785abd2c03b1e0"
+  integrity sha512-KjMd7grNapIWS/Dm0gvfHEilSyAmeLvrEGVcqLGi0VYebuqqzTbgF29efCx7tvx+IEbG3zQciRSWl3GkUSvjZg==
+  dependencies:
+    pako "^1.0.6"
+
+"@pdf-lib/standard-fonts@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz#8ba691c4421f71662ed07c9a0294b44528af2d7f"
+  integrity sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==
+  dependencies:
+    pako "^1.0.6"
+
+"@pdf-lib/upng@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/@pdf-lib/upng/-/upng-1.0.1.tgz#7dc9c636271aca007a9df4deaf2dd7e7960280cb"
+  integrity sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==
+  dependencies:
+    pako "^1.0.10"
+
 "@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7":
 "@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7":
   version "2.11.7"
   version "2.11.7"
   resolved "https://mirrors.cloud.tencent.com/npm/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz#a7f69e3665d3da9b115f9e71671dae1b97e13671"
   resolved "https://mirrors.cloud.tencent.com/npm/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz#a7f69e3665d3da9b115f9e71671dae1b97e13671"
@@ -311,6 +337,13 @@
     estree-walker "^2.0.2"
     estree-walker "^2.0.2"
     picomatch "^4.0.2"
     picomatch "^4.0.2"
 
 
+"@swc/helpers@^0.5.12":
+  version "0.5.19"
+  resolved "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.19.tgz#9a8c8a0bdaecfdfb9b8ae5421c0c8e09246dfee9"
+  integrity sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==
+  dependencies:
+    tslib "^2.8.0"
+
 "@types/estree@^1.0.0":
 "@types/estree@^1.0.0":
   version "1.0.6"
   version "1.0.6"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
@@ -335,16 +368,31 @@
   dependencies:
   dependencies:
     undici-types "~6.19.2"
     undici-types "~6.19.2"
 
 
+"@types/pako@^2.0.3":
+  version "2.0.4"
+  resolved "https://registry.npmmirror.com/@types/pako/-/pako-2.0.4.tgz#c3575ef8125e176c345fa0e7b301c1db41170c15"
+  integrity sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==
+
 "@types/qs@^6.9.7":
 "@types/qs@^6.9.7":
   version "6.9.17"
   version "6.9.17"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/qs/-/qs-6.9.17.tgz#fc560f60946d0aeff2f914eb41679659d3310e1a"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/qs/-/qs-6.9.17.tgz#fc560f60946d0aeff2f914eb41679659d3310e1a"
   integrity sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==
   integrity sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==
 
 
+"@types/raf@^3.4.0":
+  version "3.4.3"
+  resolved "https://registry.npmmirror.com/@types/raf/-/raf-3.4.3.tgz#85f1d1d17569b28b8db45e16e996407a56b0ab04"
+  integrity sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==
+
 "@types/sortablejs@^1.15.8":
 "@types/sortablejs@^1.15.8":
   version "1.15.8"
   version "1.15.8"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/sortablejs/-/sortablejs-1.15.8.tgz#11ed555076046e00869a5ef85d1e7651e7a66ef6"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/sortablejs/-/sortablejs-1.15.8.tgz#11ed555076046e00869a5ef85d1e7651e7a66ef6"
   integrity sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==
   integrity sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==
 
 
+"@types/trusted-types@^2.0.7":
+  version "2.0.7"
+  resolved "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
+  integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
+
 "@types/web-bluetooth@^0.0.16":
 "@types/web-bluetooth@^0.0.16":
   version "0.0.16"
   version "0.0.16"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
   resolved "https://mirrors.cloud.tencent.com/npm/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
@@ -583,6 +631,11 @@ base64-arraybuffer@^1.0.2:
   resolved "https://mirrors.cloud.tencent.com/npm/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
   resolved "https://mirrors.cloud.tencent.com/npm/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
   integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
   integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
 
 
+base64-js@^1.1.2, base64-js@^1.3.0:
+  version "1.5.1"
+  resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
 brace-expansion@^2.0.1:
 brace-expansion@^2.0.1:
   version "2.0.1"
   version "2.0.1"
   resolved "https://mirrors.cloud.tencent.com/npm/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
   resolved "https://mirrors.cloud.tencent.com/npm/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
@@ -597,6 +650,13 @@ braces@^3.0.3:
   dependencies:
   dependencies:
     fill-range "^7.1.1"
     fill-range "^7.1.1"
 
 
+brotli@^1.3.2:
+  version "1.3.3"
+  resolved "https://registry.npmmirror.com/brotli/-/brotli-1.3.3.tgz#7365d8cc00f12cf765d2b2c898716bcf4b604d48"
+  integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==
+  dependencies:
+    base64-js "^1.1.2"
+
 call-bind-apply-helpers@^1.0.0:
 call-bind-apply-helpers@^1.0.0:
   version "1.0.1"
   version "1.0.1"
   resolved "https://mirrors.cloud.tencent.com/npm/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
   resolved "https://mirrors.cloud.tencent.com/npm/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840"
@@ -615,6 +675,20 @@ call-bind@^1.0.7:
     get-intrinsic "^1.2.4"
     get-intrinsic "^1.2.4"
     set-function-length "^1.2.2"
     set-function-length "^1.2.2"
 
 
+canvg@^3.0.11:
+  version "3.0.11"
+  resolved "https://registry.npmmirror.com/canvg/-/canvg-3.0.11.tgz#4b4290a6c7fa36871fac2b14e432eff33b33cf2b"
+  integrity sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==
+  dependencies:
+    "@babel/runtime" "^7.12.5"
+    "@types/raf" "^3.4.0"
+    core-js "^3.8.3"
+    raf "^3.4.1"
+    regenerator-runtime "^0.13.7"
+    rgbcolor "^1.0.1"
+    stackblur-canvas "^2.0.0"
+    svg-pathdata "^6.0.3"
+
 chokidar@^4.0.0:
 chokidar@^4.0.0:
   version "4.0.1"
   version "4.0.1"
   resolved "https://mirrors.cloud.tencent.com/npm/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41"
   resolved "https://mirrors.cloud.tencent.com/npm/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41"
@@ -622,6 +696,11 @@ chokidar@^4.0.0:
   dependencies:
   dependencies:
     readdirp "^4.0.1"
     readdirp "^4.0.1"
 
 
+clone@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+  integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
+
 combined-stream@^1.0.8:
 combined-stream@^1.0.8:
   version "1.0.8"
   version "1.0.8"
   resolved "https://mirrors.cloud.tencent.com/npm/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
   resolved "https://mirrors.cloud.tencent.com/npm/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@@ -639,6 +718,16 @@ confbox@^0.1.8:
   resolved "https://mirrors.cloud.tencent.com/npm/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"
   resolved "https://mirrors.cloud.tencent.com/npm/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"
   integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
   integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
 
 
+core-js@^3.6.0, core-js@^3.8.3:
+  version "3.48.0"
+  resolved "https://registry.npmmirror.com/core-js/-/core-js-3.48.0.tgz#1f813220a47bbf0e667e3885c36cd6f0593bf14d"
+  integrity sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==
+
+core-util-is@~1.0.0:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
 css-line-break@^2.1.0:
 css-line-break@^2.1.0:
   version "2.1.0"
   version "2.1.0"
   resolved "https://mirrors.cloud.tencent.com/npm/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
   resolved "https://mirrors.cloud.tencent.com/npm/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
@@ -687,6 +776,18 @@ detect-libc@^1.0.3:
   resolved "https://mirrors.cloud.tencent.com/npm/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
   resolved "https://mirrors.cloud.tencent.com/npm/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
   integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
   integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
 
 
+dfa@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/dfa/-/dfa-1.2.0.tgz#96ac3204e2d29c49ea5b57af8d92c2ae12790657"
+  integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==
+
+dompurify@^3.3.1:
+  version "3.3.1"
+  resolved "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.1.tgz#c7e1ddebfe3301eacd6c0c12a4af284936dbbb86"
+  integrity sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==
+  optionalDependencies:
+    "@types/trusted-types" "^2.0.7"
+
 dunder-proto@^1.0.0:
 dunder-proto@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://mirrors.cloud.tencent.com/npm/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80"
   resolved "https://mirrors.cloud.tencent.com/npm/dunder-proto/-/dunder-proto-1.0.0.tgz#c2fce098b3c8f8899554905f4377b6d85dabaa80"
@@ -783,6 +884,11 @@ estree-walker@^2.0.2:
   resolved "https://mirrors.cloud.tencent.com/npm/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
   resolved "https://mirrors.cloud.tencent.com/npm/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
   integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
   integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
 
 
+fast-deep-equal@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+  integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
 fast-glob@^3.3.2:
 fast-glob@^3.3.2:
   version "3.3.2"
   version "3.3.2"
   resolved "https://mirrors.cloud.tencent.com/npm/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
   resolved "https://mirrors.cloud.tencent.com/npm/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
@@ -794,6 +900,15 @@ fast-glob@^3.3.2:
     merge2 "^1.3.0"
     merge2 "^1.3.0"
     micromatch "^4.0.4"
     micromatch "^4.0.4"
 
 
+fast-png@^6.2.0:
+  version "6.4.0"
+  resolved "https://registry.npmmirror.com/fast-png/-/fast-png-6.4.0.tgz#807fc353ccab060d09151b7d082786e02d8e92d6"
+  integrity sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==
+  dependencies:
+    "@types/pako" "^2.0.3"
+    iobuffer "^5.3.2"
+    pako "^2.1.0"
+
 fastq@^1.6.0:
 fastq@^1.6.0:
   version "1.17.1"
   version "1.17.1"
   resolved "https://mirrors.cloud.tencent.com/npm/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
   resolved "https://mirrors.cloud.tencent.com/npm/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
@@ -801,6 +916,16 @@ fastq@^1.6.0:
   dependencies:
   dependencies:
     reusify "^1.0.4"
     reusify "^1.0.4"
 
 
+fflate@^0.8.1:
+  version "0.8.2"
+  resolved "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
+  integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
+
+file-saver@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
+  integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
+
 fill-range@^7.1.1:
 fill-range@^7.1.1:
   version "7.1.1"
   version "7.1.1"
   resolved "https://mirrors.cloud.tencent.com/npm/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
   resolved "https://mirrors.cloud.tencent.com/npm/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@@ -813,6 +938,21 @@ follow-redirects@^1.15.6:
   resolved "https://mirrors.cloud.tencent.com/npm/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
   resolved "https://mirrors.cloud.tencent.com/npm/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
   integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
   integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
 
 
+fontkit@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.npmmirror.com/fontkit/-/fontkit-2.0.4.tgz#4765d664c68b49b5d6feb6bd1051ee49d8ec5ab0"
+  integrity sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==
+  dependencies:
+    "@swc/helpers" "^0.5.12"
+    brotli "^1.3.2"
+    clone "^2.1.2"
+    dfa "^1.2.0"
+    fast-deep-equal "^3.1.3"
+    restructure "^3.0.0"
+    tiny-inflate "^1.0.3"
+    unicode-properties "^1.4.0"
+    unicode-trie "^2.0.0"
+
 form-data@^4.0.0:
 form-data@^4.0.0:
   version "4.0.1"
   version "4.0.1"
   resolved "https://mirrors.cloud.tencent.com/npm/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
   resolved "https://mirrors.cloud.tencent.com/npm/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48"
@@ -882,7 +1022,7 @@ he@^1.2.0:
   resolved "https://mirrors.cloud.tencent.com/npm/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   resolved "https://mirrors.cloud.tencent.com/npm/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
 
 
-html2canvas@^1.4.1:
+html2canvas@^1.0.0, html2canvas@^1.0.0-rc.5, html2canvas@^1.4.1:
   version "1.4.1"
   version "1.4.1"
   resolved "https://mirrors.cloud.tencent.com/npm/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
   resolved "https://mirrors.cloud.tencent.com/npm/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
   integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
   integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
@@ -890,11 +1030,35 @@ html2canvas@^1.4.1:
     css-line-break "^2.1.0"
     css-line-break "^2.1.0"
     text-segmentation "^1.0.3"
     text-segmentation "^1.0.3"
 
 
+html2pdf.js@^0.14.0:
+  version "0.14.0"
+  resolved "https://registry.npmmirror.com/html2pdf.js/-/html2pdf.js-0.14.0.tgz#dd2fdf2ee3036cb4c0d7c0d4606ee2da7c677e83"
+  integrity sha512-yvNJgE/8yru2UeGflkPdjW8YEY+nDH5X7/2WG4uiuSCwYiCp8PZ8EKNiTAa6HxJ1NjC51fZSIEq6xld5CADKBQ==
+  dependencies:
+    dompurify "^3.3.1"
+    html2canvas "^1.0.0"
+    jspdf "^4.0.0"
+
+immediate@~3.0.5:
+  version "3.0.6"
+  resolved "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+  integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
+
 immutable@^5.0.2:
 immutable@^5.0.2:
   version "5.0.3"
   version "5.0.3"
   resolved "https://mirrors.cloud.tencent.com/npm/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1"
   resolved "https://mirrors.cloud.tencent.com/npm/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1"
   integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==
   integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==
 
 
+inherits@~2.0.3:
+  version "2.0.4"
+  resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+iobuffer@^5.3.2:
+  version "5.4.0"
+  resolved "https://registry.npmmirror.com/iobuffer/-/iobuffer-5.4.0.tgz#f85dff957fd0579257472f0a4cfe5ed3430e63e1"
+  integrity sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==
+
 is-extglob@^2.1.1:
 is-extglob@^2.1.1:
   version "2.1.1"
   version "2.1.1"
   resolved "https://mirrors.cloud.tencent.com/npm/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
   resolved "https://mirrors.cloud.tencent.com/npm/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -912,6 +1076,11 @@ is-number@^7.0.0:
   resolved "https://mirrors.cloud.tencent.com/npm/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   resolved "https://mirrors.cloud.tencent.com/npm/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
 
+isarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+  integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
 jiti@^1.21.6:
 jiti@^1.21.6:
   version "1.21.6"
   version "1.21.6"
   resolved "https://mirrors.cloud.tencent.com/npm/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
   resolved "https://mirrors.cloud.tencent.com/npm/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
@@ -922,6 +1091,44 @@ js-base64@^3.7.5:
   resolved "https://mirrors.cloud.tencent.com/npm/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79"
   resolved "https://mirrors.cloud.tencent.com/npm/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79"
   integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==
   integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==
 
 
+jspdf@^4.0.0:
+  version "4.2.0"
+  resolved "https://registry.npmmirror.com/jspdf/-/jspdf-4.2.0.tgz#f5b42a8e1592c3da1531d005adc87ccc19272965"
+  integrity sha512-hR/hnRevAXXlrjeqU5oahOE+Ln9ORJUB5brLHHqH67A+RBQZuFr5GkbI9XQI8OUFSEezKegsi45QRpc4bGj75Q==
+  dependencies:
+    "@babel/runtime" "^7.28.6"
+    fast-png "^6.2.0"
+    fflate "^0.8.1"
+  optionalDependencies:
+    canvg "^3.0.11"
+    core-js "^3.6.0"
+    dompurify "^3.3.1"
+    html2canvas "^1.0.0-rc.5"
+
+jspdf@^4.2.1:
+  version "4.2.1"
+  resolved "https://registry.npmmirror.com/jspdf/-/jspdf-4.2.1.tgz#6ba0d263999313f91f369ee80ecf235046b2acd8"
+  integrity sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==
+  dependencies:
+    "@babel/runtime" "^7.28.6"
+    fast-png "^6.2.0"
+    fflate "^0.8.1"
+  optionalDependencies:
+    canvg "^3.0.11"
+    core-js "^3.6.0"
+    dompurify "^3.3.1"
+    html2canvas "^1.0.0-rc.5"
+
+jszip@^3.10.1:
+  version "3.10.1"
+  resolved "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
+  integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
+  dependencies:
+    lie "~3.3.0"
+    pako "~1.0.2"
+    readable-stream "~2.3.6"
+    setimmediate "^1.0.5"
+
 kolorist@^1.8.0:
 kolorist@^1.8.0:
   version "1.8.0"
   version "1.8.0"
   resolved "https://mirrors.cloud.tencent.com/npm/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
   resolved "https://mirrors.cloud.tencent.com/npm/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
@@ -937,6 +1144,13 @@ leaflet@^1.9.4:
   resolved "https://mirrors.cloud.tencent.com/npm/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d"
   resolved "https://mirrors.cloud.tencent.com/npm/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d"
   integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
   integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
 
 
+lie@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
+  integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
+  dependencies:
+    immediate "~3.0.5"
+
 local-pkg@^0.5.0:
 local-pkg@^0.5.0:
   version "0.5.1"
   version "0.5.1"
   resolved "https://mirrors.cloud.tencent.com/npm/local-pkg/-/local-pkg-0.5.1.tgz#69658638d2a95287534d4c2fff757980100dbb6d"
   resolved "https://mirrors.cloud.tencent.com/npm/local-pkg/-/local-pkg-0.5.1.tgz#69658638d2a95287534d4c2fff757980100dbb6d"
@@ -1075,6 +1289,21 @@ ollama@^0.5.14:
   dependencies:
   dependencies:
     whatwg-fetch "^3.6.20"
     whatwg-fetch "^3.6.20"
 
 
+pako@^0.2.5:
+  version "0.2.9"
+  resolved "https://registry.npmmirror.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
+  integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
+
+pako@^1.0.10, pako@^1.0.11, pako@^1.0.6, pako@~1.0.2:
+  version "1.0.11"
+  resolved "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
+  integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+
+pako@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
+  integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
+
 path-browserify@^1.0.1:
 path-browserify@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://mirrors.cloud.tencent.com/npm/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
   resolved "https://mirrors.cloud.tencent.com/npm/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
@@ -1085,6 +1314,21 @@ pathe@^1.1.2:
   resolved "https://mirrors.cloud.tencent.com/npm/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
   resolved "https://mirrors.cloud.tencent.com/npm/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
   integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
   integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
 
 
+pdf-lib@^1.17.1:
+  version "1.17.1"
+  resolved "https://registry.npmmirror.com/pdf-lib/-/pdf-lib-1.17.1.tgz#9e7dd21261a0c1fb17992580885b39e7d08f451f"
+  integrity sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==
+  dependencies:
+    "@pdf-lib/standard-fonts" "^1.0.0"
+    "@pdf-lib/upng" "^1.0.1"
+    pako "^1.0.11"
+    tslib "^1.11.1"
+
+performance-now@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+  integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
+
 picocolors@^1.1.1:
 picocolors@^1.1.1:
   version "1.1.1"
   version "1.1.1"
   resolved "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
   resolved "https://mirrors.cloud.tencent.com/npm/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@@ -1118,6 +1362,11 @@ postcss@^8.4.27, postcss@^8.4.48:
     picocolors "^1.1.1"
     picocolors "^1.1.1"
     source-map-js "^1.2.1"
     source-map-js "^1.2.1"
 
 
+process-nextick-args@~2.0.0:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
 province-city-china@^8.5.8:
 province-city-china@^8.5.8:
   version "8.5.8"
   version "8.5.8"
   resolved "https://mirrors.cloud.tencent.com/npm/province-city-china/-/province-city-china-8.5.8.tgz#b1f28cfe917388e67684cfb64f7cf2a5c30ed44a"
   resolved "https://mirrors.cloud.tencent.com/npm/province-city-china/-/province-city-china-8.5.8.tgz#b1f28cfe917388e67684cfb64f7cf2a5c30ed44a"
@@ -1142,16 +1391,51 @@ queue-microtask@^1.2.2:
   resolved "https://mirrors.cloud.tencent.com/npm/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   resolved "https://mirrors.cloud.tencent.com/npm/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
 
+raf@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
+  integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
+  dependencies:
+    performance-now "^2.1.0"
+
+readable-stream@~2.3.6:
+  version "2.3.8"
+  resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
+  integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.1.1"
+    util-deprecate "~1.0.1"
+
 readdirp@^4.0.1:
 readdirp@^4.0.1:
   version "4.0.2"
   version "4.0.2"
   resolved "https://mirrors.cloud.tencent.com/npm/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
   resolved "https://mirrors.cloud.tencent.com/npm/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
   integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==
   integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==
 
 
+regenerator-runtime@^0.13.7:
+  version "0.13.11"
+  resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+  integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+
+restructure@^3.0.0:
+  version "3.0.2"
+  resolved "https://registry.npmmirror.com/restructure/-/restructure-3.0.2.tgz#e6b2fad214f78edee21797fa8160fef50eb9b49a"
+  integrity sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==
+
 reusify@^1.0.4:
 reusify@^1.0.4:
   version "1.0.4"
   version "1.0.4"
   resolved "https://mirrors.cloud.tencent.com/npm/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   resolved "https://mirrors.cloud.tencent.com/npm/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
 
 
+rgbcolor@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d"
+  integrity sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==
+
 rollup@^3.27.1:
 rollup@^3.27.1:
   version "3.29.5"
   version "3.29.5"
   resolved "https://mirrors.cloud.tencent.com/npm/rollup/-/rollup-3.29.5.tgz#8a2e477a758b520fb78daf04bca4c522c1da8a54"
   resolved "https://mirrors.cloud.tencent.com/npm/rollup/-/rollup-3.29.5.tgz#8a2e477a758b520fb78daf04bca4c522c1da8a54"
@@ -1166,6 +1450,11 @@ run-parallel@^1.1.9:
   dependencies:
   dependencies:
     queue-microtask "^1.2.2"
     queue-microtask "^1.2.2"
 
 
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+  version "5.1.2"
+  resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
 sass@^1.64.2:
 sass@^1.64.2:
   version "1.82.0"
   version "1.82.0"
   resolved "https://mirrors.cloud.tencent.com/npm/sass/-/sass-1.82.0.tgz#30da277af3d0fa6042e9ceabd0d984ed6d07df70"
   resolved "https://mirrors.cloud.tencent.com/npm/sass/-/sass-1.82.0.tgz#30da277af3d0fa6042e9ceabd0d984ed6d07df70"
@@ -1194,6 +1483,11 @@ set-function-length@^1.2.2:
     gopd "^1.0.1"
     gopd "^1.0.1"
     has-property-descriptors "^1.0.2"
     has-property-descriptors "^1.0.2"
 
 
+setimmediate@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
+  integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
+
 side-channel@^1.0.6:
 side-channel@^1.0.6:
   version "1.0.6"
   version "1.0.6"
   resolved "https://mirrors.cloud.tencent.com/npm/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
   resolved "https://mirrors.cloud.tencent.com/npm/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
@@ -1209,6 +1503,23 @@ side-channel@^1.0.6:
   resolved "https://mirrors.cloud.tencent.com/npm/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
   resolved "https://mirrors.cloud.tencent.com/npm/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
   integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
   integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
 
 
+stackblur-canvas@^2.0.0:
+  version "2.7.0"
+  resolved "https://registry.npmmirror.com/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz#af931277d0b5096df55e1f91c530043e066989b6"
+  integrity sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==
+
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
+svg-pathdata@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.npmmirror.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac"
+  integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==
+
 swiper@^11.1.15:
 swiper@^11.1.15:
   version "11.1.15"
   version "11.1.15"
   resolved "https://mirrors.cloud.tencent.com/npm/swiper/-/swiper-11.1.15.tgz#e2258c8d38282e2f115ca463d6e8c5b84cdcf1ca"
   resolved "https://mirrors.cloud.tencent.com/npm/swiper/-/swiper-11.1.15.tgz#e2258c8d38282e2f115ca463d6e8c5b84cdcf1ca"
@@ -1226,6 +1537,11 @@ three@^0.171.0:
   resolved "https://mirrors.cloud.tencent.com/npm/three/-/three-0.171.0.tgz#3c0dd3f8fa14e78a7f8db6e416b98f264f1185c0"
   resolved "https://mirrors.cloud.tencent.com/npm/three/-/three-0.171.0.tgz#3c0dd3f8fa14e78a7f8db6e416b98f264f1185c0"
   integrity sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==
   integrity sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==
 
 
+tiny-inflate@^1.0.0, tiny-inflate@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
+  integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
+
 to-regex-range@^5.0.1:
 to-regex-range@^5.0.1:
   version "5.0.1"
   version "5.0.1"
   resolved "https://mirrors.cloud.tencent.com/npm/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
   resolved "https://mirrors.cloud.tencent.com/npm/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -1238,6 +1554,16 @@ tslib@2.3.0:
   resolved "https://mirrors.cloud.tencent.com/npm/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
   resolved "https://mirrors.cloud.tencent.com/npm/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
   integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
   integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
 
 
+tslib@^1.11.1:
+  version "1.14.1"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
+tslib@^2.8.0:
+  version "2.8.1"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
+  integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
 typescript@5.4.5:
 typescript@5.4.5:
   version "5.4.5"
   version "5.4.5"
   resolved "https://mirrors.cloud.tencent.com/npm/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
   resolved "https://mirrors.cloud.tencent.com/npm/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
@@ -1253,6 +1579,22 @@ undici-types@~6.19.2:
   resolved "https://mirrors.cloud.tencent.com/npm/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
   resolved "https://mirrors.cloud.tencent.com/npm/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
   integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
   integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
 
 
+unicode-properties@^1.4.0:
+  version "1.4.1"
+  resolved "https://registry.npmmirror.com/unicode-properties/-/unicode-properties-1.4.1.tgz#96a9cffb7e619a0dc7368c28da27e05fc8f9be5f"
+  integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==
+  dependencies:
+    base64-js "^1.3.0"
+    unicode-trie "^2.0.0"
+
+unicode-trie@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8"
+  integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==
+  dependencies:
+    pako "^0.2.5"
+    tiny-inflate "^1.0.0"
+
 unplugin-element-plus@^0.7.2:
 unplugin-element-plus@^0.7.2:
   version "0.7.2"
   version "0.7.2"
   resolved "https://mirrors.cloud.tencent.com/npm/unplugin-element-plus/-/unplugin-element-plus-0.7.2.tgz#93408f4c064a102d31d49097144641b3fcc9726c"
   resolved "https://mirrors.cloud.tencent.com/npm/unplugin-element-plus/-/unplugin-element-plus-0.7.2.tgz#93408f4c064a102d31d49097144641b3fcc9726c"
@@ -1280,6 +1622,11 @@ unplugin@^1.14.1, unplugin@^1.3.2:
     acorn "^8.14.0"
     acorn "^8.14.0"
     webpack-virtual-modules "^0.6.2"
     webpack-virtual-modules "^0.6.2"
 
 
+util-deprecate@~1.0.1:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+  integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
 utrie@^1.0.2:
 utrie@^1.0.2:
   version "1.0.2"
   version "1.0.2"
   resolved "https://mirrors.cloud.tencent.com/npm/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
   resolved "https://mirrors.cloud.tencent.com/npm/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"