bill 1 年之前
父节点
当前提交
6314ae0ffd
共有 100 个文件被更改,包括 64409 次插入0 次删除
  1. 9 0
      .env
  2. 10 0
      .env.development
  3. 9 0
      .env.fire
  4. 24 0
      .gitignore
  5. 6 0
      .vscode/extensions.json
  6. 15 0
      index.html
  7. 15 0
      map.html
  8. 15 0
      mirror.html
  9. 3214 0
      package-lock.json
  10. 45 0
      package.json
  11. 1335 0
      pnpm-lock.yaml
  12. 二进制
      public/favicon.ico
  13. 14 0
      public/xfile-viewer/.prettierrc
  14. 247 0
      public/xfile-viewer/index.html
  15. 二进制
      public/xfile-viewer/publish/20231025172413.dcm
  16. 二进制
      public/xfile-viewer/publish/20231025172413.raw
  17. 9084 0
      public/xfile-viewer/publish/lib/cornerstone.js
  18. 3 0
      public/xfile-viewer/publish/lib/cornerstoneMath.min.js
  19. 38415 0
      public/xfile-viewer/publish/lib/cornerstoneTools.js
  20. 2 0
      public/xfile-viewer/publish/lib/cornerstoneWADOImageLoader.bundle.min.js
  21. 3 0
      public/xfile-viewer/publish/lib/cornerstoneWebImageLoader.min.js
  22. 3 0
      public/xfile-viewer/publish/lib/dicomParser.min.js
  23. 2643 0
      public/xfile-viewer/publish/lib/hammer.js
  24. 12 0
      src/App.vue
  25. 20 0
      src/app/criminal/constant.ts
  26. 27 0
      src/app/criminal/routeConfig.ts
  27. 24 0
      src/app/criminal/store/example.ts
  28. 21 0
      src/app/criminal/useStyle.scss
  29. 33 0
      src/app/criminal/view/example/edit.vue
  30. 109 0
      src/app/criminal/view/example/index.vue
  31. 12 0
      src/app/criminal/view/example/quisk.ts
  32. 296 0
      src/app/criminal/view/login/index.vue
  33. 75 0
      src/app/criminal/view/login/style.scss
  34. 14 0
      src/app/fire/constant.ts
  35. 582 0
      src/app/fire/constant/fire.ts
  36. 46 0
      src/app/fire/routeConfig.ts
  37. 106 0
      src/app/fire/store/fire.ts
  38. 7 0
      src/app/fire/useStyle.scss
  39. 321 0
      src/app/fire/view/dispatch/editFire.vue
  40. 43 0
      src/app/fire/view/dispatch/editLeaveMsg.vue
  41. 93 0
      src/app/fire/view/dispatch/header.vue
  42. 174 0
      src/app/fire/view/dispatch/index.vue
  43. 110 0
      src/app/fire/view/dispatch/leaveMsgList.vue
  44. 194 0
      src/app/fire/view/dispatch/list.vue
  45. 74 0
      src/app/fire/view/dispatch/pagging.ts
  46. 23 0
      src/app/fire/view/dispatch/quisk.ts
  47. 51 0
      src/app/index.ts
  48. 333 0
      src/app/map/App.vue
  49. 55 0
      src/app/map/company-select/index.vue
  50. 68 0
      src/app/map/company-select/organization.ts
  51. 14 0
      src/app/map/main.ts
  52. 589 0
      src/app/mirror/App.vue
  53. 11 0
      src/app/mirror/dragable.ts
  54. 14 0
      src/app/mirror/main.ts
  55. 80 0
      src/app/mirror/store/script.ts
  56. 15 0
      src/app/xmfire/constant.ts
  57. 二进制
      src/app/xmfire/images/banner@2x.png
  58. 35 0
      src/app/xmfire/routeConfig.ts
  59. 7 0
      src/app/xmfire/useStyle.scss
  60. 385 0
      src/app/xmfire/view/login/index.vue
  61. 539 0
      src/assets/icon/fire/demo.css
  62. 878 0
      src/assets/icon/fire/demo_index.html
  63. 135 0
      src/assets/icon/fire/iconfont.css
  64. 二进制
      src/assets/icon/fire/iconfont.eot
  65. 1 0
      src/assets/icon/fire/iconfont.js
  66. 219 0
      src/assets/icon/fire/iconfont.json
  67. 101 0
      src/assets/icon/fire/iconfont.svg
  68. 二进制
      src/assets/icon/fire/iconfont.ttf
  69. 二进制
      src/assets/icon/fire/iconfont.woff
  70. 二进制
      src/assets/icon/fire/iconfont.woff2
  71. 539 0
      src/assets/icon/fuse/demo.css
  72. 1936 0
      src/assets/icon/fuse/demo_index.html
  73. 319 0
      src/assets/icon/fuse/iconfont.css
  74. 1 0
      src/assets/icon/fuse/iconfont.js
  75. 541 0
      src/assets/icon/fuse/iconfont.json
  76. 二进制
      src/assets/icon/fuse/iconfont.ttf
  77. 二进制
      src/assets/icon/fuse/iconfont.woff
  78. 二进制
      src/assets/icon/fuse/iconfont.woff2
  79. 二进制
      src/assets/image/arrow.png
  80. 8 0
      src/assets/image/arrow.svg
  81. 8 0
      src/assets/image/arrow_active.svg
  82. 二进制
      src/assets/image/close.png
  83. 二进制
      src/assets/image/code.png
  84. 二进制
      src/assets/image/criminal-32.png
  85. 二进制
      src/assets/image/criminal.ico
  86. 二进制
      src/assets/image/criminalBanner.png
  87. 二进制
      src/assets/image/decoration_collect@2x.png
  88. 二进制
      src/assets/image/edit_type_panorama.png
  89. 二进制
      src/assets/image/empty__empty.png
  90. 二进制
      src/assets/image/fire.ico
  91. 二进制
      src/assets/image/fireBanner.png
  92. 二进制
      src/assets/image/goto.png
  93. 二进制
      src/assets/image/home_bg.png
  94. 二进制
      src/assets/image/icon_add.png
  95. 二进制
      src/assets/image/img_login_logo.png
  96. 二进制
      src/assets/image/img_loginbg.png
  97. 二进制
      src/assets/image/img_loginbg1.png
  98. 二进制
      src/assets/image/img_loginbg2.png
  99. 二进制
      src/assets/image/jmfirebg@2x.png
  100. 0 0
      src/assets/image/jmlogo.png

+ 9 - 0
.env

@@ -0,0 +1,9 @@
+VITE_APP_APP="fire"
+VITE_SEVER_URL="https://192.168.0.25"
+VITE_DEVCODE_URL="https://192.168.0.25/code"
+VITE_SWKK_URL="https://test.4dkankan.com"
+VITE_SERVICE_URL="https://test.4dkankan.com"
+VITE_SWSS_URL="https://uat-laser.4dkankan.com/uat"
+VITE_LASER_URL="https://uat-laser.4dkankan.com"
+VITE_FDKK_URL="https://laser.4dkankan.com"
+VITE_DOC_URL="https://192.168.0.25/docs/#/product/all/zh-cn/README"

+ 10 - 0
.env.development

@@ -0,0 +1,10 @@
+VITE_APP_APP="fire"
+VITE_SEVER_URL="https://192.168.0.25"
+# VITE_SEVER_URL="https://xj-mix3d.4dkankan.com"
+VITE_DEVCODE_URL="https://192.168.0.25/code"
+VITE_SWKK_URL="https://test.4dkankan.com"
+VITE_SERVICE_URL="https://test.4dkankan.com"
+VITE_SWSS_URL="https://uat-laser.4dkankan.com/uat"
+VITE_LASER_URL="https://uat-laser.4dkankan.com"
+VITE_FDKK_URL="https://uat-laser.4dkankan.com/uat"
+VITE_DOC_URL="https://192.168.0.25/docs/#/product/all/zh-cn/README"

+ 9 - 0
.env.fire

@@ -0,0 +1,9 @@
+VITE_APP_APP="fire"
+VITE_SEVER_URL="https://192.168.0.25"
+VITE_DEVCODE_URL="https://192.168.0.25/code"
+VITE_SWKK_URL="https://4dkankan.com"
+VITE_SERVICE_URL="https://4dkankan.com"
+VITE_SWSS_URL="https://laser.4dkankan.com/"
+VITE_LASER_URL="https://laser.4dkankan.com"
+VITE_FDKK_URL="https://uat-laser.4dkankan.com/uat"
+VITE_DOC_URL="https://192.168.0.25/docs/#/product/all/zh-cn/README"

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 6 - 0
.vscode/extensions.json

@@ -0,0 +1,6 @@
+{
+  "recommendations": [
+    "Vue.offical",
+    "Vue.vscode-typescript-vue-plugin"
+  ],
+}

+ 15 - 0
index.html

@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" type="image/ico" href="/favicon.ico" id="app-icon" />
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/main.ts"></script>
+</body>
+
+</html>

+ 15 - 0
map.html

@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" type="image/ico" href="/favicon.ico" id="app-icon" />
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/app/map/main.ts"></script>
+</body>
+
+</html>

+ 15 - 0
mirror.html

@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <link rel="icon" type="image/ico" href="/favicon.ico" id="app-icon" />
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="/src/app/mirror/main.ts"></script>
+</body>
+
+</html>

文件差异内容过多而无法显示
+ 3214 - 0
package-lock.json


+ 45 - 0
package.json

@@ -0,0 +1,45 @@
+{
+  "name": "public-service",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "npm run build-quisk",
+    "build-quisk": "vite build ./ --mode=fire",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@amap/amap-jsapi-loader": "^1.0.1",
+    "@element-plus/icons-vue": "^2.1.0",
+    "@types/qs": "^6.9.7",
+    "@vueuse/components": "^10.11.0",
+    "@vueuse/core": "^10.11.0",
+    "@vueuse/router": "^10.11.0",
+    "axios": "^1.4.0",
+    "echarts": "^5.4.3",
+    "element-plus": "^2.3.8",
+    "js-base64": "^3.7.5",
+    "mime": "^3.0.0",
+    "mitt": "^3.0.1",
+    "qrcode.vue": "^3.4.1",
+    "qs": "^6.11.2",
+    "sass": "^1.64.2",
+    "sortablejs": "^1.15.2",
+    "swiper": "^11.1.4",
+    "three": "^0.158.0",
+    "unplugin-element-plus": "^0.7.2",
+    "unplugin-vue-define-options": "^1.3.12",
+    "vue": "^3.3.4",
+    "vue-cropper": "^1.0.9",
+    "vue-draggable-plus": "^0.5.0",
+    "vue-router": "^4.2.4"
+  },
+  "devDependencies": {
+    "@types/node": "^20.4.5",
+    "@vitejs/plugin-vue": "^4.2.3",
+    "typescript": "^5.0.2",
+    "vite": "^4.4.5",
+    "vue-tsc": "^1.8.5"
+  }
+}

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


二进制
public/favicon.ico


+ 14 - 0
public/xfile-viewer/.prettierrc

@@ -0,0 +1,14 @@
+{
+    "printWidth": 200,
+    "tabWidth": 4,
+    "useTabs": false,
+    "semi": false,
+    "singleQuote": true,
+    "arrowParens": "avoid",
+    "bracketSpacing": true,
+    "disableLanguages": [],
+    "eslintIntegration": false,
+    "stylelintIntegration": false,
+    "tslintIntegration": false,
+    "proseWrap": "preserve"
+}

+ 247 - 0
public/xfile-viewer/index.html

@@ -0,0 +1,247 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        <title>文档浏览</title>
+        <script src="./publish/lib/hammer.js"></script>
+        <script src="./publish/lib/cornerstone.js"></script>
+        <script src="./publish/lib/cornerstoneMath.min.js"></script>
+        <script src="./publish/lib/cornerstoneWADOImageLoader.bundle.min.js"></script>
+        <script src="./publish/lib/cornerstoneWebImageLoader.min.js"></script>
+        <script src="./publish/lib/cornerstoneTools.js"></script>
+        <script src="./publish/lib/dicomParser.min.js"></script>
+        <style>
+            .disabled {
+                opacity: 0.5;
+                pointer-events: none;
+            }
+            select {
+                outline: none;
+            }
+            html,
+            body {
+                width: 100%;
+                height: 100%;
+            }
+            body {
+                margin: 0;
+                overflow: hidden;
+            }
+            #dicomImage {
+                width: 100%;
+                height: 100%;
+            }
+            #toolbar {
+                position: absolute;
+                left: 50%;
+                top: 24px;
+                padding: 10px;
+                transform: translateX(-50%);
+                background-color: #fff;
+                border: solid 1px #e5e5e5;
+                border-radius: 6px;
+                z-index: 999;
+                display: flex;
+                align-items: center;
+                justify-content: space-around;
+                box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
+                font-size: 12px;
+            }
+            #toolbar > div {
+                cursor: pointer;
+                padding: 0 5px;
+            }
+            #toolbar > div.active {
+                color: #1779ed;
+            }
+            #statbar {
+                cursor: pointer;
+                position: absolute;
+                left: 50%;
+                bottom: 24px;
+                padding: 10px;
+                transform: translateX(-50%);
+                background-color: #fff;
+                border: solid 1px #e5e5e5;
+                border-radius: 6px;
+                z-index: 999;
+                display: none;
+                align-items: center;
+                justify-content: space-around;
+                box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
+                font-size: 12px;
+            }
+        </style>
+    </head>
+    <body>
+        <div id="dicomImage"></div>
+        <div id="toolbar">
+            <div id="Length">
+                标注:
+                <select onchange="tools.annotation(this.value)">
+                    <option value="">请选择</option>
+                    <option value="ArrowAnnotate">箭头</option>
+                    <option value="Length">长度</option>
+                    <option value="Angle">角度</option>
+                    <option value="RectangleRoi">矩形</option>
+                    <option value="EllipticalRoi">椭圆</option>
+                    <option value="FreehandRoi">面积</option>
+                    <option value="Probe">针探</option>
+                </select>
+            </div>
+            <div id="Wwwc" onclick="tools.wwwc()">调整窗宽窗高</div>
+            <div onclick="tools.download()">下载</div>
+        </div>
+        <div id="statbar" onclick="tools.annotation('')">退出</div>
+        <script>
+            const urlParams = key => {
+                let querys = window.location.search.substr(1).split('&')
+                for (let i = 0; i < querys.length; i++) {
+                    let keypair = querys[i].split('=')
+                    if (keypair.length === 2 && keypair[0] === key) {
+                        return keypair[1]
+                    }
+                }
+                return ''
+            }
+
+            const tools = {
+                name: '',
+                set active(name) {
+                    document.querySelectorAll('#toolbar >div').forEach(el => el.classList.remove('active'))
+                    if (name) {
+                        this.name = name
+                        document.getElementById(name).classList.add('active')
+                    } else {
+                        this.name = ''
+                    }
+                },
+                download() {
+                    var link = document.createElement('a')
+                    link.download = 'preview.png'
+                    link.href = document.querySelector('canvas').toDataURL('image/png').replace('image/png', 'image/octet-stream')
+                    link.click()
+                },
+                wwwc() {
+                    const name = 'Wwwc'
+                    if (this.name == name) {
+                        cornerstoneTools.setToolActive('Pan', {
+                            mouseButtonMask: 1,
+                        })
+                        this.active = ''
+                    } else {
+                        cornerstoneTools.setToolActive('Wwwc', {
+                            mouseButtonMask: 1,
+                        })
+                        this.active = name
+                    }
+                },
+                annotation(name) {
+                    const wwwc = document.getElementById('Wwwc')
+                    const leave = document.getElementById('statbar')
+                    const select = document.querySelector('select')
+                    if (name) {
+                        if (this.name == 'Wwwc') {
+                            this.wwwc()
+                        }
+                        wwwc.classList.add('disabled')
+                        leave.style.display = 'flex'
+                        select.disabled = true
+                        cornerstoneTools.setToolActive(name, { mouseButtonMask: 1 })
+                    } else {
+                        wwwc.classList.remove('disabled')
+                        leave.style.display = 'none'
+                        select.disabled = false
+                        document.querySelector('select').value = ''
+                        cornerstoneTools.setToolPassive(this.name)
+                        cornerstoneTools.setToolActive('Pan', {
+                            mouseButtonMask: 1,
+                        })
+                    }
+                    this.name = name
+                },
+            }
+            // 注册并挂载cornerstone及其cornerstoneTools,固定操作
+            cornerstoneTools.external.cornerstone = cornerstone
+            cornerstoneTools.external.cornerstoneMath = cornerstoneMath
+            cornerstoneTools.external.Hammer = Hammer
+            cornerstoneWADOImageLoader.external.dicomParser = dicomParser
+            cornerstoneWADOImageLoader.external.cornerstone = cornerstone
+            var file = urlParams('file')
+            if (!file) {
+                alert('文档不能为空')
+            } else {
+                var imageId = 'wadouri: ' + decodeURIComponent(file) //http://192.168.0.11:80/20231025172413.dcm'
+                // 初始化cornerstoneTools工具
+                cornerstoneTools.init([
+                    {
+                        moduleName: 'globalConfiguration',
+                        configuration: {
+                            showSVGCursors: true,
+                        },
+                    },
+                    {
+                        moduleName: 'segmentation',
+                        configuration: {
+                            outlineWidth: 2,
+                        },
+                    },
+                ])
+
+                // 获取要用于加载图片的div区域
+                var element = document.getElementById('dicomImage')
+                //激活获取到的用于图片加载的区域
+                cornerstone.enable(element)
+                //   // 从cornerstoneTools库中获取窗宽,窗高工具
+                //   const WwwcTool = cornerstoneTools.WwwcTool;
+                //   //添加获取到的窗宽,窗高工具
+                //   cornerstoneTools.addTool(WwwcTool);
+                //   // 绑定工具操作功能到鼠标左键
+                //   cornerstoneTools.setToolActive("Wwwc", {
+                //     mouseButtonMask: 1,
+                //   });
+                //使用loadAndCacheImage()方法加载并缓存图片,然后使用displayImage()方法显示图片。
+                cornerstone.loadAndCacheImage(imageId).then(function (image) {
+                    cornerstone.displayImage(element, image)
+                })
+
+                /* 平移、缩放  */
+                const PanTool = cornerstoneTools.PanTool
+                cornerstoneTools.addTool(PanTool)
+                cornerstoneTools.setToolActive('Pan', { mouseButtonMask: 1 })
+
+                const ZoomMouseWheelTool = cornerstoneTools.ZoomMouseWheelTool
+                cornerstoneTools.addTool(ZoomMouseWheelTool)
+                cornerstoneTools.setToolActive('ZoomMouseWheel', { mouseButtonMask: 1 })
+
+                /*  标注工具 */
+                // 长度标注
+                const LengthTool = cornerstoneTools.LengthTool
+                cornerstoneTools.addTool(LengthTool)
+
+                const AngleTool = cornerstoneTools.AngleTool
+                cornerstoneTools.addTool(AngleTool)
+
+                const ArrowAnnotateTool = cornerstoneTools.ArrowAnnotateTool
+                cornerstoneTools.addTool(ArrowAnnotateTool)
+
+                const RectangleRoiTool = cornerstoneTools.RectangleRoiTool
+                cornerstoneTools.addTool(RectangleRoiTool)
+
+                const EllipticalRoiTool = cornerstoneTools.EllipticalRoiTool
+                cornerstoneTools.addTool(EllipticalRoiTool)
+
+                const FreehandRoiTool = cornerstoneTools.FreehandRoiTool
+                cornerstoneTools.addTool(FreehandRoiTool)
+
+                const ProbeTool = cornerstoneTools.ProbeTool
+                cornerstoneTools.addTool(ProbeTool)
+
+                const WwwcTool = cornerstoneTools.WwwcTool
+                //添加获取到的窗宽,窗高工具
+                cornerstoneTools.addTool(WwwcTool)
+            }
+        </script>
+    </body>
+</html>

二进制
public/xfile-viewer/publish/20231025172413.dcm


二进制
public/xfile-viewer/publish/20231025172413.raw


文件差异内容过多而无法显示
+ 9084 - 0
public/xfile-viewer/publish/lib/cornerstone.js


文件差异内容过多而无法显示
+ 3 - 0
public/xfile-viewer/publish/lib/cornerstoneMath.min.js


文件差异内容过多而无法显示
+ 38415 - 0
public/xfile-viewer/publish/lib/cornerstoneTools.js


文件差异内容过多而无法显示
+ 2 - 0
public/xfile-viewer/publish/lib/cornerstoneWADOImageLoader.bundle.min.js


文件差异内容过多而无法显示
+ 3 - 0
public/xfile-viewer/publish/lib/cornerstoneWebImageLoader.min.js


文件差异内容过多而无法显示
+ 3 - 0
public/xfile-viewer/publish/lib/dicomParser.min.js


文件差异内容过多而无法显示
+ 2643 - 0
public/xfile-viewer/publish/lib/hammer.js


+ 12 - 0
src/App.vue

@@ -0,0 +1,12 @@
+<template>
+  <Locale>
+    <router-view v-slot="{ Component }">
+      <component :is="Component" />
+    </router-view>
+    <div id="dialog"></div>
+  </Locale>
+</template>
+
+<script setup lang="ts">
+import Locale from "@/config/locale.vue";
+</script>

+ 20 - 0
src/app/criminal/constant.ts

@@ -0,0 +1,20 @@
+import { AppConstant } from "../";
+import banner from "@/assets/image/criminalBanner.png";
+import ico from "@/assets/image/criminal.ico";
+import linkIco from "@/assets/image/criminal-32.png";
+
+import { criminalDeptId } from "@/constant/appDeptId";
+
+export const appConstant: AppConstant = {
+  title: "多尺度融合的现场3D数字化重建系统",
+  desc: "",
+  ico,
+
+  banner,
+  name: "criminal",
+  loginComponent: () => import("./view/login/index.vue"),
+  deptId: criminalDeptId,
+};
+
+const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
+link.setAttribute("href", linkIco);

+ 27 - 0
src/app/criminal/routeConfig.ts

@@ -0,0 +1,27 @@
+import { RouteName } from "@/router/routeName";
+import { Routes } from "@/router/config";
+
+export const CriminalRouteName = {
+  ...RouteName,
+  example: "example",
+} as const;
+
+export const menuRouteNames = [
+  // CriminalRouteName.statistics,
+  CriminalRouteName.vrmodel,
+  CriminalRouteName.camera,
+  CriminalRouteName.example,
+  CriminalRouteName.organization,
+  CriminalRouteName.role,
+  CriminalRouteName.user,
+  CriminalRouteName.downloadLog,
+];
+
+export const routes: Routes = [
+  {
+    name: CriminalRouteName.example,
+    path: "example",
+    component: () => import("@/app/criminal/view/example/index.vue"),
+    meta: { title: "案件管理", icon: "iconfire_management" },
+  },
+];

+ 24 - 0
src/app/criminal/store/example.ts

@@ -0,0 +1,24 @@
+import {
+  PaggingReq,
+  PaggingRes,
+  axios,
+  exampleList,
+  deleteExample,
+  setExample as setExampleUrl,
+} from "@/request";
+import { Case } from "@/store/case";
+
+export type Example = Case;
+
+export const getExamplePagging = async (
+  params: PaggingReq<Pick<Case, "caseTitle">>
+) => (await axios.get(exampleList, { params })).data as PaggingRes<Example>;
+
+export const delExample = (example: Example) =>
+  axios.post(deleteExample, { caseId: example.caseId });
+
+export const addExample = (caseTitle: string) =>
+  axios.post(setExampleUrl, { caseTitle });
+
+export const setExample = (caseTitle: string, id: number) =>
+  axios.post(setExampleUrl, { caseTitle, caseId: id });

+ 21 - 0
src/app/criminal/useStyle.scss

@@ -0,0 +1,21 @@
+@forward 'element-plus/theme-chalk/src/common/var.scss'with ($colors: ('primary': ('base': #26559B, )),
+  $common-component-size: ('default': 40px));
+
+.delBtn {
+  color   : #26559B;
+  // color: rgb(250, 85, 85);
+}
+
+body {
+
+
+  .home-content h1,
+  .info h1 {
+    color: rgba(0, 0, 0, 0.85);
+  }
+
+  .home-content p,
+  .info p {
+    color: rgba(0, 0, 0, 0.45);
+  }
+}

+ 33 - 0
src/app/criminal/view/example/edit.vue

@@ -0,0 +1,33 @@
+<template>
+  <el-form ref="form" label-width="84px">
+    <el-form-item label="案件名称">
+      <el-input
+        v-model="bindExample.caseTitle"
+        maxlength="50"
+        placeholder="请输入案件名称"
+      />
+    </el-form-item>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import { Example, setExample, addExample } from "@/app/criminal/store/example";
+import { ElMessage } from "element-plus";
+import { QuiskExpose } from "@/helper/mount";
+
+const props = defineProps<{ example?: Example }>();
+const bindExample = ref<Example>(props.example ? { ...props.example } : ({} as Example));
+
+defineExpose<QuiskExpose>({
+  async submit() {
+    if (!bindExample.value.caseTitle || !bindExample.value.caseTitle.trim()) {
+      ElMessage.error("案件名称不能为空");
+      throw "案件名称不能为空";
+    }
+    await (bindExample.value.caseId
+      ? setExample(bindExample.value.caseTitle, bindExample.value.caseId)
+      : addExample(bindExample.value.caseTitle));
+  },
+});
+</script>

+ 109 - 0
src/app/criminal/view/example/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <com-head :options="[{ name: '案件管理', value: '2' }]">
+    <el-form label-width="84px" inline>
+      <el-form-item label="标题:">
+        <el-input v-model="state.query.caseTitle" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="承办单位:">
+        <com-company v-model="state.query.deptId" />
+      </el-form-item>
+      <el-form-item class="searh-btns" style="grid-area: 1 / 4 / 2 / 6">
+        <el-button type="primary" @click="refresh">查询</el-button>
+        <el-button type="primary" plain @click="queryReset">重置</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <div class="body-head">
+      <h3 style="visibility: hidden">案件列表</h3>
+      <div class="table-ctrl-right">
+        <el-button type="primary" @click="addHandler" v-pdpath="['add']">
+          新增案件
+        </el-button>
+      </div>
+    </div>
+
+    <el-table
+      :data="state.table.rows"
+      id="multipleTable"
+      style="width: 100%"
+      class="table"
+      size="large"
+    >
+      <el-table-column label="序号" width="70" v-slot:default="{ $index }">
+        <div style="text-align: center">
+          {{ state.pag.size * (state.pag.currentPage - 1) + $index + 1 }}
+        </div>
+      </el-table-column>
+      <el-table-column label="标题" prop="caseTitle"></el-table-column>
+      <el-table-column label="承办单位" prop="deptName"></el-table-column>
+      <el-table-column label="创建时间" prop="createTime"></el-table-column>
+      <el-table-column
+        label="操作"
+        v-slot:default="{ row }: { row: Example }"
+        :width="240"
+      >
+        <CaseEditMenu
+          :caseId="row.caseId"
+          :title="row.caseTitle"
+          :prevMenu="[
+            { key: 'info', label: '编辑案件', onClick: () => editHandler(row) },
+          ]"
+          v-if="row.caseId"
+        />
+        <span class="oper-span" @click="gotoQuery(row.caseId)" v-pdpath="['view']"
+          >查看</span
+        >
+        <span
+          class="oper-span"
+          @click="del(row)"
+          style="color: var(--primaryColor)"
+          v-pdpath="['del']"
+        >
+          删除
+        </span>
+      </el-table-column>
+    </el-table>
+
+    <com-pagination
+      @size-change="changPageSize"
+      @current-change="changPageCurrent"
+      :current-page="state.pag.currentPage"
+      :page-size="state.pag.size"
+      :total="state.pag.total"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import comHead from "@/components/head/index.vue";
+import comCompany from "@/components/company-select/index.vue";
+import comPagination from "@/components/pagination/index.vue";
+import { usePagging } from "@/hook/pagging";
+import { Example, delExample, getExamplePagging } from "@/app/criminal/store/example";
+import CaseEditMenu from "@/view/case/editMenu.vue";
+import { gotoQuery } from "@/view/case/help";
+import { addExample, editExample } from "./quisk";
+
+const { state, refresh, queryReset, del, changPageSize, changPageCurrent } = usePagging({
+  get: getExamplePagging,
+  del: delExample,
+  mapper: {
+    delMsg: "删除案件,相关档案也会一并删除,确定要删除吗?",
+  },
+  paramsTemlate: { caseTitle: "", deptId: "" },
+});
+
+const addHandler = async () => {
+  if (await addExample({})) {
+    refresh();
+  }
+};
+
+const editHandler = async (example: Example) => {
+  if (await editExample({ example })) {
+    refresh();
+  }
+};
+</script>

+ 12 - 0
src/app/criminal/view/example/quisk.ts

@@ -0,0 +1,12 @@
+import Edit from "./edit.vue";
+import { quiskMountFactory } from "@/helper/mount";
+
+export const addExample = quiskMountFactory(Edit, {
+  title: "新增案件",
+  width: 500,
+});
+
+export const editExample = quiskMountFactory(Edit, {
+  title: "编辑案件",
+  width: 500,
+});

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

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

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

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

+ 14 - 0
src/app/fire/constant.ts

@@ -0,0 +1,14 @@
+import { AppConstant } from "../";
+import banner from "@/assets/image/fireBanner.png";
+import ico from "@/assets/image/fire.ico";
+import { fireDeptId } from "@/constant/appDeptId";
+// 新疆消防救援总队火灾现场重建平台
+export const appConstant: AppConstant = {
+  title: "消防火调三维远程勘验平台",
+  // desc: "Three-dimensional remote prospecting platform for fire scenes",
+  desc: "",
+  ico,
+  banner,
+  name: "fire",
+  deptId: fireDeptId,
+};

+ 582 - 0
src/app/fire/constant/fire.ts

@@ -0,0 +1,582 @@
+import { FireStatus } from "@/app/fire/store/fire";
+
+// 火灾原因
+export const reason = [
+  {
+    label: "放火",
+    value: "放火",
+    children: [
+      { value: "刑事防火", label: "刑事防火" },
+      { value: "精神病、智障等人士防火", label: "精神病、智障等人士防火" },
+      { value: "自焚", label: "自焚" },
+      { value: "其他", label: "其他" },
+    ],
+  },
+  {
+    value: "电气火灾",
+    label: "电气火灾",
+    children: [
+      { label: "蓄电池故障(热失控)", value: "蓄电池故障(热失控)" },
+      {
+        label: "电气线路故障(线路接插件、配电盘、控制装置等) ",
+        value: "电气线路故障(线路接插件、配电盘、控制装置等) ",
+      },
+      { label: "电动机及其他工业设备", value: "电动机及其他工业设备" },
+      { label: "电器设备故障", value: "电器设备故障" },
+      { label: "电加热器具火灾", value: "电加热器具火灾" },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  {
+    value: "产作业",
+    label: "产作业",
+    children: [
+      { label: "焊割", value: "焊割" },
+      { label: "熬炼", value: "熬炼" },
+      { label: "化工生产", value: "化工生产" },
+      { label: "机械设备类故障", value: "机械设备类故障" },
+      { label: "烘烤", value: "烘烤" },
+      { label: "接触高温", value: "接触高温" },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  {
+    value: "用火不慎",
+    label: "用火不慎",
+    children: [
+      { label: "余火复燃", value: "余火复燃" },
+      { label: "照明不慎", value: "照明不慎" },
+      { label: "烘烤不慎", value: "烘烤不慎" },
+      { label: "敬神祭祖", value: "敬神祭祖" },
+      { label: "油锅起火", value: "油锅起火" },
+      { label: "燃气炉具故障及使用不当", value: "燃气炉具故障及使用不当" },
+      { label: "燃油炉具故障及使用不当", value: "燃油炉具故障及使用不当" },
+      { label: "其他炉具故障及使用不当", value: "其他炉具故障及使用不当" },
+      { label: "烟道过热窜火、飞火等", value: "烟道过热窜火、飞火等" },
+      { label: "烧荒、野外生火不慎", value: "烧荒、野外生火不慎" },
+      { label: "使用蚊香不慎", value: "使用蚊香不慎" },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  {
+    value: "吸烟",
+    label: "吸烟",
+    children: [
+      { label: "违章吸烟", value: "违章吸烟" },
+      { label: "乱扔烟头、火柴等", value: "乱扔烟头、火柴等" },
+      { label: "卧床吸烟", value: "卧床吸烟" },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  {
+    value: "玩火",
+    label: "玩火",
+    children: [
+      { label: "小孩玩火", value: "小孩玩火" },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  { label: "燃放烟花爆竹", value: "燃放烟花爆竹" },
+  { label: "自燃", value: "自燃" },
+  { label: "雷击", value: "雷击" },
+  { label: "静电", value: "静电" },
+  { label: "遗留火种", value: "遗留火种" },
+  { label: "其他", value: "其他" },
+  { label: "不排除原因", value: "不排除原因" },
+];
+
+// 天气情况等标准化勘验信息
+export const place = [
+  {
+    label: "非建构筑物",
+    value: "非建构筑物",
+    children: [
+      { label: "室外集贸市场", value: "室外集贸市场" },
+      { label: "露天堆垛", value: "露天堆垛" },
+      {
+        label: "露天农副业场所",
+        value: "露天农副业场所",
+        children: [
+          { label: "农田", value: "农田" },
+          { label: "经济林木(含果园)", value: "经济林木(含果园)" },
+          { label: "苗圃", value: "苗圃" },
+          { label: "花卉", value: "花卉" },
+          { label: "养殖场", value: "养殖场" },
+          { label: "大棚", value: "大棚" },
+          { label: "其他", value: "其他" },
+        ],
+      },
+      {
+        label: "室外独立生产设施设备",
+        value: "室外独立生产设施设备",
+        children: [
+          { label: "风力发电设施", value: "风力发电设施" },
+          { label: "光伏发电设施", value: "光伏发电设施" },
+          {
+            label: "小型变压器、配电柜等室外输配电设备",
+            value: "小型变压器、配电柜等室外输配电设备",
+          },
+          { label: "变电站", value: "变电站" },
+          { label: "通讯基站", value: "通讯基站" },
+          { label: "其他室外独立式生产设施", value: "其他室外独立式生产设施" },
+        ],
+      },
+      { label: "公园", value: "公园" },
+      { label: "森林", value: "森林" },
+      { label: "草原", value: "草原" },
+      { label: "道路绿化带、隔离带", value: "道路绿化带、隔离带" },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  {
+    label: "交通工具",
+    value: "交通工具",
+    children: [
+      {
+        label: "客车",
+        value: "客车",
+        children: [
+          { label: "燃油车", value: "燃油车" },
+          { label: "燃气车", value: "燃气车" },
+          { label: "电动车", value: "电动车" },
+          { label: "混合电动车", value: "混合电动车" },
+        ],
+      },
+      {
+        label: "货车",
+        value: "货车",
+        children: [
+          { label: "燃油车", value: "燃油车" },
+          { label: "燃气车", value: "燃气车" },
+          { label: "电动车", value: "电动车" },
+          { label: "混合电动车", value: "混合电动车" },
+        ],
+      },
+      {
+        label: "轿车",
+        value: "轿车",
+        children: [
+          { label: "燃油车", value: "燃油车" },
+          { label: "燃气车", value: "燃气车" },
+          { label: "电动车", value: "电动车" },
+          { label: "混合电动车", value: "混合电动车" },
+        ],
+      },
+      {
+        label: "特种车",
+        value: "特种车",
+        children: [
+          { label: "燃油车", value: "燃油车" },
+          { label: "燃气车", value: "燃气车" },
+          { label: "电动车", value: "电动车" },
+          { label: "混合电动车", value: "混合电动车" },
+        ],
+      },
+      { label: "摩托车", value: "摩托车" },
+      { label: "铁路列车", value: "铁路列车" },
+      { label: "船舶", value: "船舶" },
+      { label: "航空(天)", value: "航空(天)" },
+      { label: "城市轨道交通工具", value: "城市轨道交通工具" },
+      { label: "农用机械", value: "农用机械" },
+      {
+        label: "电动助力车(三轮车、自行车)",
+        value: "电动助力车(三轮车、自行车)",
+      },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  {
+    label: "建构筑物",
+    value: "建构筑物",
+    children: [
+      {
+        label: "居住场所",
+        value: "居住场所",
+        children: [
+          { label: "自建住宅", value: "自建住宅" },
+          {
+            label: "非自建住宅",
+            value: "非自建住宅",
+            children: [
+              { label: "商品房	", value: "商品房	" },
+              { label: "经适房	", value: "经适房	" },
+              { label: "集资房	", value: "集资房	" },
+              { label: "公寓房	", value: "公寓房	" },
+            ],
+          },
+          { label: "员工集体宿舍", value: "员工集体宿舍" },
+        ],
+      },
+      { label: "办公场所", value: "办公场所" },
+      {
+        label: "学校",
+        value: "学校",
+        children: [
+          {
+            label: "幼儿园托儿所",
+            value: "幼儿园托儿所",
+            children: [
+              { label: "教学场所	", value: "教学场所	" },
+              { label: "实验场所	", value: "实验场所	" },
+              { label: "学生集体宿舍	", value: "学生集体宿舍	" },
+              { label: "食堂	", value: "食堂	" },
+              { label: "礼堂等室内活动场馆	", value: "礼堂等室内活动场馆	" },
+              { label: "其他	", value: "其他	" },
+            ],
+          },
+          {
+            label: "小学",
+            value: "小学",
+            children: [
+              { label: "教学场所	", value: "教学场所	" },
+              { label: "实验场所	", value: "实验场所	" },
+              { label: "学生集体宿舍	", value: "学生集体宿舍	" },
+              { label: "食堂	", value: "食堂	" },
+              { label: "礼堂等室内活动场馆	", value: "礼堂等室内活动场馆	" },
+              { label: "其他	", value: "其他	" },
+            ],
+          },
+          {
+            label: "中学",
+            value: "中学",
+            children: [
+              { label: "教学场所	", value: "教学场所	" },
+              { label: "实验场所	", value: "实验场所	" },
+              { label: "学生集体宿舍	", value: "学生集体宿舍	" },
+              { label: "食堂	", value: "食堂	" },
+              { label: "礼堂等室内活动场馆	", value: "礼堂等室内活动场馆	" },
+              { label: "其他	", value: "其他	" },
+            ],
+          },
+          {
+            label: "大学",
+            value: "大学",
+            children: [
+              { label: "教学场所	", value: "教学场所	" },
+              { label: "实验场所	", value: "实验场所	" },
+              { label: "学生集体宿舍	", value: "学生集体宿舍	" },
+              { label: "食堂	", value: "食堂	" },
+              { label: "礼堂等室内活动场馆	", value: "礼堂等室内活动场馆	" },
+              { label: "其他	", value: "其他	" },
+            ],
+          },
+          {
+            label: "校外培训机构",
+            value: "校外培训机构",
+            children: [
+              { label: "教学场所	", value: "教学场所	" },
+              { label: "实验场所	", value: "实验场所	" },
+              { label: "学生集体宿舍	", value: "学生集体宿舍	" },
+              { label: "食堂	", value: "食堂	" },
+              { label: "礼堂等室内活动场馆	", value: "礼堂等室内活动场馆	" },
+              { label: "其他	", value: "其他	" },
+            ],
+          },
+          {
+            label: "成人教育机构",
+            value: "成人教育机构",
+            children: [
+              { label: "教学场所	", value: "教学场所	" },
+              { label: "实验场所	", value: "实验场所	" },
+              { label: "学生集体宿舍	", value: "学生集体宿舍	" },
+              { label: "食堂	", value: "食堂	" },
+              { label: "礼堂等室内活动场馆	", value: "礼堂等室内活动场馆	" },
+              { label: "其他	", value: "其他	" },
+            ],
+          },
+        ],
+      },
+      {
+        label: "商业场所",
+        value: "商业场所",
+        children: [
+          { label: "商场", value: "商场" },
+          { label: "超市", value: "超市" },
+          { label: "室内市场", value: "室内市场" },
+          { label: "经营性小场所", value: "经营性小场所" },
+          { label: "其他", value: "其他" },
+        ],
+      },
+      {
+        label: "宾馆、饭店、招待所",
+        value: "宾馆、饭店、招待所",
+        children: [
+          { label: "客房", value: "客房" },
+          { label: "餐饮", value: "餐饮" },
+          { label: "办公室", value: "办公室" },
+          { label: "会议室", value: "会议室" },
+          { label: "民宿", value: "民宿" },
+        ],
+      },
+      {
+        label: "纯餐饮场所",
+        value: "纯餐饮场所",
+        children: [
+          { label: "餐馆酒家", value: "餐馆酒家" },
+          { label: "农家乐", value: "农家乐" },
+          { label: "露天大排档", value: "露天大排档" },
+          { label: "私房菜馆", value: "私房菜馆" },
+        ],
+      },
+      {
+        label: "医疗机构",
+        value: "医疗机构",
+        children: [
+          { label: "医院", value: "医院" },
+          { label: "诊所", value: "诊所" },
+          { label: "康复中心", value: "康复中心" },
+          { label: "精神病院", value: "精神病院" },
+        ],
+      },
+      {
+        label: "养老院",
+        value: "养老院",
+        children: [
+          { label: "公办养老院", value: "公办养老院" },
+          { label: "私营养老院", value: "私营养老院" },
+        ],
+      },
+      { label: "福利院", value: "福利院" },
+      {
+        label: "公共娱乐场所",
+        value: "公共娱乐场所",
+        children: [
+          {
+            label: "影剧院、礼堂等演出、放映场所",
+            value: "影剧院、礼堂等演出、放映场所",
+          },
+          {
+            label: "舞厅、卡拉OK厅等歌舞娱乐场所",
+            value: "舞厅、卡拉OK厅等歌舞娱乐场所",
+          },
+          {
+            label: "具有娱乐功能的夜总会、音乐茶座场所",
+            value: "具有娱乐功能的夜总会、音乐茶座场所",
+          },
+          {
+            label:
+              "保龄球馆、旱冰场、按摩、沐足、棋牌、桑拿浴室等营业性健身、休闲场所",
+            value:
+              "保龄球馆、旱冰场、按摩、沐足、棋牌、桑拿浴室等营业性健身、休闲场所",
+          },
+          { label: "儿童娱乐、活动场所", value: "儿童娱乐、活动场所" },
+          { label: "游艺等其他公共娱乐场所", value: "游艺等其他公共娱乐场所" },
+        ],
+      },
+      {
+        label: "体育场馆",
+        value: "体育场馆",
+        children: [
+          { label: "室内部分", value: "室内部分" },
+          { label: "露天部分", value: "露天部分" },
+        ],
+      },
+      {
+        label: "金融交易场所",
+        value: "金融交易场所",
+        children: [
+          { label: "证券交易所", value: "证券交易所" },
+          { label: "银行网点", value: "银行网点" },
+        ],
+      },
+      {
+        label: "交通枢纽(站)",
+        value: "交通枢纽(站)",
+        children: [
+          { label: "候机场所", value: "候机场所" },
+          { label: "候船场所", value: "候船场所" },
+          {
+            label: "候车场所",
+            value: "候车场所",
+            children: [
+              { label: "汽车客运站	", value: "汽车客运站	" },
+              {
+                label: "铁路、高铁、城轨候车场所	",
+                value: "铁路、高铁、城轨候车场所	",
+              },
+            ],
+          },
+          { label: "地铁站", value: "地铁站" },
+          { label: "公交站", value: "公交站" },
+          { label: "交通综合枢纽", value: "交通综合枢纽" },
+        ],
+      },
+      {
+        label: "文物古建筑",
+        value: "文物古建筑",
+        children: [
+          { label: "国家级", value: "国家级" },
+          { label: "省级", value: "省级" },
+          { label: "市、县级", value: "市、县级" },
+          { label: "其他", value: "其他" },
+        ],
+      },
+      {
+        label: "文博馆(图书馆、博物馆、档案馆等)",
+        value: "文博馆(图书馆、博物馆、档案馆等)",
+      },
+      { label: "科研试验场所", value: "科研试验场所" },
+      { label: "广播电视中心", value: "广播电视中心" },
+      {
+        label: "通信场所",
+        value: "通信场所",
+        children: [
+          { label: "营业厅", value: "营业厅" },
+          { label: "机房", value: "机房" },
+          { label: "其他", value: "其他" },
+        ],
+      },
+      {
+        label: "宗教场所",
+        value: "宗教场所",
+        children: [
+          { label: "寺庙", value: "寺庙" },
+          { label: "教堂", value: "教堂" },
+          { label: "道观", value: "道观" },
+          { label: "其他民间信仰场所", value: "其他民间信仰场所" },
+        ],
+      },
+      { label: "会议、展览中心", value: "会议、展览中心" },
+      {
+        label: "物资仓储场所",
+        value: "物资仓储场所",
+        children: [
+          {
+            label: "危险品",
+            value: "危险品",
+            children: [
+              { label: "甲类	", value: "甲类	" },
+              { label: "乙类	", value: "乙类	" },
+              { label: "丙类	", value: "丙类	" },
+              { label: "丁类	", value: "丁类	" },
+              { label: "戊类	", value: "戊类	" },
+            ],
+          },
+          { label: "棉花", value: "棉花" },
+          { label: "粮食", value: "粮食" },
+          { label: "百货", value: "百货" },
+          { label: "综合", value: "综合" },
+          { label: "物流快递", value: "物流快递" },
+          { label: "三合一、多合一", value: "三合一、多合一" },
+          { label: "经营性小场所", value: "经营性小场所" },
+        ],
+      },
+      {
+        label: "厂房",
+        value: "厂房",
+        children: [
+          { label: "甲乙类生产", value: "甲乙类生产" },
+          { label: "丙类生产", value: "丙类生产" },
+          { label: "丁戊类生产", value: "丁戊类生产" },
+          { label: "劳动密集型", value: "劳动密集型" },
+          { label: "三合一、多合一", value: "三合一、多合一" },
+          { label: "经营性小场所", value: "经营性小场所" },
+          { label: "其他", value: "其他" },
+        ],
+      },
+      {
+        label: "加油加气站充电站",
+        value: "加油加气站充电站",
+        children: [
+          { label: "加油站", value: "加油站" },
+          { label: "加气站", value: "加气站" },
+          { label: "充电站", value: "充电站" },
+          { label: "加油加气站", value: "加油加气站" },
+          { label: "其他", value: "其他" },
+        ],
+      },
+      {
+        label: "汽车库",
+        value: "汽车库",
+        children: [
+          { label: "高层", value: "高层" },
+          { label: "多层", value: "多层" },
+          { label: "地下", value: "地下" },
+          { label: "半地下", value: "半地下" },
+          { label: "机械", value: "机械" },
+          { label: "敞开式", value: "敞开式" },
+        ],
+      },
+      {
+        label: "修车库",
+        value: "修车库",
+        children: [
+          { label: "简易修理厂", value: "简易修理厂" },
+          { label: "临时搭建修理厂", value: "临时搭建修理厂" },
+        ],
+      },
+      {
+        label: "工地",
+        value: "工地",
+        children: [
+          {
+            label: "建设工地",
+            value: "建设工地",
+            children: [
+              {
+                label: "建筑物、构筑物、设施等	",
+                value: "建筑物、构筑物、设施等	",
+              },
+              { label: "临时宿舍	", value: "临时宿舍	" },
+              { label: "临时办公场所	", value: "临时办公场所	" },
+              { label: "临时食堂	", value: "临时食堂	" },
+              { label: "临时室内仓库	", value: "临时室内仓库	" },
+              { label: "临时建材堆放场所	", value: "临时建材堆放场所	" },
+            ],
+          },
+          { label: "装修、改造工地", value: "装修、改造工地" },
+          { label: "动拆迁工地", value: "动拆迁工地" },
+        ],
+      },
+      {
+        label: "室内农副业场所",
+        value: "室内农副业场所",
+        children: [
+          { label: "温室", value: "温室" },
+          { label: "养殖场", value: "养殖场" },
+        ],
+      },
+      {
+        label: "石油化工企业",
+        value: "石油化工企业",
+        children: [
+          { label: "生产装置", value: "生产装置" },
+          { label: "储罐区", value: "储罐区" },
+          { label: "生活区", value: "生活区" },
+          { label: "办公区", value: "办公区" },
+        ],
+      },
+      { label: "其他", value: "其他" },
+    ],
+  },
+  {
+    label: "垃圾及废弃物",
+    value: "垃圾及废弃物",
+    children: [
+      { label: "垃圾箱", value: "垃圾箱" },
+      { label: "垃圾堆", value: "垃圾堆" },
+      { label: "垃圾场", value: "垃圾场" },
+      { label: "垃圾中转站", value: "垃圾中转站" },
+      { label: "废品回收场所", value: "废品回收场所" },
+      { label: "其他", value: "其他" },
+    ],
+  },
+];
+
+export const fireStatusDesc = {
+  [FireStatus.all]: "全部",
+  [FireStatus.incomplete]: "未认定",
+  [FireStatus.complete]: "已认定",
+};
+
+export const fireStatuOptions = [
+  {
+    value: FireStatus.all,
+    label: fireStatusDesc[FireStatus.all],
+  },
+  {
+    value: FireStatus.incomplete,
+    label: fireStatusDesc[FireStatus.incomplete],
+  },
+  { value: FireStatus.complete, label: fireStatusDesc[FireStatus.complete] },
+];

+ 46 - 0
src/app/fire/routeConfig.ts

@@ -0,0 +1,46 @@
+import { RouteName } from "@/router/routeName";
+import { Routes } from "@/router/config";
+
+export const FireRouteName = {
+  ...RouteName,
+  dispatch: "dispatch",
+  teaching: "teaching",
+  recycle: "caseRecovery",
+} as const;
+
+export const menuRouteNames = [
+  FireRouteName.vrmodel,
+  FireRouteName.camera,
+  FireRouteName.dispatch,
+  FireRouteName.teaching,
+  FireRouteName.statistics,
+  FireRouteName.organization,
+  FireRouteName.role,
+  FireRouteName.user,
+  FireRouteName.downloadLog,
+  FireRouteName.setting,
+  FireRouteName.recycle,
+  FireRouteName.cameraVersion,
+  FireRouteName.cameraVersionApp,
+];
+
+export const routes: Routes = [
+  {
+    name: FireRouteName.dispatch,
+    path: "dispatch",
+    component: () => import("./view/dispatch/index.vue"),
+    meta: { title: "火调管理", icon: "iconfire_management" },
+  },
+  {
+    name: FireRouteName.teaching,
+    path: "teaching",
+    component: () => import("./view/dispatch/index.vue"),
+    meta: { title: "教学平台", icon: "iconfire_study" },
+  },
+  {
+    name: FireRouteName.recycle,
+    path: "recycle",
+    component: () => import("./view/dispatch/index.vue"),
+    meta: { title: "案件回收", icon: "icon-del" },
+  },
+];

+ 106 - 0
src/app/fire/store/fire.ts

@@ -0,0 +1,106 @@
+import { UN_REQ_NUM } from "@/constant/sys";
+import {
+  PaggingReq,
+  PaggingRes,
+  axios,
+  fireDetail,
+  getFireList,
+  getMessageList,
+  insertFire,
+  insertMessage,
+  setTeach,
+  setUnTeach,
+  updateFire,
+} from "@/request";
+
+export enum FireStatus {
+  all = UN_REQ_NUM,
+  incomplete = 0,
+  complete = 1,
+}
+
+export type Fire = {
+  accidentDate: string;
+  createTime: string;
+  creatorDeptId: string;
+  caseId: number;
+  mapShow: boolean;
+  creatorId: string;
+  creatorName: string;
+  deptId: string;
+  editTime: string;
+  editorId: string;
+  editorName: string;
+  fireReason: string;
+  id: string;
+  isTeached: number;
+  organizerDeptName: string;
+  organizerUsers: string;
+  projectAddress: string;
+  latAndLong: string;
+  latlng: string;
+  projectName: string;
+  projectSite: string;
+  projectSiteCode: string;
+  projectSn: string;
+  status: FireStatus;
+  statusDesc: string;
+  updateTime: string;
+  isDelete?: number;
+
+  field1: string;
+  field2: string;
+  field3: string;
+  field4: string;
+
+  field5: string;
+  field6: string;
+  field7: string;
+  field8: string;
+  field9: string;
+  field10: string;
+};
+
+export enum FirePaggingRoute {
+  fire = 1,
+  teached = 2,
+}
+
+type FirePaggingParams = PaggingReq<Fire & { queryType: FirePaggingRoute }>;
+export const getFirePagging = async (params: FirePaggingParams) =>
+  (await axios.get(getFireList, { params })).data as PaggingRes<Fire>;
+
+export const addFire = async (fire: Omit<Fire, "id">) =>
+  axios.post(insertFire, fire);
+
+export const setFire = async (fire: Fire) => await axios.post(updateFire, fire);
+
+export const delFire = (fire: Fire, isDelete = 1) =>
+  setFire({ ...fire, isDelete: isDelete });
+
+export const getFire = async (projectId: string) =>
+  (await axios.get<Fire>(fireDetail, { params: { type: 0, projectId } })).data;
+
+export const revokeFireTeachs = (ids: string[]) =>
+  axios.post(setUnTeach, { ids: ids.join(",") });
+
+export const setFireTeachs = (ids: string[]) =>
+  axios.post(setTeach, { ids: ids.join(",") });
+
+export type FireLeaveMsg = {
+  id: string;
+  content: string;
+  userName: string;
+  createTime: number;
+};
+
+export const getFireLeaveMsgPagging = async (
+  params: PaggingReq<{ projectId: string }>
+) =>
+  (await axios.get(getMessageList, { params }))
+    .data as PaggingRes<FireLeaveMsg>;
+
+export const addFireLeaveMsg = (params: {
+  content: string;
+  projectId: string;
+}) => axios.post(insertMessage, params);

+ 7 - 0
src/app/fire/useStyle.scss

@@ -0,0 +1,7 @@
+@forward 'element-plus/theme-chalk/src/common/var.scss'with ($colors: ('primary': ('base': #D8000A, )),
+  $common-component-size: ('default': 40px));
+
+.delBtn {
+  color   : #D8000A;
+  // color: rgb(250, 85, 85);
+}

+ 321 - 0
src/app/fire/view/dispatch/editFire.vue

@@ -0,0 +1,321 @@
+<template>
+  <el-form ref="form" label-width="106px" class="camera-from">
+    <div class="def-select-map" ref="mapEl"></div>
+    <div class="el-form-item">
+      <el-col :span="12">
+        <el-form-item label="项目编号" class="mandatory">
+          <el-input
+            v-model="bindFire.projectSn"
+            maxlength="18"
+            placeholder="请输入项目编号"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="起火对象" class="mandatory">
+          <el-input
+            v-model="bindFire.projectName"
+            maxlength="50"
+            placeholder="请输入起火对象"
+          />
+        </el-form-item>
+      </el-col>
+    </div>
+    <el-form-item label="详细地址" class="mandatory asdasd">
+      <!-- <el-input
+        v-model="bindFire.projectAddress"
+        maxlength="50"
+        placeholder="请输入详细地址"
+      /> -->
+      <el-input
+        v-model="bindFire.projectAddress"
+        placeholder="输入名称搜索"
+        clearable
+        disabled
+      >
+        <template #append>
+          <el-button :icon="Search" @click="searchAMapAddress" />
+        </template>
+      </el-input>
+    </el-form-item>
+    <el-form-item label="勘验地址">
+      <el-input v-model="bindFire.field1" maxlength="50" placeholder="请输入勘验地址" />
+    </el-form-item>
+    <el-form-item label="起火场所" class="mandatory">
+      <el-cascader
+        style="width: 100%"
+        v-model="projectSite"
+        placeholder="起火场所"
+        :options="place"
+        :props="{ expandTrigger: 'hover' }"
+      />
+    </el-form-item>
+    <!-- <el-form-item label="全宗名称">
+      <el-input v-model="bindFire.field2" maxlength="50" placeholder="请输入全宗名称" />
+    </el-form-item> -->
+
+    <div class="el-form-item">
+      <el-col :span="12">
+        <el-form-item label="勘验单位" class="mandatory">
+          <companySelect v-model="bindFire.deptId" hideAll :notUpdate="true" disabled />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="勘验人员" class="mandatory">
+          <el-input
+            v-model="bindFire.organizerUsers"
+            maxlength="50"
+            placeholder="请输入勘验人员"
+          />
+        </el-form-item>
+      </el-col>
+    </div>
+    <div class="el-form-item">
+      <!-- <el-col :span="12">
+        <el-form-item label="勘验人姓名">
+          <el-input
+            v-model="bindFire.field3"
+            maxlength="18"
+            placeholder="请输入勘验人姓名"
+          />
+        </el-form-item>
+      </el-col> -->
+      <el-col :span="12">
+        <el-form-item label="勘验人职务">
+          <el-input
+            v-model="bindFire.field4"
+            maxlength="50"
+            placeholder="请输入勘验人职务"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="火灾原因" class="mandatory">
+          <el-cascader
+            style="width: 100%"
+            v-model="fireReason"
+            placeholder="火灾原因:"
+            :options="reason"
+            :props="{ expandTrigger: 'hover' }"
+          />
+        </el-form-item>
+      </el-col>
+    </div>
+
+    <div class="el-form-item">
+      <el-col :span="12">
+        <el-form-item label="事故日期" class="mandatory" placeholder="请选择事故日期">
+          <el-date-picker
+            type="date"
+            v-model="accidentDate"
+            style="width: 100%"
+            :disabled-date="(date) => date.getTime() > new Date().getTime()"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="大屏显示" class="mandatory">
+          <el-switch v-model="bindFire.mapShow" :disabled="!bindFire.latlng" />
+        </el-form-item>
+      </el-col>
+    </div>
+
+    <div class="el-form-item">
+      <el-col :span="12">
+        <el-form-item label="事件分类">
+          <el-input
+            v-model="bindFire.field5"
+            maxlength="18"
+            placeholder="请输入事件分类"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="分类登记">
+          <el-input
+            v-model="bindFire.field6"
+            maxlength="50"
+            placeholder="请输入分类登记"
+          />
+        </el-form-item>
+      </el-col>
+    </div>
+
+    <div class="el-form-item">
+      <el-col :span="12">
+        <el-form-item label="天气情况">
+          <el-input
+            v-model="bindFire.field7"
+            placeholder="请输入天气情况"
+            show-word-limit
+            maxlength="100"
+          />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item
+          label="勘验起止时间"
+          class="mandatory"
+          placeholder="请选择勘验起止时间"
+        >
+          <el-date-picker
+            v-model="f8"
+            type="daterange"
+            range-separator="-"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            :disabled-date="(date) => date.getTime() > new Date().getTime()"
+          />
+          <!-- <el-date-picker
+            type="date"
+            v-model="f8"
+            style="width: 100%"
+            :disabled-date="(date) => date.getTime() > new Date().getTime()"
+          /> -->
+        </el-form-item>
+      </el-col>
+    </div>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import companySelect from "@/components/company-select/index.vue";
+import { ref, watchEffect } from "vue";
+import { Fire, setFire, addFire } from "@/app/fire/store/fire";
+import { reason, place } from "@/app/fire/constant/fire";
+import { ElMessage } from "element-plus";
+import { dateFormat, debounce } from "@/util";
+import { genCascaderValue, getCode } from "@/helper/cascader";
+import { QuiskExpose } from "@/helper/mount";
+import AMapLoader from "@amap/amap-jsapi-loader";
+import { user } from "@/store/user";
+import { Search } from "@element-plus/icons-vue";
+import { selectMapImage } from "@/view/case/quisk";
+
+const props = defineProps<{ fire?: Fire }>();
+
+const bindFire = ref<Fire>(
+  props.fire
+    ? { ...props.fire }
+    : ({
+        deptId: user.value.info.deptId,
+      } as Fire)
+);
+
+const fireReason = genCascaderValue(bindFire, "fireReason");
+const projectSite = genCascaderValue(bindFire, "projectSite");
+const accidentDate = ref(
+  bindFire.value.accidentDate ? new Date(bindFire.value.accidentDate) : new Date()
+);
+
+const f8s = [new Date(), new Date()];
+if (bindFire.value.field8) {
+  const s = bindFire.value.field8.split("至");
+  console.log(s);
+  if (s.length > 1) {
+    f8s[0] = new Date(s[0]);
+    f8s[1] = new Date(s[1]);
+  } else {
+    f8s[0] = new Date(s[0]);
+  }
+}
+
+const f8 = ref(f8s);
+const searchAMapAddress = async () => {
+  const data = await selectMapImage({});
+  if (!data?.search) return;
+  bindFire.value.projectAddress = data.search.text;
+  bindFire.value.latlng = bindFire.value.latAndLong = `${data.search.lat},${data.search.lng}`;
+};
+
+defineExpose<QuiskExpose>({
+  async submit() {
+    if (!bindFire.value.projectAddress || !bindFire.value.projectAddress.trim()) {
+      ElMessage.error("详细地址不能为空!");
+      throw "详细地址不能为空!";
+    } else if (!bindFire.value.projectSn || !bindFire.value.projectSn.trim()) {
+      ElMessage.error("项目编号不能为空!");
+      throw "项目编号不能为空!";
+    } else if (!bindFire.value.projectName || !bindFire.value.projectName.trim()) {
+      ElMessage.error("起火对象不能为空!");
+      throw "起火对象不能为空!";
+    } else if (!bindFire.value.projectSite || !bindFire.value.projectSite.trim()) {
+      ElMessage.error("起火场所不能为空!");
+      throw "起火场所不能为空!";
+    } else if (!bindFire.value.deptId || !bindFire.value.deptId.trim()) {
+      ElMessage.error("勘验单位不能为空!");
+      throw "勘验单位不能为空!";
+    } else if (!bindFire.value.organizerUsers || !bindFire.value.organizerUsers.trim()) {
+      ElMessage.error("勘验人员不能为空!");
+      throw "勘验人员不能为空!";
+    } else if (!accidentDate) {
+      ElMessage.error("事故日期不能为空!");
+      throw "事故日期不能为空!";
+    } else if (!bindFire.value.fireReason || !bindFire.value.fireReason.trim()) {
+      ElMessage.error("火灾原因不能为空!");
+      throw "火灾原因不能为空!";
+    }
+
+    bindFire.value.accidentDate = dateFormat(accidentDate.value, "yyyy-MM-dd");
+    bindFire.value.field8 =
+      dateFormat(f8.value[0], "yyyy-MM-dd") +
+      "至" +
+      dateFormat(f8.value[1], "yyyy-MM-dd");
+    bindFire.value.projectSiteCode = getCode(place, bindFire.value.projectSite);
+    bindFire.value.id
+      ? await setFire(bindFire.value)
+      : await addFire(bindFire.value as any);
+  },
+});
+</script>
+
+<style scoped>
+.search-result {
+  position: absolute;
+  left: 0;
+  right: 0;
+  z-index: 1;
+  overflow: hidden;
+  top: 100%;
+
+  &.show {
+    max-height: 450px;
+    overflow-y: auto;
+  }
+}
+
+.def-map-info {
+  margin-top: 10px;
+  p {
+    font-size: 14px;
+    color: rgba(0, 0, 0, 0.85);
+    display: inline;
+    &:not(:last-child)::after {
+      content: ",";
+      margin-right: 6px;
+    }
+  }
+
+  span::after {
+    content: ":";
+  }
+}
+
+.def-select-map {
+  position: absolute;
+  opacity: 0;
+  z-index: -1;
+  width: 540px;
+  height: 390px;
+  z-index: 1;
+  left: -100vw;
+  top: -100vh;
+  display: none;
+}
+
+.asdasd {
+  position: relative;
+  margin-bottom: 15px;
+  z-index: 2;
+}
+</style>

+ 43 - 0
src/app/fire/view/dispatch/editLeaveMsg.vue

@@ -0,0 +1,43 @@
+<template>
+  <el-form ref="form" label-width="84px" class="leave-from">
+    <el-input
+      type="textarea"
+      placeholder="请输入留言内容,限200字"
+      maxlength="200"
+      show-word-limit
+      v-model="content"
+    >
+    </el-input>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { ElMessage } from "element-plus";
+import { ref } from "vue";
+import { QuiskExpose } from "@/helper/mount";
+import { addFireLeaveMsg } from "@/app/fire/store/fire";
+
+const props = defineProps<{
+  projectId: string;
+}>();
+const content = ref("");
+
+defineExpose<QuiskExpose>({
+  async submit() {
+    if (!content.value.trim()) {
+      ElMessage.error("留言内容不能为空!");
+      throw "留言内容不能为空!";
+    }
+    await addFireLeaveMsg({
+      content: content.value,
+      projectId: props.projectId,
+    });
+  },
+});
+</script>
+
+<style>
+.leave-from textarea {
+  height: 200px;
+}
+</style>

+ 93 - 0
src/app/fire/view/dispatch/header.vue

@@ -0,0 +1,93 @@
+<template>
+  <com-head :options="head" showCtrl>
+    <el-form label-width="84px">
+      <el-form-item label="项目编号:">
+        <el-input v-model="pagging.state.query.projectSn" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="起火对象:">
+        <el-input
+          v-model="pagging.state.query.projectName"
+          placeholder="请输入"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="详细地址:">
+        <el-input
+          v-model="pagging.state.query.projectAddress"
+          placeholder="请输入"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="起火场所:">
+        <el-cascader
+          style="width: 100%"
+          v-model="projectSite"
+          placeholder="请选择"
+          :options="[{ label: '全部', value: UN_REQ_NUM.toString() }].concat(place)"
+          :props="{ expandTrigger: 'hover', checkStrictly: true }"
+        ></el-cascader>
+      </el-form-item>
+
+      <el-form-item label="勘验单位:">
+        <com-company v-model="pagging.state.query.deptId" />
+      </el-form-item>
+      <el-form-item label="事故日期:">
+        <el-date-picker
+          type="date"
+          v-model="pagging.state.query.accidentDate"
+          placeholder="请选择"
+          style="width: 100%"
+        />
+      </el-form-item>
+      <!-- <el-form-item label="火灾原因:">
+        <el-cascader
+          style="width: 100%"
+          v-model="fireReason"
+          placeholder="请选择"
+          :options="[{ label: '全部', value: UN_REQ_NUM.toString() }].concat(reason)"
+          :props="{ expandTrigger: 'hover', checkStrictly: true }"
+        ></el-cascader>
+      </el-form-item> -->
+      <el-form-item label="项目状态:">
+        <el-select placeholder="请选择" v-model="pagging.state.query.status" showAll>
+          <el-option
+            v-for="option in fireStatuOptions"
+            :key="option.value"
+            :value="option.value"
+            :label="option.label"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item class="searh-btns" style="grid-area: 1/4/4/5">
+        <el-button type="primary" @click="pagging.refresh">查询</el-button>
+        <el-button type="primary" plain @click="pagging.queryReset">重置</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+</template>
+
+<script lang="ts" setup>
+import { FirePagging } from "./pagging";
+import comCompany from "@/components/company-select/index.vue";
+import { genCascaderValue, getCode, getRaw, getValue } from "@/helper/cascader";
+import { reason, place, fireStatuOptions } from "@/app/fire/constant/fire";
+import comHead from "@/components/head/index.vue";
+import { computed, ref, watchEffect } from "vue";
+import { UN_REQ_NUM } from "@/constant/sys";
+
+const props = defineProps<{ pagging: FirePagging; isTeached: boolean }>();
+const head = computed(() => [
+  { name: props.isTeached ? "教学平台" : "火调管理", value: "2" },
+]);
+
+const projectSite = genCascaderValue(
+  computed(() => props.pagging.state.query),
+  "projectSite",
+  UN_REQ_NUM.toString()
+);
+watchEffect(() => {
+  props.pagging.state.query.projectSiteCode = getCode(place, getRaw(projectSite.value!));
+});
+const fireReason = genCascaderValue(
+  computed(() => props.pagging.state.query),
+  "fireReason"
+);
+</script>

+ 174 - 0
src/app/fire/view/dispatch/index.vue

@@ -0,0 +1,174 @@
+<template>
+  <Header :pagging="pagging" :isTeached="isTeached" />
+  <List
+    :pagging="pagging"
+    :checkPerm="isTeached ? 'cancel' : 'teach'"
+    :isRecycle="isRecycle"
+  >
+    <template v-slot:tableCtrl>
+      <template v-if="!isRecycle">
+        <template v-if="isTeached">
+          <el-button
+            type="primary"
+            @click="revokeTeaching()"
+            v-pdpath="'cancel'"
+            :class="{ disable: !pagging.state.table.selectRows.length }"
+          >
+            撤销教学
+          </el-button>
+        </template>
+        <template v-else>
+          <el-button type="primary" @click="addHandler" v-pdpath="'add'">
+            新增火调项目
+          </el-button>
+          <el-button
+            type="primary"
+            @click="setTeaching"
+            v-pdpath="'teach'"
+            :class="{ disable: !pagging.state.table.selectRows.length }"
+          >
+            设为教学项目
+          </el-button>
+        </template>
+      </template>
+    </template>
+
+    <template v-slot:appendColumn v-if="!isTeached">
+      <el-table-column label="教学项目" v-slot:default="{ row }: { row: Fire }">
+        {{ row.isTeached ? "是" : "否" }}
+      </el-table-column>
+    </template>
+
+    <template v-slot:rowCtrl="{ row }: { row: Fire }">
+      <template v-if="!isRecycle">
+        <template v-if="isTeached">
+          <span class="oper-span" @click="gotoQuery(row.caseId)" v-pdpath="['view']">
+            查看
+          </span>
+          <span
+            class="oper-span"
+            @click="showMessageHandler(row)"
+            v-pdpath="['message:look']"
+            >留言
+          </span>
+        </template>
+        <template v-else>
+          <CaseEditMenu
+            :title="row.projectSn"
+            :prev-menu="[
+              {
+                key: 'info',
+                label: '火调信息',
+                onClick: () => editHandler(row),
+              },
+            ]"
+            :caseId="row.caseId"
+            v-if="row.caseId"
+          />
+          <span class="oper-span" @click="gotoQuery(row.caseId)" v-pdpath="['view']">
+            查看
+          </span>
+          <span class="oper-span" @click="copy(row.caseId)" v-pdpath="['view']">
+            复制
+          </span>
+          <span
+            class="oper-span"
+            @click="pagging.del(row)"
+            style="color: var(--primaryColor)"
+            v-pdpath="['del']"
+          >
+            删除
+          </span>
+        </template>
+      </template>
+      <template v-else>
+        <span
+          class="oper-span"
+          @click="async () => {
+            await setFire({...row, isDelete: 0} as any)
+            pagging.refresh();
+          }"
+          v-pdpath="['retruen']"
+          style="color: var(--primaryColor)"
+        >
+          恢复
+        </span>
+        <span
+          class="oper-span"
+          @click="pagging.del(row)"
+          v-pdpath="['del']"
+          style="color: var(--primaryColor)"
+        >
+          删除
+        </span>
+      </template>
+    </template>
+  </List>
+</template>
+
+<script setup lang="ts">
+import Header from "./header.vue";
+import List from "./list.vue";
+import { useFirePagging } from "./pagging";
+import { Fire, revokeFireTeachs, setFireTeachs, setFire } from "@/app/fire/store/fire";
+import { copyCase } from "@/store/case";
+import CaseEditMenu from "@/view/case/editMenu.vue";
+import { gotoQuery } from "@/view/case/help";
+import { confirm } from "@/helper/message";
+import { addFire, editFire, showLeaveMsgList, addLeaveMsg } from "./quisk";
+import { shareCase } from "@/view/case/quisk";
+import { ElMessage } from "element-plus";
+
+const { pagging, isTeached, isRecycle } = useFirePagging();
+
+const copy = async (caseId: number) => {
+  await copyCase(caseId);
+  pagging.refresh();
+};
+
+// 撤销教学
+const revokeTeaching = async (row?: Fire) => {
+  if (!(await confirm("撤销教学,火调项目将不再显示在教学平台。(火调项目不会删除)"))) {
+    return;
+  }
+  const items = row ? [row] : pagging.state.table.selectRows;
+  await revokeFireTeachs(items.map(({ id }) => id));
+  pagging.refresh();
+  ElMessage.success(
+    items.map(({ projectSn }) => projectSn).join(",") + "已成功从教学项目撤销"
+  );
+};
+
+const showMessageHandler = (row: Fire) => {
+  showLeaveMsgList({
+    projectId: row.id,
+    onAddLeaveMsg: () => addLeaveMsg({ projectId: row.id }),
+  });
+};
+
+const setTeaching = async () => {
+  let items = pagging.state.table.selectRows || [];
+  if (!items.length) {
+    return ElMessage.error("请先选择操作项");
+  }
+  if (
+    !(await confirm(
+      "将火调项目设为教学项目后,总队及以下全部队伍均可查看。设置后可在教学平台取消设置。"
+    ))
+  ) {
+    return;
+  }
+  await setFireTeachs(items.map(({ id }) => id));
+  pagging.refresh();
+  ElMessage.success(
+    "已成功设置" + items.length + "个火调项目到教学平台,已设置的项目不重复设置。"
+  );
+};
+
+const editHandler = async (row: Fire) => {
+  (await editFire({ fire: row })) && pagging.refresh();
+};
+const addHandler = async () => {
+  (await addFire({})) && pagging.refresh();
+};
+</script>

+ 110 - 0
src/app/fire/view/dispatch/leaveMsgList.vue

@@ -0,0 +1,110 @@
+<template>
+  <!-- power="message:add" -->
+  <div class="body-layer" style="padding: 0">
+    <el-table
+      ref="multipleTable"
+      :data="state.table.rows"
+      tooltip-effect="dark"
+      style="width: 100%; max-height: 540px"
+    >
+      <el-table-column label="留言内容" prop="content" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.content"
+          placement="bottom-start"
+          v-if="row.content && row.content.length > 30"
+        >
+          <a class="msg-content"> {{ row.content.substr(0, 30) }}... </a>
+        </el-tooltip>
+        <a class="msg-content" v-else>
+          {{ row.content }}
+        </a>
+      </el-table-column>
+      <el-table-column label="创建人" prop="userName" width="150"></el-table-column>
+      <el-table-column
+        label="创建日期"
+        prop="createTime"
+        v-slot:default="{ row }"
+        width="180"
+      >
+        {{ format(row.createTime) }}
+      </el-table-column>
+    </el-table>
+
+    <com-pagination
+      @size-change="changPageSize"
+      @current-change="changPageCurrent"
+      :current-page="state.pag.currentPage"
+      :page-size="state.pag.size"
+      :total="state.pag.total"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { usePagging } from "@/hook/pagging";
+import comPagination from "@/components/pagination/index.vue";
+import { FireLeaveMsg, getFireLeaveMsgPagging } from "@/app/fire/store/fire";
+import { dateFormat } from "@/util";
+import { QuiskExpose } from "@/helper/mount";
+
+const props = defineProps<{
+  projectId: string;
+  onAddLeaveMsg: (projectId: string) => boolean | Promise<boolean>;
+}>();
+
+const { state, refresh, changPageSize, changPageCurrent } = usePagging({
+  get: getFireLeaveMsgPagging,
+  paramsTemlate: { projectId: props.projectId },
+});
+
+const format = (date: number) => dateFormat(new Date(date), "yyyy-MM-dd hh:mm");
+
+defineExpose<QuiskExpose>({
+  async submit() {
+    if (await props.onAddLeaveMsg(props.projectId)) {
+      refresh();
+    }
+    throw "不退出";
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.table-ctrl-right {
+  .search-scene {
+    margin: 0 20px 0 26px;
+  }
+  i {
+    margin-left: 20px;
+    font-size: 1.7rem;
+    vertical-align: middle;
+    cursor: pointer;
+
+    &.active {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.leave-from {
+  // width: 520px;
+  margin: 0 auto;
+}
+
+.title {
+  font-weight: bold;
+  color: #26559b;
+  line-height: 19px;
+  font-size: 14px;
+}
+
+.msg-content {
+  overflow: hidden; //超出的文本隐藏
+  text-overflow: ellipsis; //溢出用省略号显示
+  white-space: nowrap; //溢出不换行
+  position: relative;
+  cursor: pointer;
+}
+</style>

+ 194 - 0
src/app/fire/view/dispatch/list.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="body-layer">
+    <div class="body-head">
+      <h3 style="visibility: hidden">项目列表</h3>
+      <div class="table-ctrl-right">
+        <slot name="tableCtrl" />
+      </div>
+    </div>
+
+    <el-table
+      :data="pagging.state.table.rows"
+      id="multipleTable"
+      style="width: 100%"
+      class="table"
+      size="large"
+      @selection-change="pagging.changeSelectRows"
+    >
+      <el-table-column
+        v-if="!isRecycle"
+        type="selection"
+        width="50"
+        :selectable="() => !!operateIsPermissionByPath(checkPerm)"
+      />
+      <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="projectSn"></el-table-column>
+      <el-table-column label="事件分类" prop="field5" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.field5"
+          placement="bottom-start"
+          v-if="row.field5 && row.field5.length > 10"
+        >
+          <p class="tip oper-user">{{ row.field5.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.field5 }}</p>
+      </el-table-column>
+      <el-table-column label="起火对象" prop="projectName" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.projectName"
+          placement="bottom-start"
+          v-if="row.projectName && row.projectName.length > 10"
+        >
+          <p class="tip oper-user">{{ row.projectName.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.projectName }}</p>
+      </el-table-column>
+      <el-table-column label="分类登记" prop="field6" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.field6"
+          placement="bottom-start"
+          v-if="row.field6 && row.field6.length > 10"
+        >
+          <p class="tip oper-user">{{ row.field6.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.field6 }}</p>
+      </el-table-column>
+      <el-table-column label="详细地址" prop="projectAddress" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.projectAddress"
+          placement="bottom-start"
+          v-if="row.projectAddress && row.projectAddress.length > 10"
+        >
+          <p class="tip oper-user">{{ row.projectAddress.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.projectAddress }}</p>
+      </el-table-column>
+      <el-table-column label="起火场所" prop="projectSite" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.projectSite"
+          placement="bottom-start"
+          v-if="row.projectSite && row.projectSite.length > 10"
+        >
+          <p class="tip oper-user">{{ row.projectSite.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.projectSite }}</p>
+      </el-table-column>
+      <el-table-column label="天气情况" prop="field7" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.field7"
+          placement="bottom-start"
+          v-if="row.field7 && row.field7.length > 10"
+        >
+          <p class="tip oper-user">{{ row.field7.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.field7 }}</p>
+      </el-table-column>
+
+      <el-table-column label="勘验地址" prop="field1" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.field1"
+          placement="bottom-start"
+          v-if="row.field1 && row.field1.length > 10"
+        >
+          <p class="tip oper-user">{{ row.field1.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.field1 }}</p>
+      </el-table-column>
+      <el-table-column label="全宗名称" prop="caseNewName" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.caseNewName"
+          placement="bottom-start"
+          v-if="row.caseNewName && row.caseNewName.length > 10"
+        >
+          <p class="tip oper-user">{{ row.caseNewName.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.caseNewName }}</p>
+      </el-table-column>
+      <el-table-column label="勘验人员" prop="organizerUsers" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.organizerUsers"
+          placement="bottom-start"
+          v-if="row.organizerUsers && row.organizerUsers.length > 10"
+        >
+          <p class="tip oper-user">{{ row.organizerUsers.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.organizerUsers }}</p>
+      </el-table-column>
+      <el-table-column label="勘验人勘验人职务" prop="field4" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.field4"
+          placement="bottom-start"
+          v-if="row.field4 && row.field4.length > 10"
+        >
+          <p class="tip oper-user">{{ row.field4.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.field4 }}</p>
+      </el-table-column>
+
+      <el-table-column label="勘验单位" prop="organizerDeptName"></el-table-column>
+      <el-table-column label="事故日期" prop="accidentDate"></el-table-column>
+      <el-table-column label="勘验起止时间" prop="field8"></el-table-column>
+      <el-table-column label="火灾原因" prop="fireReason" v-slot:default="{ row }">
+        <el-tooltip
+          class="item"
+          effect="dark"
+          :content="row.fireReason"
+          placement="bottom-start"
+          v-if="row.fireReason && row.fireReason.length > 10"
+        >
+          <p class="tip oper-user">{{ row.fireReason.substring(0, 10) }}...</p>
+        </el-tooltip>
+        <p class="tip" v-else>{{ row.fireReason }}</p>
+      </el-table-column>
+      <el-table-column label="项目状态" v-slot:default="{ row }">
+        {{ fireStatusDesc[row.status as FireStatus] }}
+      </el-table-column>
+      <slot name="appendColumn" />
+      <el-table-column label="操作" v-slot:default="{ row }" :width="220" fixed="right">
+        <slot name="rowCtrl" :row="row" />
+      </el-table-column>
+    </el-table>
+
+    <com-pagination
+      @size-change="pagging.changPageSize"
+      @current-change="pagging.changPageCurrent"
+      :current-page="pagging.state.pag.currentPage"
+      :page-size="pagging.state.pag.size"
+      :total="pagging.state.pag.total"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import comPagination from "@/components/pagination/index.vue";
+import { fireStatusDesc } from "@/app/fire/constant/fire";
+import { FireStatus } from "@/app/fire/store/fire";
+import { operateIsPermissionByPath } from "@/directive/permission";
+import { FirePagging } from "./pagging";
+
+defineProps<{ pagging: FirePagging; checkPerm: string; isRecycle: boolean }>();
+</script>

+ 74 - 0
src/app/fire/view/dispatch/pagging.ts

@@ -0,0 +1,74 @@
+import { usePagging } from "@/hook/pagging";
+import {
+  FirePaggingRoute,
+  FireStatus,
+  delFire,
+  getFirePagging,
+} from "@/app/fire/store/fire";
+import { computed, watch, watchEffect } from "vue";
+import { router } from "@/router";
+import { FireRouteName } from "@/app/fire/routeConfig";
+import { UN_REQ_NUM } from "@/constant/sys";
+
+export const useFirePagging = () => {
+  const isTeached = computed(
+    () => router.currentRoute.value.name === FireRouteName.teaching
+  );
+  const isRecycle = computed(
+    () => router.currentRoute.value.name === FireRouteName.recycle
+  );
+
+  const pagging = usePagging({
+    get: getFirePagging,
+    del: async (raw) => {
+      await delFire(raw, isRecycle.value ? 1 : 2);
+    },
+    mapper: {
+      delMsg: () =>
+        isRecycle.value
+          ? "删除后将无法恢复,确定要删除吗?"
+          : "删除火调项目,相关档案也会一并删除,确定要删除吗?",
+    },
+    paramsTemlate: {
+      projectSn: "",
+      projectName: "",
+      projectAddress: "",
+      deptId: "",
+      isDelete: 0,
+      accidentDate: "",
+      status: FireStatus.all,
+      projectSiteCode: "",
+      fireReason: UN_REQ_NUM.toString(),
+      organizerUsers: "",
+    } as any,
+  });
+
+  watch(
+    () => [pagging.state.query.queryType, isTeached.value, isRecycle.value],
+    () => {
+      console.log(isRecycle.value);
+      if (isRecycle.value) {
+        pagging.state.query.queryType = FirePaggingRoute.fire;
+        pagging.state.query.isDelete = 2;
+      } else {
+        pagging.state.query.isDelete = 0;
+        pagging.state.query.queryType = isTeached.value
+          ? FirePaggingRoute.teached
+          : FirePaggingRoute.fire;
+      }
+    },
+    { flush: "post", immediate: true }
+  );
+
+  const params = [{ ...pagging.state.query }, { ...pagging.state.query }];
+  watchEffect(
+    () => {
+      pagging.state.query = params[isTeached.value ? 0 : 1];
+      // pagging.state.query = params[isRecycle.value ? 0 : 1];
+    },
+    { flush: "sync" }
+  );
+
+  return { pagging, isTeached, isRecycle };
+};
+export type FirePagging = ReturnType<typeof useFirePagging>["pagging"];

+ 23 - 0
src/app/fire/view/dispatch/quisk.ts

@@ -0,0 +1,23 @@
+import EditFire from "./editFire.vue";
+import EditLeaveMsg from "./editLeaveMsg.vue";
+import LeaveMsgList from "./leaveMsgList.vue";
+import { quiskMountFactory } from "@/helper/mount";
+
+export const addFire = quiskMountFactory(EditFire, {
+  title: "新增火调项目",
+});
+
+export const editFire = quiskMountFactory(EditFire, {
+  title: "编辑火调项目",
+});
+
+export const addLeaveMsg = quiskMountFactory(EditLeaveMsg, {
+  title: "新增留言",
+  width: 540,
+});
+
+export const showLeaveMsgList = quiskMountFactory(LeaveMsgList, {
+  title: "留言",
+  enterText: "发表留言",
+  power: "message:add",
+});

+ 51 - 0
src/app/index.ts

@@ -0,0 +1,51 @@
+import { Routes } from "@/router";
+
+import { appConstant as criminalConstant } from "./criminal/constant";
+import {
+  routes as cRoutes,
+  menuRouteNames as cMenuRouteNames,
+} from "./criminal/routeConfig";
+
+import { appConstant as fireConstant } from "./fire/constant";
+import {
+  routes as fireRoutes,
+  menuRouteNames as firelMenuRouteNames,
+} from "./fire/routeConfig";
+
+import { appConstant as xmfireConstant } from "./xmfire/constant";
+import {
+  routes as xmfireRoutes,
+  menuRouteNames as xmfirelMenuRouteNames,
+} from "./xmfire/routeConfig";
+
+export type AppConstant = {
+  title: string;
+  ico: string;
+  desc: string;
+  banner: string;
+  deptId: number;
+  name: string;
+  loginComponent?: any;
+};
+
+export let appConstant: AppConstant;
+export let appRoutes: Routes;
+export let menuRouteNames: string[];
+
+const app = import.meta.env.VITE_APP_APP;
+console.log('app', app)
+
+// 为了打包去除不需要的app只能固定写法
+if (app === "fire") {
+  appRoutes = fireRoutes;
+  menuRouteNames = firelMenuRouteNames;
+  appConstant = fireConstant;
+} else if (app === "criminal") {
+  appRoutes = cRoutes;
+  menuRouteNames = cMenuRouteNames;
+  appConstant = criminalConstant;
+} else if (app === "xmfire") {
+  appRoutes = xmfireRoutes;
+  menuRouteNames = xmfirelMenuRouteNames;
+  appConstant = xmfireConstant;
+}

+ 333 - 0
src/app/map/App.vue

@@ -0,0 +1,333 @@
+<template>
+  <!-- <div>地图页面</div> -->
+  <div class="tabbar">
+    <div class="nav">
+      <el-button-group class="ml-4">
+        <el-button
+          :type="currentType(0) ? 'primary' : 'default'"
+          @click="handleSelect(0)"
+          >地图</el-button
+        >
+        <el-button
+          :type="currentType(1) ? 'primary' : 'default'"
+          @click="handleSelect(1)"
+          >卡片</el-button
+        >
+      </el-button-group>
+    </div>
+    <el-form-item label="所属架构:" class="filter">
+      <com-company v-model="state.deptId" :id="state.caseId" hideAll />
+    </el-form-item>
+  </div>
+  <div ref="mapEl" class="map-container" v-show="currentType(0)"></div>
+  <div class="card-container" v-show="currentType(1)">
+    <div class="card-list">
+      <template v-for="item of list" v-if="list.length > 0">
+        <el-card
+          style="width: 480px"
+          shadow="hover"
+          @click="handCardClick(item.caseId)"
+        >
+          <img
+            class="cover"
+            :src="
+              item.cover
+                ? item.cover
+                : 'https://test-mix3d.4dkankan.com/code/assets/pic.d5781b0c.jpg'
+            "
+            style="width: 100%"
+          />
+          <div class="card">
+            <span> 项目: {{ item.projectName }}</span>
+            <span> 地址: {{ item.projectAddress }}</span>
+            <span> 类别: {{ item.projectSite }}</span>
+          </div>
+        </el-card>
+      </template>
+      <template v-else>
+        <div class="no-data">
+          <img :src="emptyBG" />
+          <span>暂无数据</span>
+        </div>
+      </template>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref, computed, onBeforeMount } from "vue";
+// import { useRouteQuery } from "@vueuse/router";
+import AMapLoader from "@amap/amap-jsapi-loader";
+import axios from "axios";
+import { getFuseCodeLink } from "../../view/case/help";
+import comCompany from "./company-select/index.vue";
+import { reactive } from "vue";
+import { watch } from "vue";
+import { ElLoading } from "element-plus";
+import linkIco from "@/assets/image/fire.ico";
+import { useUrlSearchParams } from "@vueuse/core";
+import emptyBG from "@/assets/image/empty__empty.png";
+
+const params = useUrlSearchParams("history");
+console.log("params", params.deptId);
+
+const current = ref(0);
+const list = ref<any>([]);
+
+const state = reactive({
+  deptId: params.deptId || "",
+  // caseId: params.caseId || "",
+});
+
+const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
+link.setAttribute("href", linkIco);
+
+document.title = "案件显示";
+
+const currentType = computed(() => (type: number) => current.value === type);
+const handleSelect = (type: number) => {
+  current.value = type;
+};
+
+const markers = ref<any>([]);
+
+const getQuery = (
+  caseId: number,
+  share: boolean = false,
+  single: boolean = false
+) =>
+  `${getFuseCodeLink(caseId, true)}${share ? "&share=1" : ""}${
+    single ? "&single=1" : ""
+  }#show/summary`;
+
+const request = axios.create({
+  baseURL: "",
+  timeout: 1000,
+  headers: {
+    share: 1,
+    "Content-Type": "application/json",
+  },
+});
+const mapEl = ref<HTMLDivElement>();
+let AMap, map;
+
+const queryURL = `${import.meta.env.VITE_SEVER_URL}/fusion-xj/web/fireProject/queryProject`
+
+// debugger;
+const getDataQuest = () => {
+  return new Promise(async (reslove, reject) => {
+    const res = await request.post(
+      queryURL,
+      {
+        pageNum: 1,
+        pageSize: 10000,
+        deptId: state.deptId,
+      }
+    );
+    console.log("res.data", res);
+    if (res.status === 200 && res.data.code === 0) {
+      reslove(res.data.data.list);
+    } else {
+      reslove([]);
+    }
+  });
+};
+
+const refresh = async () => {
+  const loading = ElLoading.service({
+    lock: true,
+    text: "Loading",
+    background: "rgba(0, 0, 0, 0.7)",
+  });
+  map.remove(markers.value);
+  markers.value = [];
+  initMakers();
+  loading.close();
+};
+
+watch(
+  () => state.deptId,
+  () => {
+    refresh();
+  },
+  {
+    immediate: false,
+    deep: true,
+  }
+);
+const initMakers = async () => {
+  const data = (await getDataQuest()) as any as any[];
+  console.log("data", data);
+  const positions: any[] = [];
+  list.value = data as any[];
+  Array.from(data).forEach((item: any) => {
+    // console.log(item)
+    const latlng = item.latlng;
+    const coord = latlng.split(",");
+
+    console.log("coord", coord, item.caseId);
+    const url = getQuery(item.caseId, true);
+    console.log("url", url);
+    const icon = new AMap.Icon({
+      image:
+        "//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-default.png",
+      size: new AMap.Size(22, 28), //图标所处区域大小
+      imageSize: new AMap.Size(22, 28), //图标大小
+    });
+
+    const pos = coord.reverse();
+    positions.push(pos);
+    const marker = new AMap.Marker({
+      icon: icon,
+      position: pos,
+      title: item.title,
+      label: item.title,
+      extData: { url: url, id: item.caseId },
+      // offset: new AMap.Pixel(-26, -54),
+    });
+    markers.value.push(marker);
+    marker.setMap(map);
+
+    marker.on("click", () => {
+      const data = marker.getExtData();
+      window.open(data.url);
+      console.log("click", data);
+    });
+  });
+
+  var polygon = new AMap.Polygon({
+    path: positions,
+    map: map,
+    strokeOpacity: 0, //透明
+    fillOpacity: 0, //透明
+    bubble: true, //事件穿透到地图
+  });
+  var overlaysList = map.getAllOverlays("polygon"); //获取多边形图层
+  map.setFitView(); //自适应显示
+};
+const loadMap = async () => {
+  AMap = await AMapLoader.load({
+    plugins: ["AMap.PlaceSearch"],
+    key: "e661b00bdf2c44cccf71ef6070ef41b8",
+    version: "2.0",
+  });
+
+  map = new AMap.Map(mapEl.value, {
+    WebGLParams: {
+      preserveDrawingBuffer: true,
+    },
+    resizeEnable: true,
+  });
+
+  //添加插件
+  AMap.plugin(
+    ["AMap.ToolBar", "AMap.Scale", "AMap.HawkEye", "AMap.MapType"],
+    function () {
+      //异步同时加载多个插件
+      // map.addControl(new AMap.HawkEye()); //显示缩略图
+      map.addControl(new AMap.Scale()); //显示当前地图中心的比例尺
+      map.addControl(new AMap.MapType()); //显示当前地图中心的比例尺
+    }
+  );
+  console.log("map", map);
+
+  initMakers();
+};
+
+onMounted(() => {
+  // console.log("caseId", caseId);
+  loadMap();
+});
+const handCardClick = (id: number) => {
+  const url = getQuery(id, true);
+  console.log("url", url);
+  window.open(url);
+};
+</script>
+<style>
+body {
+  padding: 0;
+  margin: 0;
+}
+
+.map-container {
+  width: 100%;
+  height: 100vh;
+}
+
+.tabbar {
+  display: flex;
+  width: 100%;
+  position: fixed;
+  top: 30px;
+  z-index: 10000;
+  justify-items: center;
+  justify-content: center;
+}
+
+.tabbar .nav {
+  display: flex;
+  /* background: white; */
+  justify-content: center;
+  align-items: center;
+}
+.el-form-item {
+  margin-bottom: 0px !important;
+}
+
+.tabbar .nav .nav_item {
+  padding: 10px;
+  cursor: pointer;
+}
+
+.card-container {
+  width: 100%;
+  padding-top: 100px;
+}
+
+.card-list {
+  margin: 0 auto;
+
+  display: flex;
+  gap: 50px 25px;
+  width: 90%;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: center;
+}
+
+.cover {
+  cursor: pointer;
+  max-height: 220px;
+  object-fit: cover;
+}
+
+.card {
+  display: flex;
+  flex-direction: column;
+  cursor: pointer;
+  gap: 10px;
+}
+
+.filter {
+  margin: 0;
+  margin-left: 20px;
+}
+
+.amap-ctrl-list-layer {
+  z-index: 100000;
+}
+.no-data {
+  width: 100%;
+  height: 100%;
+  /* background: red; */
+  min-height: 530px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+.no-data span {
+  color: #999;
+}
+</style>

+ 55 - 0
src/app/map/company-select/index.vue

@@ -0,0 +1,55 @@
+<template>
+  <el-cascader
+    style="width: 100%"
+    v-model="state.path"
+    :disabled="disabled"
+    placeholder="勘验单位:"
+    :options="state.options"
+    :props="{ expandTrigger: 'hover', checkStrictly: true }"
+  />
+</template>
+
+<script setup lang="ts">
+import { useTreeSelect, Props, TreeOption } from "@/hook/treeSelect";
+import { getOrganizationTree } from "./organization";
+import { watchEffect } from "vue";
+
+const emit = defineEmits<{
+  (e: "update:data", data: TreeOption | null): void;
+  (e: "update:modelValue", value: string): void;
+  (e: "update:label", value: string): void;
+  (e: "update:path", path: string[]): void;
+}>();
+const props = defineProps<Props>();
+
+const state = useTreeSelect(
+  props,
+  getOrganizationTree,
+  (val) => emit("update:modelValue", val),
+  {
+    label: "name",
+    level: "level",
+  }
+);
+
+watchEffect(() => {
+  emit("update:data", state.currentOption);
+});
+watchEffect(() => {
+  emit("update:label", state.label);
+});
+watchEffect(() => {
+  emit("update:path", state.path);
+});
+</script>
+
+<style scoped>
+.aaa::after {
+  content: "";
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+}
+</style>

+ 68 - 0
src/app/map/company-select/organization.ts

@@ -0,0 +1,68 @@
+import {
+  addTreeitem,
+  axios,
+  delTreeitem,
+  editTreeitem,
+  getTreeselect,
+} from "@/request";
+import { useUrlSearchParams } from "@vueuse/core";
+const params = useUrlSearchParams("history");
+console.log("params", params.deptId);
+
+
+const request = axios.create({
+  baseURL: `${import.meta.env.VITE_SEVER_URL}/`,
+  timeout: 1000,
+  headers: {
+    share: 1,
+    'Content-Type': 'application/json'
+  },
+});
+
+export enum DeptType {
+  corps = 1,
+  detachment = 2,
+  brigade = 3,
+}
+
+export type Organization = {
+  ancestors: string;
+  children?: Organization[];
+  deptType: DeptType;
+  id: string;
+  leader: string;
+  level: number;
+  name: string;
+  parentId: string;
+  parentName: string;
+  phone: string;
+  remark: string;
+};
+
+export const getOrganizationTree = async (type?: string) =>
+  (await axios.get<Organization[]>(getTreeselect, { headers: { share: 1 }, params: { type, deptId: params.deptId, ingoreRes: true, share: 1, } })).data;
+
+export const addOrganization = async (
+  dept: Omit<Organization, "id">,
+  deptIds: string[]
+) => {
+  await axios.post(addTreeitem, {
+    superior: "sheq",
+    ...dept,
+    deptIdList: deptIds.join(","),
+  });
+};
+
+export const setOrganization = async (
+  dept: Organization,
+  deptIds: string[]
+) => {
+  await axios.post(editTreeitem, {
+    superior: "sheq",
+    ...dept,
+    deptIdList: deptIds.join(","),
+  });
+};
+
+export const delOrganization = async (deptId: string) =>
+  axios.post(delTreeitem + deptId);

+ 14 - 0
src/app/map/main.ts

@@ -0,0 +1,14 @@
+import { createApp } from "vue";
+import "element-plus/dist/index.css";
+import * as ElementPlusIconsVue from "@element-plus/icons-vue";
+import App from "./App.vue";
+import ElementPlus from "element-plus";
+
+const app = createApp(App);
+
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component);
+}
+
+app.use(ElementPlus);
+app.mount("#app");

+ 589 - 0
src/app/mirror/App.vue

@@ -0,0 +1,589 @@
+<template>
+  <div class="mirror-setting" v-if="!isNotFound">
+    <!-- 图片预览 -->
+    <el-dialog v-model="dialogVisible">
+      <img
+        v-if="checkSourceIsImage(dialogImageUrl) && dialogVisible"
+        style="width: 100%; height: 500px; object-fit: scale-down"
+        w-full
+        :src="dialogImageUrl"
+        alt="Preview Image"
+      />
+
+      <video
+        v-if="checkSourceIsVideo(dialogImageUrl) && dialogVisible"
+        style="width: 100%"
+        w-full
+        controls
+        :src="dialogImageUrl"
+      />
+
+      <audio
+        v-if="checkSourceIsAudio(dialogImageUrl) && dialogVisible"
+        style="width: 100%"
+        w-full
+        controls
+        :src="dialogImageUrl"
+      />
+    </el-dialog>
+    <!-- 分镜配置 -->
+    <div class="project-title">
+      <el-input
+        class="title"
+        type="textarea"
+        maxlength="100"
+        :autosize="{ minRows: 1, maxRows: 4 }"
+        v-model="project.title"
+      />
+      <el-button type="primary" @click="saveProject">保存</el-button>
+    </div>
+    <div class="content">
+      <el-table
+        :key="data.list.length"
+        class="main-table"
+        key="id"
+        border
+        v-dragable="dragOptions"
+        :data="data.list"
+        header-row-class-name="t-head"
+        header-cell-class-name="t-cell"
+      >
+        <!-- <template v-for="item in columns" :key="item.prop">
+          <el-table-column  :prop="item.prop" :label="item.label" />
+          大纲
+        </template> 
+        :on-preview="handlePictureCardPreview" :on-remove="handleRemove"
+        -->
+        <el-table-column prop="name" label="大纲">
+          <template v-slot="{ row }">
+            <el-input
+              type="textarea"
+              :autosize="{ minRows: 3 }"
+              v-model="row.name"
+              :row="3"
+              maxlength="1000"
+              placeholder="概括拍摄内容"
+            />
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="desc" label="分镜描述">
+          <template v-slot="{ row }">
+            <el-input
+              class="gray"
+              type="textarea"
+              :autosize="{ minRows: 3 }"
+              maxlength="1000"
+              v-model="row.desc"
+              :row="3"
+              placeholder="详细描述分镜"
+            />
+          </template>
+        </el-table-column>
+        <!-- show-overflow-tooltip -->
+        <el-table-column prop="clip" label="已拍摄片段">
+          <template v-slot="{ row }">
+            <el-upload
+              ref="upload"
+              v-model:file-list="row.fileList"
+              class="list-upload-style"
+              :accept="DrawFormats.toString()"
+              :class="{
+                activefileList: row.fileList && row.fileList.length == 1,
+              }"
+              :before-upload="beforeUpload"
+              list-type="picture-card"
+              :action="uploadFileUrl"
+              :on-success="handleUploadSuccess"
+              :limit="1"
+            >
+              <div
+                class="uploadImg"
+                v-if="row.fileList && row.fileList.length == 0"
+              >
+                <el-icon><Plus /></el-icon>
+              </div>
+              <template #file="{ file }">
+                <div style="width: 100%">
+                  <img
+                    v-if="file.cover"
+                    class="el-upload-list__item-thumbnail"
+                    :src="getCoverUrl(file.cover)"
+                    alt=""
+                  />
+                  <span class="el-upload-list__item-actions">
+                    <span
+                      class="el-upload-list__item-preview"
+                      @click="handlePictureCardPreview(file)"
+                    >
+                      <el-icon><zoom-in /></el-icon>
+                    </span>
+                    <span
+                      class="el-upload-list__item-delete"
+                      @click="handleRemove(row)"
+                    >
+                      <el-icon><Delete /></el-icon>
+                    </span>
+                  </span>
+                </div>
+              </template>
+            </el-upload>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="words" label="台词文案">
+          <template v-slot="{ row }">
+            <el-input
+              class="gray"
+              maxlength="1000"
+              type="textarea"
+              :autosize="{ minRows: 3 }"
+              v-model="row.words"
+              placeholder="点击输入台词"
+            />
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="marks" label="备注">
+          <template v-slot="{ row, $index }">
+            <div class="marksDiv">
+              <el-input
+                class="gray"
+                maxlength="1000"
+                type="textarea"
+                :autosize="{ minRows: 3 }"
+                v-model="row.marks"
+                placeholder="点击输入内容"
+              />
+              <span
+                class="table-delete"
+                @click="handleTableRemove($index, row)"
+              >
+                <el-icon><Delete /></el-icon>
+              </span>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+    <div class="add-handle">
+      <el-button type="primary" @click="handleAdd">
+        <el-icon class="el-icon--right">
+          <Plus />
+        </el-icon>
+        添加
+        <el-input class="add-line" type="text" v-model="addLine" size="small">
+        </el-input>
+        行
+      </el-button>
+    </div>
+  </div>
+  <noCase :show-btn="false" v-else></noCase>
+</template>
+
+<script lang="ts" setup>
+import { vDragable } from "./dragable";
+import { ElMessage } from "element-plus";
+import { reactive, ref, onMounted, computed } from "vue";
+import type { UploadFile } from "element-plus";
+import { uploadFile as uploadFileUrl } from "@/request";
+import {
+  getCaseScriptInfo,
+  CaseScriptSaveOrUpdate,
+  CaseScriptGetCover,
+} from "@/app/mirror/store/script";
+import linkIco from "@/assets/image/fire.ico";
+import musicHeadphones from "@/assets/image/music.png";
+import { getCaseInfo } from "@/store/case";
+import noCase from "@/view/case/no-case.vue";
+
+const link = document.querySelector<HTMLLinkElement>("#app-icon")!;
+link.setAttribute("href", linkIco);
+
+const caseId = ref(null);
+const project = reactive({
+  title: "我的脚本",
+});
+const DrawFormats = [
+  ".jpg",
+  ".jpeg",
+  ".png",
+  ".mp4",
+  ".m4v",
+  ".mp3",
+  ".aac",
+  ".wav",
+];
+const isNotFound = ref(false);
+
+const dialogImageUrl = ref("");
+const dialogVisible = ref(false);
+const disabled = ref(false);
+
+const addLine = ref(1);
+const active = ref(1);
+const dragOptions = [
+  // {
+  //   selector: "thead tr", // add drag support for column
+  //   option: {
+  //     // sortablejs's option
+  //     animation: 150,
+  //     onEnd: (evt) => {
+  //       let oldCol: any = {};
+  //       Object.assign(oldCol, columns.value[evt.oldIndex]);
+  //       columns.value.splice(evt.oldIndex, 1); // 因为新增了数据,所以要移除原来的列的index要在原来的基础上
+  //       setTimeout(() => {
+  //         columns.value.splice(evt.newIndex, 0, oldCol); // 把原来的列数据添加到新的位置,然后再从原位置移除它,触发table的重绘
+  //       }, 30);
+  //       console.log(evt.oldIndex, evt.newIndex);
+  //     },
+  //   },
+  // },
+  {
+    selector: "tbody", // add drag support for row
+    option: {
+      // sortablejs's option
+      animation: 150,
+      onEnd: (evt: any) => {
+        // let oldItem = sortList.value[evt.oldIndex];
+        // let sortLists = sortList.value.filter(
+        //   (_, index) => index !== evt.oldIndex
+        // );
+        // sortLists.splice(evt.newIndex, 0, oldItem);
+        // sortList.value = sortLists;
+        let list = JSON.parse(JSON.stringify(data.newSortList));
+        const target = list.splice(evt.oldIndex, 1);
+        list.splice(evt.newIndex, 0, target[0]);
+        data.newSortList = list;
+        console.log(evt.oldIndex, evt.newIndex, data.newSortList, data.list);
+      },
+    },
+  },
+];
+
+const columns = ref([
+  // { prop: "id", label: "ID", hidden: true, },
+  { prop: "name", label: "大纲" },
+  { prop: "desc", label: "分镜描述" },
+  { prop: "clip", label: "已拍摄片段" },
+  { prop: "words", label: "台词文案" },
+  { prop: "marks", label: "备注" },
+]);
+const beforeUpload = async (file: File) => {
+  const fileType = file.name
+    .substring(file.name.lastIndexOf("."))
+    .toUpperCase();
+  if (!DrawFormats.some((type) => type.toUpperCase() === fileType)) {
+    ElMessage.error(`请上传${DrawFormats}格式的文件`);
+    return false;
+  } else {
+    return true;
+  }
+};
+const checkSourceIsVideo = computed(() => (url: string) => {
+  return url.includes(".mp4") || url.includes(".m4v");
+});
+const checkSourceIsAudio = computed(() => (url: string) => {
+  return url.includes(".mp3") || url.includes(".aac") || url.includes(".wav");
+});
+const checkSourceIsImage = computed(() => (url: string) => {
+  return (
+    url.includes(".jpg") ||
+    url.includes(".png") ||
+    url.includes(".jpeg") ||
+    url.includes(".gif")
+  );
+});
+
+const getCoverUrl = computed(() => (url: string) => {
+  switch (true) {
+    // case url.includes(".mp4"):
+    //   return (
+    //     url + "?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto"
+    //   );
+    case url.includes(".mp3") ||
+      url.includes(".aac") ||
+      url.includes(".wmv") ||
+      url.includes(".wav"):
+      return musicHeadphones;
+    default:
+      return url;
+  }
+});
+
+const data = reactive({
+  list: [{ id: 1, name: "", desc: "", fileList: [] }],
+  newSortList: [],
+});
+const sortList = ref([0]);
+
+onMounted(async () => {
+  caseId.value = GetRequest("caseId");
+  try {
+    const caseInfo = await getCaseInfo(caseId.value!);
+    if (caseInfo && caseId.value) {
+      document.title = caseInfo.caseTitle + " | 分镜配置";
+    } else {
+      isNotFound.value = true;
+    }
+  } catch (error) {
+    //TODO hack 由于没hook
+    if (error === "用户未登录") {
+      location.href = getRootLoginURl();
+    } else {
+      isNotFound.value = true;
+    }
+  }
+
+  getCaseScriptList();
+  console.log("caseId", caseId); //query传参
+});
+
+function getRootLoginURl() {
+  const currentPath = location.pathname.replace("/mirror.html", "");
+  const loginUrl =
+    location.protocol + "//" + location.host + currentPath + "/#/login";
+  console.log("loginUrl", loginUrl);
+  return loginUrl;
+}
+
+function getCaseScriptList() {
+  getCaseScriptInfo(caseId.value)
+    .then((res) => {
+      project.title = (res && res.name) || "我的脚本";
+      data.list = (res && res.content) || [];
+      data.newSortList = (res && res.content) || [];
+      const idList = data.list.map((ele) => ele.id);
+      active.value = idList.length == 0 ? 0 : Math.max.apply(null, idList) || 1;
+      sortList.value = data.list.map((_, index) => index);
+      console.log("getCaseScriptList", idList, active.value);
+      // Array.from(data.list).forEach((item) => {
+      //   item.fileList.forEach(async (file: File, index) => {
+      //     console.log("getCaseScriptList", file);
+      //     if ((file as any).url.includes(".mp4") || (file as any).url.includes(".m4v")) {
+      //       const res = file.cover || await CaseScriptGetCover((file as any).url);
+      //       (item.fileList[index] as any).cover = res;
+      //     } else {
+      //       (item.fileList[index] as any).cover = (file as any).url;
+      //     }
+      //   });
+      // });
+    })
+    .catch((err) => {
+      console.log(err);
+    });
+}
+function handleAdd() {
+  // let content = sortList.value.map((index) => data.list[index]);
+  // data.list.length = 0;
+  // Object.assign(data.list, content);
+  console.log("add", data.list, data.newSortList);
+  if (data.newSortList.length == 0) {
+  }
+  for (var i = 1; i <= addLine.value; i++) {
+    console.log(i);
+    data.newSortList.push({
+      id: active.value + 1,
+      name: "",
+      desc: "",
+      words: "",
+      marks: "",
+      fileList: [],
+    });
+  }
+  active.value++;
+  data.list = data.newSortList;
+  sortList.value = data.list.map((_, index) => index);
+}
+const handleRemove = (data) => {
+  data.fileList = [];
+};
+const handleTableRemove = (index, datas) => {
+  data.newSortList = data.newSortList.filter((ele) => ele.id !== datas.id);
+  console.log("saveProject", data.newSortList);
+  data.list = data.newSortList;
+  // let content = sortList.value.map((index) => data.list[index]);
+  // data.list.length = 0;
+  // content.splice(index, 1);
+  // Object.assign(data.list, content);
+  // sortList.value = content.map((_, index) => index);
+  ElMessage.success("删除成功");
+  console.log("saveProject", index, datas, data.list);
+};
+const handlePictureCardPreview = (file: UploadFile) => {
+  dialogImageUrl.value = file.url!;
+  dialogVisible.value = true;
+};
+
+const saveProject = () => {
+  // let content = sortList.value.map((index) => data.list[index]);
+  let apiDataList = data.newSortList.map((item) => {
+    let asData = data.list.find((ele) => ele.id === item.id) || {};
+    return {
+      ...item,
+      ...asData,
+    };
+  });
+  console.log("saveProject", data.list, data.newSortList);
+  CaseScriptSaveOrUpdate({
+    caseId: caseId.value,
+    name: project.title,
+    content: apiDataList,
+  }).then((res) => {
+    console.log("saveProject");
+    ElMessage.success("保存成功");
+  });
+};
+async function handleUploadSuccess(response: any, uploadFile: UploadFile) {
+  uploadFile.url = response.data;
+  console.log("handleUploadSuccess", uploadFile.url);
+  if (uploadFile.url!.includes(".mp4") || uploadFile.url!.includes(".m4v")) {
+    const res = await CaseScriptGetCover(uploadFile.url!);
+    (uploadFile as any).cover = res;
+  } else {
+    (uploadFile as any).cover = uploadFile.url;
+  }
+}
+
+function GetRequest(value) {
+  var url = decodeURI(window.location.search); //?id="123456"&name="www";
+  var object = {};
+  if (url.indexOf("?") != -1) {
+    //url中存在问号,也就说有参数。
+    var str = url.substr(1); //得到?后面的字符串
+    var strs = str.split("&"); //将得到的参数分隔成数组[id="123456",name="www"];
+    for (var i = 0; i < strs.length; i++) {
+      object[strs[i].split("=")[0]] = strs[i].split("=")[1]; //得到{id:'123456',name:'www'}
+    }
+  }
+  return object[value];
+}
+</script>
+<style lang="scss" scoped></style>
+
+<style lang="scss">
+body,
+#app {
+  margin: 0;
+  padding: 0;
+}
+
+.mirror-setting {
+  width: 100%;
+  min-height: 100%;
+  padding-top: 80px;
+  min-height: calc(100vh - 80px);
+  margin: 0 auto;
+  background: #eee;
+
+  .content {
+    margin: 0 auto;
+    display: flex;
+    padding: 0 40px;
+  }
+
+  .t-head {
+    border: 1px solid #ddd;
+    /* padding: 10px; */
+    /* display: flex; */
+    position: relative;
+    background-color: #eee;
+  }
+
+  tbody {
+    /* border-top: 20px solid transparent; */
+  }
+
+  .t-head th {
+    margin-bottom: 20px;
+  }
+
+  .project-title {
+    display: flex;
+    padding: 0 40px;
+    align-items: center;
+    /* justify-content: center; */
+  }
+
+  .project-title .title {
+    font-size: 28px;
+    min-height: 0;
+    height: auto;
+    background-color: transparent !important;
+    /* width: 300px; */
+    margin: 30px 0;
+  }
+
+  .el-textarea__inner {
+    background-color: transparent;
+    box-shadow: none;
+    resize: none;
+  }
+
+  .gray .el-textarea__inner {
+    background: rgba(227, 225, 225, 0.2);
+  }
+
+  .add-handle {
+    padding: 30px 0;
+    display: flex;
+    justify-content: center;
+  }
+
+  .add-line {
+    margin: 0 10px;
+    width: 30px;
+  }
+
+  .add-line .el-input__wrapper {
+    box-shadow: none;
+    background: rgba(23, 41, 46, 0.2);
+  }
+
+  .add-line input {
+    color: white;
+    text-align: center;
+  }
+  .activefileList {
+    .el-upload-list--picture-card {
+    }
+    .el-upload--picture-card {
+      display: none;
+    }
+  }
+  .list-upload-style {
+    width: 100%;
+    text-align: center;
+    .el-upload-list,
+    .el-upload--text {
+      width: 100%;
+    }
+    .el-upload-list__item-thumbnail,
+    .el-upload--picture-card {
+      min-height: 73px;
+      height: 73px;
+      width: 100%;
+    }
+    .uploadImg,
+    .el-upload-list__item {
+      width: 100%;
+      min-height: 73px;
+      height: 73px;
+      line-height: 73px;
+      .el-upload-list__item-thumbnail {
+        width: 100%;
+        max-width: 100px;
+        object-fit: cover;
+      }
+    }
+  }
+  .marksDiv {
+    position: relative;
+    .table-delete {
+      position: absolute;
+      right: -10px;
+      top: -3px;
+    }
+  }
+}
+</style>

+ 11 - 0
src/app/mirror/dragable.ts

@@ -0,0 +1,11 @@
+import Sortable from "sortablejs";
+
+export const vDragable = {
+  mounted(el, binding) {
+    const options = binding.value;
+    for (let oi = 0; oi < options.length; oi++) {
+      const o = options[oi];
+      new Sortable(el.querySelector(o.selector), o.option);
+    }
+  },
+};

+ 14 - 0
src/app/mirror/main.ts

@@ -0,0 +1,14 @@
+import { createApp } from "vue";
+import "element-plus/dist/index.css";
+import * as ElementPlusIconsVue from "@element-plus/icons-vue";
+import App from "./App.vue";
+import ElementPlus from "element-plus";
+import "@/store/user";
+const app = createApp(App);
+
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component);
+}
+
+app.use(ElementPlus);
+app.mount("#app");

+ 80 - 0
src/app/mirror/store/script.ts

@@ -0,0 +1,80 @@
+import { UN_REQ_NUM } from "@/constant/sys";
+import {
+  getCaseScriptSaveOrUpdateUrl,
+  getCaseScriptInfoUrl,
+  getVideoCover,
+  axios,
+} from "@/request";
+
+export enum FireStatus {
+  all = UN_REQ_NUM,
+  incomplete = 0,
+  complete = 1,
+}
+
+export type Fire = {
+  accidentDate: string;
+  createTime: string;
+  creatorDeptId: string;
+  caseId: number;
+  mapShow: boolean;
+  creatorId: string;
+  creatorName: string;
+  deptId: string;
+  editTime: string;
+  editorId: string;
+  editorName: string;
+  fireReason: string;
+  id: string;
+  isTeached: number;
+  organizerDeptName: string;
+  organizerUsers: string;
+  projectAddress: string;
+  latAndLong: string;
+  latlng: string;
+  projectName: string;
+  projectSite: string;
+  projectSiteCode: string;
+  projectSn: string;
+  status: FireStatus;
+  statusDesc: string;
+  updateTime: string;
+  isDelete?: number;
+
+  field1: string;
+  field2: string;
+  field3: string;
+  field4: string;
+
+  field5: string;
+  field6: string;
+  field7: string;
+  field8: string;
+  field9: string;
+  field10: string;
+};
+
+export enum FirePaggingRoute {
+  fire = 1,
+  teached = 2,
+}
+
+export const getCaseScriptInfo = async (caseId) => {
+  let res = await axios.get(getCaseScriptInfoUrl, { params: { caseId, ingoreRes: true } });
+  console.log(res);
+  if (res && res.code === 0) {
+    return res.data;
+  } else {
+    // window.location.href = '/'
+    throw {
+      code: 4008,
+      msg: "用户未登录"
+    };
+  }
+};
+
+export const CaseScriptSaveOrUpdate = async (fire: Omit<Fire, "id">) =>
+  axios.post(getCaseScriptSaveOrUpdateUrl, fire, { params: { ingoreRes: true } });
+
+export const CaseScriptGetCover = async (url: string, width = 200, height = 200) =>
+  (await axios.get(getVideoCover, { params: { videoPath: url, width, height } })).data;

+ 15 - 0
src/app/xmfire/constant.ts

@@ -0,0 +1,15 @@
+import { AppConstant } from "..";
+import banner from "@/assets/image/jmfirebg@2x.png";
+import ico from "@/assets/image/jmlogo.png";
+import { xmfireDeptId } from "@/constant/appDeptId";
+
+export const appConstant: AppConstant = {
+  title: "火灾调查三维远程勘验平台",
+  desc: "",
+  // desc: "Three-dimensional remote prospecting platform for fire scenes",
+  ico,
+  name: "fire",
+  banner,
+  loginComponent: () => import("./view/login/index.vue"),
+  deptId: xmfireDeptId,
+};

二进制
src/app/xmfire/images/banner@2x.png


+ 35 - 0
src/app/xmfire/routeConfig.ts

@@ -0,0 +1,35 @@
+import { RouteName } from "@/router/routeName";
+import { Routes } from "@/router/config";
+
+export const FireRouteName = {
+  ...RouteName,
+  dispatch: "dispatch",
+  teaching: "teaching",
+} as const;
+
+export const menuRouteNames = [
+  FireRouteName.statistics,
+  FireRouteName.downloadLog,
+  FireRouteName.vrmodel,
+  FireRouteName.camera,
+  FireRouteName.dispatch,
+  FireRouteName.teaching,
+  FireRouteName.organization,
+  FireRouteName.role,
+  FireRouteName.user,
+];
+
+export const routes: Routes = [
+  {
+    name: FireRouteName.dispatch,
+    path: "dispatch",
+    component: () => import("../fire/view/dispatch/index.vue"),
+    meta: { title: "火调管理", icon: "iconfire_management" },
+  },
+  {
+    name: FireRouteName.teaching,
+    path: "teaching",
+    component: () => import("../fire/view/dispatch/index.vue"),
+    meta: { title: "教学平台", icon: "iconfire_study" },
+  },
+];

+ 7 - 0
src/app/xmfire/useStyle.scss

@@ -0,0 +1,7 @@
+@forward 'element-plus/theme-chalk/src/common/var.scss'with ($colors: ('primary': ('base': #D8000A, )),
+  $common-component-size: ('default': 40px));
+
+.delBtn {
+  color   : #D8000A;
+  // color: rgb(250, 85, 85);
+}

+ 385 - 0
src/app/xmfire/view/login/index.vue

@@ -0,0 +1,385 @@
+<template>
+  <div class="system-layer" :style="{ backgroundImage: `url(${appConstant.banner})` }">
+    <div class="content">
+      <div class="login-layer">
+        <div class="content">
+          <div class="info">
+            <img src="@/app/xmfire/images/banner@2x.png" />
+            <span>{{ dateFormat(currentDate, "yyyy-MM-dd hh:mm:ss") }}</span>
+          </div>
+          <el-form class="panel login" :model="form" @submit.stop>
+            <img :src="appConstant.ico" />
+            <h2>{{ appConstant.title }}</h2>
+            <el-form-item class="panel-form-item">
+              <p class="err-info">{{ verification.phone }}</p>
+              <el-input
+                :maxlength="11"
+                v-model.trim="form.phone"
+                placeholder="手机号"
+                @keydown.enter="submitClick"
+              ></el-input>
+            </el-form-item>
+            <el-form-item class="panel-form-item">
+              <p class="err-info">{{ verification.psw }}</p>
+              <el-input
+                v-model="form.psw"
+                :maxlength="16"
+                placeholder="密码"
+                :type="flag ? 'password' : 'text'"
+                @keydown.enter="submitClick"
+              >
+                <template v-slot:suffix>
+                  <img
+                    v-if="flag"
+                    @click="flag = !flag"
+                    style="width: 20px; margin: 15px"
+                    src="@/assets/image/pasword.png"
+                    alt=""
+                  />
+                  <el-icon :size="20" @click="flag = !flag" class="icon-style" v-else>
+                    <View />
+                  </el-icon>
+                </template>
+              </el-input>
+            </el-form-item>
+
+            <el-form-item class="panel-form-item code-form-item">
+              <p class="err-info">{{ verification.code }}</p>
+              <el-input
+                v-model="form.code"
+                placeholder="验证码"
+                @keydown.enter="submitClick"
+                class="code-input"
+              >
+                <template v-slot:append>
+                  <img :src="codeImg" class="code-img" @click="refer" />
+                </template>
+              </el-input>
+            </el-form-item>
+
+            <el-form-item class="panel-form-item">
+              <el-button type="primary" class="fill" @click="submitClick">登录</el-button>
+            </el-form-item>
+            <div class="more">
+              <a @click="$router.push({ name: 'forget' })">忘记密码</a>
+            </div>
+          </el-form>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, watch, ref, computed, onUnmounted } from "vue";
+import { openErrorMsg, baseURL, getCode } from "@/request";
+import { PHONE } from "@/constant/REG";
+import { guid, strToParams, dateFormat } from "@/util";
+import { RouteName, router } from "@/router";
+import { login } from "@/store/system";
+import { appConstant } from "@/app";
+import { user } from "@/store/user";
+
+const currentDate = ref(new Date());
+const interval = setInterval(() => {
+  currentDate.value = new Date();
+});
+onUnmounted(() => clearInterval(interval));
+
+// 是否显示明文密码
+const flag = ref(true);
+// 表单
+const form = reactive({
+  phone: localStorage.getItem("userName") || "",
+  psw: localStorage.getItem("password") || "",
+  code: "",
+  remember: import.meta.env.DEV || localStorage.getItem("remember") === "1",
+});
+const verification = reactive({ phone: "", psw: "", code: "" });
+// 验证
+watch(
+  form,
+  () => {
+    console.log("form", form);
+    if (!form.phone) {
+      verification.phone = "请输入手机号";
+    } else if (form.phone == "88888888888") {
+      verification.phone = "";
+    } else {
+      verification.phone = PHONE.REG.test(form.phone) ? "" : PHONE.tip;
+    }
+    if (!form.psw) {
+      verification.psw = "请输入密码";
+    } else {
+      verification.psw = "";
+    }
+    if (!form.code.trim()) {
+      verification.code = "请输入验证码";
+    } else {
+      verification.code = "";
+    }
+  },
+  { immediate: true }
+);
+
+// 图片验证码
+const imgKey = ref(guid());
+const refer = () => (imgKey.value = guid());
+const codeImg = computed(() => baseURL + getCode + "?key=" + imgKey.value);
+
+// 表单提交
+const submitClick = async () => {
+  if (verification.phone && verification.phone !== "88888888888") {
+    return openErrorMsg(verification.phone);
+  }
+  if (verification.psw) return openErrorMsg(verification.psw);
+  if (verification.code) return openErrorMsg(verification.code);
+
+  try {
+    await login({ phoneNum: form.phone, code: form.code, password: form.psw });
+
+    if (form.remember) {
+      localStorage.setItem("userName", form.phone);
+      localStorage.setItem("password", form.psw);
+      localStorage.setItem("remember", "1");
+    } else {
+      localStorage.setItem("userName", "");
+      localStorage.setItem("password", "");
+      localStorage.setItem("remember", "0");
+    }
+
+    const params = strToParams(window.location.search);
+    if ("redirect" in params) {
+      const url = new URL(unescape(params.redirect));
+      url.searchParams.delete("token");
+      url.searchParams.append("token", user.value.token);
+      window.location.replace(url);
+    } else {
+      router.replace({ name: RouteName.scene });
+    }
+  } catch (e) {
+    console.error(e);
+    return refer();
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.system-layer {
+  width: 100%;
+  min-height: 100%;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  background: no-repeat left bottom;
+  background-size: cover;
+}
+
+.content {
+  display: flex;
+  justify-content: center;
+  align-items: flex-start;
+}
+
+.login-layer {
+  text-align: right;
+}
+.content {
+  display: flex;
+  justify-content: center;
+  align-items: flex-start;
+}
+.info {
+  color: #fff;
+  flex: none;
+  text-align: left;
+  position: relative;
+  height: 100%;
+  pointer-events: none;
+  span {
+    position: absolute;
+    bottom: 20px;
+    left: 50%;
+    transform: translateX(-50%);
+    font-size: 18px;
+    color: #fff;
+  }
+
+  img {
+    height: 562px;
+  }
+  h1 {
+    font-size: 2.8rem;
+    line-height: 3.7rem;
+    margin-bottom: 0.7rem;
+  }
+  p {
+    font-size: 2rem;
+    line-height: 2.2rem;
+  }
+}
+
+.top-text {
+  margin-bottom: 50px;
+  pointer-events: none;
+  height: 153px;
+  min-width: 1200px;
+  img {
+    position: absolute;
+    right: 0;
+  }
+}
+.login {
+  width: 380px;
+  padding: 40px 40px 30px;
+  position: relative;
+  display: inline-block;
+
+  h2 {
+    padding-left: 0;
+    padding-bottom: 0;
+    border-bottom: none;
+    margin-bottom: 2.14rem;
+
+    span {
+      color: #646566;
+      font-size: 1.33rem;
+      margin-top: 0.71rem;
+      display: block;
+    }
+  }
+
+  .panel-form-item {
+    padding-left: 0;
+    padding-right: 0;
+    .icon-style {
+      margin-right: 14px;
+      font-size: 20px;
+      line-height: 50px;
+    }
+  }
+
+  .more a:first-child::after {
+    content: "";
+    position: absolute;
+    right: -5px;
+    width: 1px;
+    height: 8px;
+    background: #dcdee0;
+    top: 50%;
+    transform: translateY(-50%);
+  }
+}
+
+.code-img {
+  width: 100%;
+  height: 100%;
+  // object-fit: cover;
+}
+</style>
+
+<style lang="scss">
+.system-layer .panel {
+  background: rgba(255, 255, 255, 0.7);
+  box-shadow: 0px 2px 20px 0px rgba(5, 38, 38, 0.15);
+  width: 380px;
+  padding: 40px 40px 30px;
+  text-align: initial;
+  text-align: center;
+  border-radius: 0;
+
+  img {
+    width: 100px;
+  }
+
+  h2 {
+    color: #323233;
+    font-size: 1.4rem;
+    margin-bottom: 2.14rem;
+    font-weight: normal;
+    padding-left: 0;
+    border-bottom: 0;
+    text-align: center;
+  }
+
+  .panel-form-item {
+    position: relative;
+    padding-bottom: 2.14rem;
+    margin: 0;
+    padding-left: 0;
+    padding-right: 0;
+
+    &.remember {
+      padding: 0;
+    }
+
+    .err-info {
+      position: absolute;
+      top: 100%;
+      left: 0;
+      font-size: 1rem;
+      line-height: 2.14rem;
+      color: #fa5555;
+    }
+  }
+
+  .more {
+    text-align: center;
+
+    a {
+      color: #323233;
+      line-height: 21px;
+      font-size: 16px;
+      margin: 0 5px;
+      position: relative;
+      text-decoration: none;
+      cursor: pointer;
+    }
+  }
+}
+
+.panel-form-item .el-select {
+  width: 100%;
+}
+
+.panel-form-item .el-button,
+.panel-form-item .el-input__inner {
+  height: 50px;
+  font-size: 1.14rem;
+}
+
+.panel-form-item .el-button {
+  line-height: 26px;
+  font-weight: bold;
+  font-size: 16px;
+}
+
+.panel-form-item .el-form-item__label {
+  line-height: 50px;
+}
+.login .code-form-item .el-input {
+  display: flex;
+}
+
+.login .code-form-item .el-input-group__append {
+  flex: none;
+  margin-left: 10px;
+  width: 95px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0;
+}
+
+.login .code-form-item .el-input__inner {
+  flex: 1;
+}
+.login .code-form-item .el-input-group__append,
+.login .code-form-item .el-input__inner {
+  border-radius: 4px;
+}
+input[type="password"]::-ms-reveal {
+  display: none;
+}
+</style>

+ 539 - 0
src/assets/icon/fire/demo.css

@@ -0,0 +1,539 @@
+/* Logo 字体 */
+@font-face {
+  font-family: "iconfont logo";
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+}
+
+.logo {
+  font-family: "iconfont logo";
+  font-size: 160px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* tabs */
+.nav-tabs {
+  position: relative;
+}
+
+.nav-tabs .nav-more {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 42px;
+  line-height: 42px;
+  color: #666;
+}
+
+#tabs {
+  border-bottom: 1px solid #eee;
+}
+
+#tabs li {
+  cursor: pointer;
+  width: 100px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 16px;
+  border-bottom: 2px solid transparent;
+  position: relative;
+  z-index: 1;
+  margin-bottom: -1px;
+  color: #666;
+}
+
+
+#tabs .active {
+  border-bottom-color: #f00;
+  color: #222;
+}
+
+.tab-container .content {
+  display: none;
+}
+
+/* 页面布局 */
+.main {
+  padding: 30px 100px;
+  width: 960px;
+  margin: 0 auto;
+}
+
+.main .logo {
+  color: #333;
+  text-align: left;
+  margin-bottom: 30px;
+  line-height: 1;
+  height: 110px;
+  margin-top: -50px;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.main .logo a {
+  font-size: 160px;
+  color: #333;
+}
+
+.helps {
+  margin-top: 40px;
+}
+
+.helps pre {
+  padding: 20px;
+  margin: 10px 0;
+  border: solid 1px #e7e1cd;
+  background-color: #fffdef;
+  overflow: auto;
+}
+
+.icon_lists {
+  width: 100% !important;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.icon_lists li {
+  width: 100px;
+  margin-bottom: 10px;
+  margin-right: 20px;
+  text-align: center;
+  list-style: none !important;
+  cursor: default;
+}
+
+.icon_lists li .code-name {
+  line-height: 1.2;
+}
+
+.icon_lists .icon {
+  display: block;
+  height: 100px;
+  line-height: 100px;
+  font-size: 42px;
+  margin: 10px auto;
+  color: #333;
+  -webkit-transition: font-size 0.25s linear, width 0.25s linear;
+  -moz-transition: font-size 0.25s linear, width 0.25s linear;
+  transition: font-size 0.25s linear, width 0.25s linear;
+}
+
+.icon_lists .icon:hover {
+  font-size: 100px;
+}
+
+.icon_lists .svg-icon {
+  /* 通过设置 font-size 来改变图标大小 */
+  width: 1em;
+  /* 图标和文字相邻时,垂直对齐 */
+  vertical-align: -0.15em;
+  /* 通过设置 color 来改变 SVG 的颜色/fill */
+  fill: currentColor;
+  /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
+      normalize.css 中也包含这行 */
+  overflow: hidden;
+}
+
+.icon_lists li .name,
+.icon_lists li .code-name {
+  color: #666;
+}
+
+/* markdown 样式 */
+.markdown {
+  color: #666;
+  font-size: 14px;
+  line-height: 1.8;
+}
+
+.highlight {
+  line-height: 1.5;
+}
+
+.markdown img {
+  vertical-align: middle;
+  max-width: 100%;
+}
+
+.markdown h1 {
+  color: #404040;
+  font-weight: 500;
+  line-height: 40px;
+  margin-bottom: 24px;
+}
+
+.markdown h2,
+.markdown h3,
+.markdown h4,
+.markdown h5,
+.markdown h6 {
+  color: #404040;
+  margin: 1.6em 0 0.6em 0;
+  font-weight: 500;
+  clear: both;
+}
+
+.markdown h1 {
+  font-size: 28px;
+}
+
+.markdown h2 {
+  font-size: 22px;
+}
+
+.markdown h3 {
+  font-size: 16px;
+}
+
+.markdown h4 {
+  font-size: 14px;
+}
+
+.markdown h5 {
+  font-size: 12px;
+}
+
+.markdown h6 {
+  font-size: 12px;
+}
+
+.markdown hr {
+  height: 1px;
+  border: 0;
+  background: #e9e9e9;
+  margin: 16px 0;
+  clear: both;
+}
+
+.markdown p {
+  margin: 1em 0;
+}
+
+.markdown>p,
+.markdown>blockquote,
+.markdown>.highlight,
+.markdown>ol,
+.markdown>ul {
+  width: 80%;
+}
+
+.markdown ul>li {
+  list-style: circle;
+}
+
+.markdown>ul li,
+.markdown blockquote ul>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown>ul li p,
+.markdown>ol li p {
+  margin: 0.6em 0;
+}
+
+.markdown ol>li {
+  list-style: decimal;
+}
+
+.markdown>ol li,
+.markdown blockquote ol>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown code {
+  margin: 0 3px;
+  padding: 0 5px;
+  background: #eee;
+  border-radius: 3px;
+}
+
+.markdown strong,
+.markdown b {
+  font-weight: 600;
+}
+
+.markdown>table {
+  border-collapse: collapse;
+  border-spacing: 0px;
+  empty-cells: show;
+  border: 1px solid #e9e9e9;
+  width: 95%;
+  margin-bottom: 24px;
+}
+
+.markdown>table th {
+  white-space: nowrap;
+  color: #333;
+  font-weight: 600;
+}
+
+.markdown>table th,
+.markdown>table td {
+  border: 1px solid #e9e9e9;
+  padding: 8px 16px;
+  text-align: left;
+}
+
+.markdown>table th {
+  background: #F7F7F7;
+}
+
+.markdown blockquote {
+  font-size: 90%;
+  color: #999;
+  border-left: 4px solid #e9e9e9;
+  padding-left: 0.8em;
+  margin: 1em 0;
+}
+
+.markdown blockquote p {
+  margin: 0;
+}
+
+.markdown .anchor {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  margin-left: 8px;
+}
+
+.markdown .waiting {
+  color: #ccc;
+}
+
+.markdown h1:hover .anchor,
+.markdown h2:hover .anchor,
+.markdown h3:hover .anchor,
+.markdown h4:hover .anchor,
+.markdown h5:hover .anchor,
+.markdown h6:hover .anchor {
+  opacity: 1;
+  display: inline-block;
+}
+
+.markdown>br,
+.markdown>p>br {
+  clear: both;
+}
+
+
+.hljs {
+  display: block;
+  background: white;
+  padding: 0.5em;
+  color: #333333;
+  overflow-x: auto;
+}
+
+.hljs-comment,
+.hljs-meta {
+  color: #969896;
+}
+
+.hljs-string,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-strong,
+.hljs-emphasis,
+.hljs-quote {
+  color: #df5000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-type {
+  color: #a71d5d;
+}
+
+.hljs-literal,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-attribute {
+  color: #0086b3;
+}
+
+.hljs-section,
+.hljs-name {
+  color: #63a35c;
+}
+
+.hljs-tag {
+  color: #333333;
+}
+
+.hljs-title,
+.hljs-attr,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+  color: #795da3;
+}
+
+.hljs-addition {
+  color: #55a532;
+  background-color: #eaffea;
+}
+
+.hljs-deletion {
+  color: #bd2c00;
+  background-color: #ffecec;
+}
+
+.hljs-link {
+  text-decoration: underline;
+}
+
+/* 代码高亮 */
+/* PrismJS 1.15.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  background: none;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre)>code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+  white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #9a6e3a;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function,
+.token.class-name {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}

+ 878 - 0
src/assets/icon/fire/demo_index.html

@@ -0,0 +1,878 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8"/>
+  <title>iconfont Demo</title>
+  <link rel="shortcut icon" href="//img.alicdn.com/imgextra/i2/O1CN01ZyAlrn1MwaMhqz36G_!!6000000001499-73-tps-64-64.ico" type="image/x-icon"/>
+  <link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01EYTRnJ297D6vehehJ_!!6000000008020-55-tps-64-64.svg"/>
+  <link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
+  <link rel="stylesheet" href="demo.css">
+  <link rel="stylesheet" href="iconfont.css">
+  <script src="iconfont.js"></script>
+  <!-- jQuery -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
+  <!-- 代码高亮 -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
+  <style>
+    .main .logo {
+      margin-top: 0;
+      height: auto;
+    }
+
+    .main .logo a {
+      display: flex;
+      align-items: center;
+    }
+
+    .main .logo .sub-title {
+      margin-left: 0.5em;
+      font-size: 22px;
+      color: #fff;
+      background: linear-gradient(-45deg, #3967FF, #B500FE);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+    }
+  </style>
+</head>
+<body>
+  <div class="main">
+    <h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
+      <img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
+      
+    </a></h1>
+    <div class="nav-tabs">
+      <ul id="tabs" class="dib-box">
+        <li class="dib active"><span>Unicode</span></li>
+        <li class="dib"><span>Font class</span></li>
+        <li class="dib"><span>Symbol</span></li>
+      </ul>
+      
+      <a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=2466491" target="_blank" class="nav-more">查看项目</a>
+      
+    </div>
+    <div class="tab-container">
+      <div class="content unicode" style="display: block;">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6e2;</span>
+                <div class="name">fire_role_management</div>
+                <div class="code-name">&amp;#xe6e2;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6e0;</span>
+                <div class="name">fire_organization</div>
+                <div class="code-name">&amp;#xe6e0;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a0;</span>
+                <div class="name">fire_home_study</div>
+                <div class="code-name">&amp;#xe6a0;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6a1;</span>
+                <div class="name">fire_home_vr</div>
+                <div class="code-name">&amp;#xe6a1;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69f;</span>
+                <div class="name">collect</div>
+                <div class="code-name">&amp;#xe69f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe695;</span>
+                <div class="name">fire_mic_off</div>
+                <div class="code-name">&amp;#xe695;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe696;</span>
+                <div class="name">fire_cancel</div>
+                <div class="code-name">&amp;#xe696;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe697;</span>
+                <div class="name">fire_pen_open</div>
+                <div class="code-name">&amp;#xe697;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe698;</span>
+                <div class="name">fire_pen_off</div>
+                <div class="code-name">&amp;#xe698;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe699;</span>
+                <div class="name">fire_cross</div>
+                <div class="code-name">&amp;#xe699;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69a;</span>
+                <div class="name">fire_exit</div>
+                <div class="code-name">&amp;#xe69a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69b;</span>
+                <div class="name">fire_share</div>
+                <div class="code-name">&amp;#xe69b;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69c;</span>
+                <div class="name">fire_vr</div>
+                <div class="code-name">&amp;#xe69c;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69d;</span>
+                <div class="name">fire_mic_on</div>
+                <div class="code-name">&amp;#xe69d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe69e;</span>
+                <div class="name">fire_appendix</div>
+                <div class="code-name">&amp;#xe69e;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68f;</span>
+                <div class="name">fire_119</div>
+                <div class="code-name">&amp;#xe68f;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe690;</span>
+                <div class="name">fire_arrow</div>
+                <div class="code-name">&amp;#xe690;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe691;</span>
+                <div class="name">fire_building</div>
+                <div class="code-name">&amp;#xe691;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe692;</span>
+                <div class="name">fire_other</div>
+                <div class="code-name">&amp;#xe692;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe693;</span>
+                <div class="name">fire_bus</div>
+                <div class="code-name">&amp;#xe693;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe694;</span>
+                <div class="name">fire_recycle</div>
+                <div class="code-name">&amp;#xe694;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe686;</span>
+                <div class="name">fire_user</div>
+                <div class="code-name">&amp;#xe686;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe687;</span>
+                <div class="name">fire_camera</div>
+                <div class="code-name">&amp;#xe687;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe688;</span>
+                <div class="name">log_eye_normal</div>
+                <div class="code-name">&amp;#xe688;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe689;</span>
+                <div class="name">log_eye_selected</div>
+                <div class="code-name">&amp;#xe689;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68a;</span>
+                <div class="name">fire_home</div>
+                <div class="code-name">&amp;#xe68a;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68b;</span>
+                <div class="name">fire_annex</div>
+                <div class="code-name">&amp;#xe68b;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68c;</span>
+                <div class="name">fire_management </div>
+                <div class="code-name">&amp;#xe68c;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68d;</span>
+                <div class="name">fire_study</div>
+                <div class="code-name">&amp;#xe68d;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe68e;</span>
+                <div class="name">fire_scenes</div>
+                <div class="code-name">&amp;#xe68e;</div>
+              </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="unicode-">Unicode 引用</h2>
+          <hr>
+
+          <p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
+          <ul>
+            <li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
+            <li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
+          </ul>
+          <blockquote>
+            <p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
+          </blockquote>
+          <p>Unicode 使用步骤如下:</p>
+          <h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
+<pre><code class="language-css"
+>@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1639981610282') format('woff2'),
+       url('iconfont.woff?t=1639981610282') format('woff'),
+       url('iconfont.ttf?t=1639981610282') format('truetype');
+}
+</code></pre>
+          <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
+<pre><code class="language-css"
+>.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
+<pre>
+<code class="language-html"
+>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
+</code></pre>
+          <blockquote>
+            <p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+          </blockquote>
+          </div>
+      </div>
+      <div class="content font-class">
+        <ul class="icon_lists dib-box">
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_role_management"></span>
+            <div class="name">
+              fire_role_management
+            </div>
+            <div class="code-name">.iconfire_role_management
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_organization"></span>
+            <div class="name">
+              fire_organization
+            </div>
+            <div class="code-name">.iconfire_organization
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_home_study"></span>
+            <div class="name">
+              fire_home_study
+            </div>
+            <div class="code-name">.iconfire_home_study
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_home_vr"></span>
+            <div class="name">
+              fire_home_vr
+            </div>
+            <div class="code-name">.iconfire_home_vr
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconcollect"></span>
+            <div class="name">
+              collect
+            </div>
+            <div class="code-name">.iconcollect
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_mic_off"></span>
+            <div class="name">
+              fire_mic_off
+            </div>
+            <div class="code-name">.iconfire_mic_off
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_cancel"></span>
+            <div class="name">
+              fire_cancel
+            </div>
+            <div class="code-name">.iconfire_cancel
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_pen_on"></span>
+            <div class="name">
+              fire_pen_open
+            </div>
+            <div class="code-name">.iconfire_pen_on
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_pen_off"></span>
+            <div class="name">
+              fire_pen_off
+            </div>
+            <div class="code-name">.iconfire_pen_off
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_cross"></span>
+            <div class="name">
+              fire_cross
+            </div>
+            <div class="code-name">.iconfire_cross
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_exit"></span>
+            <div class="name">
+              fire_exit
+            </div>
+            <div class="code-name">.iconfire_exit
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_share"></span>
+            <div class="name">
+              fire_share
+            </div>
+            <div class="code-name">.iconfire_share
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_vr"></span>
+            <div class="name">
+              fire_vr
+            </div>
+            <div class="code-name">.iconfire_vr
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_mic_on"></span>
+            <div class="name">
+              fire_mic_on
+            </div>
+            <div class="code-name">.iconfire_mic_on
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_appendix"></span>
+            <div class="name">
+              fire_appendix
+            </div>
+            <div class="code-name">.iconfire_appendix
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_119"></span>
+            <div class="name">
+              fire_119
+            </div>
+            <div class="code-name">.iconfire_119
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_arrow"></span>
+            <div class="name">
+              fire_arrow
+            </div>
+            <div class="code-name">.iconfire_arrow
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_building"></span>
+            <div class="name">
+              fire_building
+            </div>
+            <div class="code-name">.iconfire_building
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_other"></span>
+            <div class="name">
+              fire_other
+            </div>
+            <div class="code-name">.iconfire_other
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_bus"></span>
+            <div class="name">
+              fire_bus
+            </div>
+            <div class="code-name">.iconfire_bus
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_recycle"></span>
+            <div class="name">
+              fire_recycle
+            </div>
+            <div class="code-name">.iconfire_recycle
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_user"></span>
+            <div class="name">
+              fire_user
+            </div>
+            <div class="code-name">.iconfire_user
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_camera"></span>
+            <div class="name">
+              fire_camera
+            </div>
+            <div class="code-name">.iconfire_camera
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconlog_eye_normal"></span>
+            <div class="name">
+              log_eye_normal
+            </div>
+            <div class="code-name">.iconlog_eye_normal
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconlog_eye_selected"></span>
+            <div class="name">
+              log_eye_selected
+            </div>
+            <div class="code-name">.iconlog_eye_selected
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_home"></span>
+            <div class="name">
+              fire_home
+            </div>
+            <div class="code-name">.iconfire_home
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_annex"></span>
+            <div class="name">
+              fire_annex
+            </div>
+            <div class="code-name">.iconfire_annex
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_management"></span>
+            <div class="name">
+              fire_management 
+            </div>
+            <div class="code-name">.iconfire_management
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_study"></span>
+            <div class="name">
+              fire_study
+            </div>
+            <div class="code-name">.iconfire_study
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconfire_scenes"></span>
+            <div class="name">
+              fire_scenes
+            </div>
+            <div class="code-name">.iconfire_scenes
+            </div>
+          </li>
+          
+        </ul>
+        <div class="article markdown">
+        <h2 id="font-class-">font-class 引用</h2>
+        <hr>
+
+        <p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
+        <p>与 Unicode 使用方式相比,具有如下特点:</p>
+        <ul>
+          <li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
+          <li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
+        </ul>
+        <p>使用步骤如下:</p>
+        <h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
+<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
+</code></pre>
+        <h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;span class="iconfont iconxxx"&gt;&lt;/span&gt;
+</code></pre>
+        <blockquote>
+          <p>"
+            iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+        </blockquote>
+      </div>
+      </div>
+      <div class="content symbol">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_role_management"></use>
+                </svg>
+                <div class="name">fire_role_management</div>
+                <div class="code-name">#iconfire_role_management</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_organization"></use>
+                </svg>
+                <div class="name">fire_organization</div>
+                <div class="code-name">#iconfire_organization</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_home_study"></use>
+                </svg>
+                <div class="name">fire_home_study</div>
+                <div class="code-name">#iconfire_home_study</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_home_vr"></use>
+                </svg>
+                <div class="name">fire_home_vr</div>
+                <div class="code-name">#iconfire_home_vr</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconcollect"></use>
+                </svg>
+                <div class="name">collect</div>
+                <div class="code-name">#iconcollect</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_mic_off"></use>
+                </svg>
+                <div class="name">fire_mic_off</div>
+                <div class="code-name">#iconfire_mic_off</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_cancel"></use>
+                </svg>
+                <div class="name">fire_cancel</div>
+                <div class="code-name">#iconfire_cancel</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_pen_on"></use>
+                </svg>
+                <div class="name">fire_pen_open</div>
+                <div class="code-name">#iconfire_pen_on</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_pen_off"></use>
+                </svg>
+                <div class="name">fire_pen_off</div>
+                <div class="code-name">#iconfire_pen_off</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_cross"></use>
+                </svg>
+                <div class="name">fire_cross</div>
+                <div class="code-name">#iconfire_cross</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_exit"></use>
+                </svg>
+                <div class="name">fire_exit</div>
+                <div class="code-name">#iconfire_exit</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_share"></use>
+                </svg>
+                <div class="name">fire_share</div>
+                <div class="code-name">#iconfire_share</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_vr"></use>
+                </svg>
+                <div class="name">fire_vr</div>
+                <div class="code-name">#iconfire_vr</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_mic_on"></use>
+                </svg>
+                <div class="name">fire_mic_on</div>
+                <div class="code-name">#iconfire_mic_on</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_appendix"></use>
+                </svg>
+                <div class="name">fire_appendix</div>
+                <div class="code-name">#iconfire_appendix</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_119"></use>
+                </svg>
+                <div class="name">fire_119</div>
+                <div class="code-name">#iconfire_119</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_arrow"></use>
+                </svg>
+                <div class="name">fire_arrow</div>
+                <div class="code-name">#iconfire_arrow</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_building"></use>
+                </svg>
+                <div class="name">fire_building</div>
+                <div class="code-name">#iconfire_building</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_other"></use>
+                </svg>
+                <div class="name">fire_other</div>
+                <div class="code-name">#iconfire_other</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_bus"></use>
+                </svg>
+                <div class="name">fire_bus</div>
+                <div class="code-name">#iconfire_bus</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_recycle"></use>
+                </svg>
+                <div class="name">fire_recycle</div>
+                <div class="code-name">#iconfire_recycle</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_user"></use>
+                </svg>
+                <div class="name">fire_user</div>
+                <div class="code-name">#iconfire_user</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_camera"></use>
+                </svg>
+                <div class="name">fire_camera</div>
+                <div class="code-name">#iconfire_camera</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconlog_eye_normal"></use>
+                </svg>
+                <div class="name">log_eye_normal</div>
+                <div class="code-name">#iconlog_eye_normal</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconlog_eye_selected"></use>
+                </svg>
+                <div class="name">log_eye_selected</div>
+                <div class="code-name">#iconlog_eye_selected</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_home"></use>
+                </svg>
+                <div class="name">fire_home</div>
+                <div class="code-name">#iconfire_home</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_annex"></use>
+                </svg>
+                <div class="name">fire_annex</div>
+                <div class="code-name">#iconfire_annex</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_management"></use>
+                </svg>
+                <div class="name">fire_management </div>
+                <div class="code-name">#iconfire_management</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_study"></use>
+                </svg>
+                <div class="name">fire_study</div>
+                <div class="code-name">#iconfire_study</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconfire_scenes"></use>
+                </svg>
+                <div class="name">fire_scenes</div>
+                <div class="code-name">#iconfire_scenes</div>
+            </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="symbol-">Symbol 引用</h2>
+          <hr>
+
+          <p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
+            这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
+          <ul>
+            <li>支持多色图标了,不再受单色限制。</li>
+            <li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
+            <li>兼容性较差,支持 IE9+,及现代浏览器。</li>
+            <li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
+          </ul>
+          <p>使用步骤如下:</p>
+          <h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
+<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
+</code></pre>
+          <h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
+<pre><code class="language-html">&lt;style&gt;
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+&lt;/style&gt;
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
+  &lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
+&lt;/svg&gt;
+</code></pre>
+          </div>
+      </div>
+
+    </div>
+  </div>
+  <script>
+  $(document).ready(function () {
+      $('.tab-container .content:first').show()
+
+      $('#tabs li').click(function (e) {
+        var tabContent = $('.tab-container .content')
+        var index = $(this).index()
+
+        if ($(this).hasClass('active')) {
+          return
+        } else {
+          $('#tabs li').removeClass('active')
+          $(this).addClass('active')
+
+          tabContent.hide().eq(index).fadeIn()
+        }
+      })
+    })
+  </script>
+</body>
+</html>

+ 135 - 0
src/assets/icon/fire/iconfont.css

@@ -0,0 +1,135 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 2466491 */
+  src: url('iconfont.woff2?t=1639981610282') format('woff2'),
+       url('iconfont.woff?t=1639981610282') format('woff'),
+       url('iconfont.ttf?t=1639981610282') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.iconfire_role_management:before {
+  content: "\e6e2";
+}
+
+.iconfire_organization:before {
+  content: "\e6e0";
+}
+
+.iconfire_home_study:before {
+  content: "\e6a0";
+}
+
+.iconfire_home_vr:before {
+  content: "\e6a1";
+}
+
+.iconcollect:before {
+  content: "\e69f";
+}
+
+.iconfire_mic_off:before {
+  content: "\e695";
+}
+
+.iconfire_cancel:before {
+  content: "\e696";
+}
+
+.iconfire_pen_on:before {
+  content: "\e697";
+}
+
+.iconfire_pen_off:before {
+  content: "\e698";
+}
+
+.iconfire_cross:before {
+  content: "\e699";
+}
+
+.iconfire_exit:before {
+  content: "\e69a";
+}
+
+.iconfire_share:before {
+  content: "\e69b";
+}
+
+.iconfire_vr:before {
+  content: "\e69c";
+}
+
+.iconfire_mic_on:before {
+  content: "\e69d";
+}
+
+.iconfire_appendix:before {
+  content: "\e69e";
+}
+
+.iconfire_119:before {
+  content: "\e68f";
+}
+
+.iconfire_arrow:before {
+  content: "\e690";
+}
+
+.iconfire_building:before {
+  content: "\e691";
+}
+
+.iconfire_other:before {
+  content: "\e692";
+}
+
+.iconfire_bus:before {
+  content: "\e693";
+}
+
+.iconfire_recycle:before {
+  content: "\e694";
+}
+
+.iconfire_user:before {
+  content: "\e686";
+}
+
+.iconfire_camera:before {
+  content: "\e687";
+}
+
+.iconlog_eye_normal:before {
+  content: "\e688";
+}
+
+.iconlog_eye_selected:before {
+  content: "\e689";
+}
+
+.iconfire_home:before {
+  content: "\e68a";
+}
+
+.iconfire_annex:before {
+  content: "\e68b";
+}
+
+.iconfire_management:before {
+  content: "\e68c";
+}
+
+.iconfire_study:before {
+  content: "\e68d";
+}
+
+.iconfire_scenes:before {
+  content: "\e68e";
+}
+

二进制
src/assets/icon/fire/iconfont.eot


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


+ 219 - 0
src/assets/icon/fire/iconfont.json

@@ -0,0 +1,219 @@
+{
+  "id": "2466491",
+  "name": "消防火调VR",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "26624915",
+      "name": "fire_role_management",
+      "font_class": "fire_role_management",
+      "unicode": "e6e2",
+      "unicode_decimal": 59106
+    },
+    {
+      "icon_id": "26624684",
+      "name": "fire_organization",
+      "font_class": "fire_organization",
+      "unicode": "e6e0",
+      "unicode_decimal": 59104
+    },
+    {
+      "icon_id": "21445457",
+      "name": "fire_home_study",
+      "font_class": "fire_home_study",
+      "unicode": "e6a0",
+      "unicode_decimal": 59040
+    },
+    {
+      "icon_id": "21445458",
+      "name": "fire_home_vr",
+      "font_class": "fire_home_vr",
+      "unicode": "e6a1",
+      "unicode_decimal": 59041
+    },
+    {
+      "icon_id": "21104009",
+      "name": "collect",
+      "font_class": "collect",
+      "unicode": "e69f",
+      "unicode_decimal": 59039
+    },
+    {
+      "icon_id": "20808329",
+      "name": "fire_mic_off",
+      "font_class": "fire_mic_off",
+      "unicode": "e695",
+      "unicode_decimal": 59029
+    },
+    {
+      "icon_id": "20808330",
+      "name": "fire_cancel",
+      "font_class": "fire_cancel",
+      "unicode": "e696",
+      "unicode_decimal": 59030
+    },
+    {
+      "icon_id": "20808331",
+      "name": "fire_pen_open",
+      "font_class": "fire_pen_on",
+      "unicode": "e697",
+      "unicode_decimal": 59031
+    },
+    {
+      "icon_id": "20808332",
+      "name": "fire_pen_off",
+      "font_class": "fire_pen_off",
+      "unicode": "e698",
+      "unicode_decimal": 59032
+    },
+    {
+      "icon_id": "20808333",
+      "name": "fire_cross",
+      "font_class": "fire_cross",
+      "unicode": "e699",
+      "unicode_decimal": 59033
+    },
+    {
+      "icon_id": "20808334",
+      "name": "fire_exit",
+      "font_class": "fire_exit",
+      "unicode": "e69a",
+      "unicode_decimal": 59034
+    },
+    {
+      "icon_id": "20808335",
+      "name": "fire_share",
+      "font_class": "fire_share",
+      "unicode": "e69b",
+      "unicode_decimal": 59035
+    },
+    {
+      "icon_id": "20808336",
+      "name": "fire_vr",
+      "font_class": "fire_vr",
+      "unicode": "e69c",
+      "unicode_decimal": 59036
+    },
+    {
+      "icon_id": "20808338",
+      "name": "fire_mic_on",
+      "font_class": "fire_mic_on",
+      "unicode": "e69d",
+      "unicode_decimal": 59037
+    },
+    {
+      "icon_id": "20808339",
+      "name": "fire_appendix",
+      "font_class": "fire_appendix",
+      "unicode": "e69e",
+      "unicode_decimal": 59038
+    },
+    {
+      "icon_id": "20806963",
+      "name": "fire_119",
+      "font_class": "fire_119",
+      "unicode": "e68f",
+      "unicode_decimal": 59023
+    },
+    {
+      "icon_id": "20806964",
+      "name": "fire_arrow",
+      "font_class": "fire_arrow",
+      "unicode": "e690",
+      "unicode_decimal": 59024
+    },
+    {
+      "icon_id": "20806965",
+      "name": "fire_building",
+      "font_class": "fire_building",
+      "unicode": "e691",
+      "unicode_decimal": 59025
+    },
+    {
+      "icon_id": "20806966",
+      "name": "fire_other",
+      "font_class": "fire_other",
+      "unicode": "e692",
+      "unicode_decimal": 59026
+    },
+    {
+      "icon_id": "20806967",
+      "name": "fire_bus",
+      "font_class": "fire_bus",
+      "unicode": "e693",
+      "unicode_decimal": 59027
+    },
+    {
+      "icon_id": "20806968",
+      "name": "fire_recycle",
+      "font_class": "fire_recycle",
+      "unicode": "e694",
+      "unicode_decimal": 59028
+    },
+    {
+      "icon_id": "20805833",
+      "name": "fire_user",
+      "font_class": "fire_user",
+      "unicode": "e686",
+      "unicode_decimal": 59014
+    },
+    {
+      "icon_id": "20805834",
+      "name": "fire_camera",
+      "font_class": "fire_camera",
+      "unicode": "e687",
+      "unicode_decimal": 59015
+    },
+    {
+      "icon_id": "20805835",
+      "name": "log_eye_normal",
+      "font_class": "log_eye_normal",
+      "unicode": "e688",
+      "unicode_decimal": 59016
+    },
+    {
+      "icon_id": "20805836",
+      "name": "log_eye_selected",
+      "font_class": "log_eye_selected",
+      "unicode": "e689",
+      "unicode_decimal": 59017
+    },
+    {
+      "icon_id": "20805837",
+      "name": "fire_home",
+      "font_class": "fire_home",
+      "unicode": "e68a",
+      "unicode_decimal": 59018
+    },
+    {
+      "icon_id": "20805838",
+      "name": "fire_annex",
+      "font_class": "fire_annex",
+      "unicode": "e68b",
+      "unicode_decimal": 59019
+    },
+    {
+      "icon_id": "20805839",
+      "name": "fire_management ",
+      "font_class": "fire_management",
+      "unicode": "e68c",
+      "unicode_decimal": 59020
+    },
+    {
+      "icon_id": "20805840",
+      "name": "fire_study",
+      "font_class": "fire_study",
+      "unicode": "e68d",
+      "unicode_decimal": 59021
+    },
+    {
+      "icon_id": "20805841",
+      "name": "fire_scenes",
+      "font_class": "fire_scenes",
+      "unicode": "e68e",
+      "unicode_decimal": 59022
+    }
+  ]
+}

文件差异内容过多而无法显示
+ 101 - 0
src/assets/icon/fire/iconfont.svg


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


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


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


+ 539 - 0
src/assets/icon/fuse/demo.css

@@ -0,0 +1,539 @@
+/* Logo 字体 */
+@font-face {
+  font-family: "iconfont logo";
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+}
+
+.logo {
+  font-family: "iconfont logo";
+  font-size: 160px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* tabs */
+.nav-tabs {
+  position: relative;
+}
+
+.nav-tabs .nav-more {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 42px;
+  line-height: 42px;
+  color: #666;
+}
+
+#tabs {
+  border-bottom: 1px solid #eee;
+}
+
+#tabs li {
+  cursor: pointer;
+  width: 100px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 16px;
+  border-bottom: 2px solid transparent;
+  position: relative;
+  z-index: 1;
+  margin-bottom: -1px;
+  color: #666;
+}
+
+
+#tabs .active {
+  border-bottom-color: #f00;
+  color: #222;
+}
+
+.tab-container .content {
+  display: none;
+}
+
+/* 页面布局 */
+.main {
+  padding: 30px 100px;
+  width: 960px;
+  margin: 0 auto;
+}
+
+.main .logo {
+  color: #333;
+  text-align: left;
+  margin-bottom: 30px;
+  line-height: 1;
+  height: 110px;
+  margin-top: -50px;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.main .logo a {
+  font-size: 160px;
+  color: #333;
+}
+
+.helps {
+  margin-top: 40px;
+}
+
+.helps pre {
+  padding: 20px;
+  margin: 10px 0;
+  border: solid 1px #e7e1cd;
+  background-color: #fffdef;
+  overflow: auto;
+}
+
+.icon_lists {
+  width: 100% !important;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.icon_lists li {
+  width: 100px;
+  margin-bottom: 10px;
+  margin-right: 20px;
+  text-align: center;
+  list-style: none !important;
+  cursor: default;
+}
+
+.icon_lists li .code-name {
+  line-height: 1.2;
+}
+
+.icon_lists .icon {
+  display: block;
+  height: 100px;
+  line-height: 100px;
+  font-size: 42px;
+  margin: 10px auto;
+  color: #333;
+  -webkit-transition: font-size 0.25s linear, width 0.25s linear;
+  -moz-transition: font-size 0.25s linear, width 0.25s linear;
+  transition: font-size 0.25s linear, width 0.25s linear;
+}
+
+.icon_lists .icon:hover {
+  font-size: 100px;
+}
+
+.icon_lists .svg-icon {
+  /* 通过设置 font-size 来改变图标大小 */
+  width: 1em;
+  /* 图标和文字相邻时,垂直对齐 */
+  vertical-align: -0.15em;
+  /* 通过设置 color 来改变 SVG 的颜色/fill */
+  fill: currentColor;
+  /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
+      normalize.css 中也包含这行 */
+  overflow: hidden;
+}
+
+.icon_lists li .name,
+.icon_lists li .code-name {
+  color: #666;
+}
+
+/* markdown 样式 */
+.markdown {
+  color: #666;
+  font-size: 14px;
+  line-height: 1.8;
+}
+
+.highlight {
+  line-height: 1.5;
+}
+
+.markdown img {
+  vertical-align: middle;
+  max-width: 100%;
+}
+
+.markdown h1 {
+  color: #404040;
+  font-weight: 500;
+  line-height: 40px;
+  margin-bottom: 24px;
+}
+
+.markdown h2,
+.markdown h3,
+.markdown h4,
+.markdown h5,
+.markdown h6 {
+  color: #404040;
+  margin: 1.6em 0 0.6em 0;
+  font-weight: 500;
+  clear: both;
+}
+
+.markdown h1 {
+  font-size: 28px;
+}
+
+.markdown h2 {
+  font-size: 22px;
+}
+
+.markdown h3 {
+  font-size: 16px;
+}
+
+.markdown h4 {
+  font-size: 14px;
+}
+
+.markdown h5 {
+  font-size: 12px;
+}
+
+.markdown h6 {
+  font-size: 12px;
+}
+
+.markdown hr {
+  height: 1px;
+  border: 0;
+  background: #e9e9e9;
+  margin: 16px 0;
+  clear: both;
+}
+
+.markdown p {
+  margin: 1em 0;
+}
+
+.markdown>p,
+.markdown>blockquote,
+.markdown>.highlight,
+.markdown>ol,
+.markdown>ul {
+  width: 80%;
+}
+
+.markdown ul>li {
+  list-style: circle;
+}
+
+.markdown>ul li,
+.markdown blockquote ul>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown>ul li p,
+.markdown>ol li p {
+  margin: 0.6em 0;
+}
+
+.markdown ol>li {
+  list-style: decimal;
+}
+
+.markdown>ol li,
+.markdown blockquote ol>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown code {
+  margin: 0 3px;
+  padding: 0 5px;
+  background: #eee;
+  border-radius: 3px;
+}
+
+.markdown strong,
+.markdown b {
+  font-weight: 600;
+}
+
+.markdown>table {
+  border-collapse: collapse;
+  border-spacing: 0px;
+  empty-cells: show;
+  border: 1px solid #e9e9e9;
+  width: 95%;
+  margin-bottom: 24px;
+}
+
+.markdown>table th {
+  white-space: nowrap;
+  color: #333;
+  font-weight: 600;
+}
+
+.markdown>table th,
+.markdown>table td {
+  border: 1px solid #e9e9e9;
+  padding: 8px 16px;
+  text-align: left;
+}
+
+.markdown>table th {
+  background: #F7F7F7;
+}
+
+.markdown blockquote {
+  font-size: 90%;
+  color: #999;
+  border-left: 4px solid #e9e9e9;
+  padding-left: 0.8em;
+  margin: 1em 0;
+}
+
+.markdown blockquote p {
+  margin: 0;
+}
+
+.markdown .anchor {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  margin-left: 8px;
+}
+
+.markdown .waiting {
+  color: #ccc;
+}
+
+.markdown h1:hover .anchor,
+.markdown h2:hover .anchor,
+.markdown h3:hover .anchor,
+.markdown h4:hover .anchor,
+.markdown h5:hover .anchor,
+.markdown h6:hover .anchor {
+  opacity: 1;
+  display: inline-block;
+}
+
+.markdown>br,
+.markdown>p>br {
+  clear: both;
+}
+
+
+.hljs {
+  display: block;
+  background: white;
+  padding: 0.5em;
+  color: #333333;
+  overflow-x: auto;
+}
+
+.hljs-comment,
+.hljs-meta {
+  color: #969896;
+}
+
+.hljs-string,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-strong,
+.hljs-emphasis,
+.hljs-quote {
+  color: #df5000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-type {
+  color: #a71d5d;
+}
+
+.hljs-literal,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-attribute {
+  color: #0086b3;
+}
+
+.hljs-section,
+.hljs-name {
+  color: #63a35c;
+}
+
+.hljs-tag {
+  color: #333333;
+}
+
+.hljs-title,
+.hljs-attr,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+  color: #795da3;
+}
+
+.hljs-addition {
+  color: #55a532;
+  background-color: #eaffea;
+}
+
+.hljs-deletion {
+  color: #bd2c00;
+  background-color: #ffecec;
+}
+
+.hljs-link {
+  text-decoration: underline;
+}
+
+/* 代码高亮 */
+/* PrismJS 1.15.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  background: none;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre)>code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+  white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #9a6e3a;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function,
+.token.class-name {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}

文件差异内容过多而无法显示
+ 1936 - 0
src/assets/icon/fuse/demo_index.html


+ 319 - 0
src/assets/icon/fuse/iconfont.css

@@ -0,0 +1,319 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 3549513 */
+  src: url('iconfont.woff2?t=1722820936866') format('woff2'),
+       url('iconfont.woff?t=1722820936866') format('woff'),
+       url('iconfont.ttf?t=1722820936866') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-index:before {
+  content: "\e76d";
+}
+
+.icon-symbol:before {
+  content: "\e76e";
+}
+
+.icon-arrows1:before {
+  content: "\e76f";
+}
+
+.icon-text1:before {
+  content: "\e770";
+}
+
+.icon-fire_firmware:before {
+  content: "\e767";
+}
+
+.icon-fire_app:before {
+  content: "\e768";
+}
+
+.icon-fire_statistics:before {
+  content: "\e74d";
+}
+
+.icon-query_home:before {
+  content: "\e74b";
+}
+
+.icon-list-detail:before {
+  content: "\e740";
+}
+
+.icon-without:before {
+  content: "\e70e";
+}
+
+.icon-keyboard:before {
+  content: "\e70d";
+}
+
+.icon-recover:before {
+  content: "\e693";
+}
+
+.icon-repeal:before {
+  content: "\e694";
+}
+
+.icon-broken_l:before {
+  content: "\e6fd";
+}
+
+.icon-arrows:before {
+  content: "\e6fe";
+}
+
+.icon-blood:before {
+  content: "\e6ff";
+}
+
+.icon-circle:before {
+  content: "\e700";
+}
+
+.icon-cigarette_e:before {
+  content: "\e701";
+}
+
+.icon-corpse:before {
+  content: "\e702";
+}
+
+.icon-icon_n:before {
+  content: "\e703";
+}
+
+.icon-form:before {
+  content: "\e704";
+}
+
+.icon-footprint_l:before {
+  content: "\e705";
+}
+
+.icon-footprint_r:before {
+  content: "\e706";
+}
+
+.icon-fire_p:before {
+  content: "\e707";
+}
+
+.icon-rectangle:before {
+  content: "\e708";
+}
+
+.icon-shoeprints_l:before {
+  content: "\e709";
+}
+
+.icon-text:before {
+  content: "\e70a";
+}
+
+.icon-fingerprint:before {
+  content: "\e70b";
+}
+
+.icon-shoeprints_r:before {
+  content: "\e70c";
+}
+
+.icon-nav-setup:before {
+  content: "\e64b";
+}
+
+.icon-a-film:before {
+  content: "\e6e8";
+}
+
+.icon-nav-edit:before {
+  content: "\e642";
+}
+
+.icon-pic:before {
+  content: "\e648";
+}
+
+.icon-list-scene:before {
+  content: "\e6e4";
+}
+
+.icon-list-file:before {
+  content: "\e6e5";
+}
+
+.icon-list-record:before {
+  content: "\e6e6";
+}
+
+.icon-list-view:before {
+  content: "\e6e7";
+}
+
+.icon-video1:before {
+  content: "\e63b";
+}
+
+.icon-order:before {
+  content: "\e6dd";
+}
+
+.icon-pin1:before {
+  content: "\e6e3";
+}
+
+.icon-nav-measure:before {
+  content: "\e64a";
+}
+
+.icon-v-l:before {
+  content: "\e66f";
+}
+
+.icon-h-r:before {
+  content: "\e670";
+}
+
+.icon-f-l:before {
+  content: "\e673";
+}
+
+.icon-search:before {
+  content: "\e64c";
+}
+
+.icon-left1:before {
+  content: "\e6ae";
+}
+
+.icon-right:before {
+  content: "\e6af";
+}
+
+.icon-state_e:before {
+  content: "\e624";
+}
+
+.icon-state_f:before {
+  content: "\e625";
+}
+
+.icon-state_s:before {
+  content: "\e626";
+}
+
+.icon-eye-n:before {
+  content: "\e621";
+}
+
+.icon-eye-s:before {
+  content: "\e622";
+}
+
+.icon-more:before {
+  content: "\e600";
+}
+
+.icon-element:before {
+  content: "\e666";
+}
+
+.icon-extend:before {
+  content: "\e690";
+}
+
+.icon-shrink:before {
+  content: "\e691";
+}
+
+.icon-pause:before {
+  content: "\e636";
+}
+
+.icon-preview:before {
+  content: "\e63a";
+}
+
+.icon-clear:before {
+  content: "\e63f";
+}
+
+.icon-play_stop:before {
+  content: "\e6b4";
+}
+
+.icon-transparency:before {
+  content: "\e6d7";
+}
+
+.icon-pull-down:before {
+  content: "\e61d";
+}
+
+.icon-pull-up:before {
+  content: "\e61e";
+}
+
+.icon-add:before {
+  content: "\e631";
+}
+
+.icon-close:before {
+  content: "\e633";
+}
+
+.icon-pin:before {
+  content: "\e67c";
+}
+
+.icon-flip:before {
+  content: "\e67e";
+}
+
+.icon-move:before {
+  content: "\e680";
+}
+
+.icon-del:before {
+  content: "\e632";
+}
+
+.icon-checkbox:before {
+  content: "\e649";
+}
+
+.icon-nor:before {
+  content: "\e696";
+}
+
+.icon-joint:before {
+  content: "\e6e0";
+}
+
+.icon-path:before {
+  content: "\e6e1";
+}
+
+.icon-label:before {
+  content: "\e6e2";
+}
+
+.icon-case:before {
+  content: "\e6da";
+}
+
+.icon-scene:before {
+  content: "\e6db";
+}
+

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


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

@@ -0,0 +1,541 @@
+{
+  "id": "3549513",
+  "name": "融合平台",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "41299621",
+      "name": "index",
+      "font_class": "index",
+      "unicode": "e76d",
+      "unicode_decimal": 59245
+    },
+    {
+      "icon_id": "41299620",
+      "name": "symbol",
+      "font_class": "symbol",
+      "unicode": "e76e",
+      "unicode_decimal": 59246
+    },
+    {
+      "icon_id": "41299619",
+      "name": "arrows",
+      "font_class": "arrows1",
+      "unicode": "e76f",
+      "unicode_decimal": 59247
+    },
+    {
+      "icon_id": "41299618",
+      "name": "text",
+      "font_class": "text1",
+      "unicode": "e770",
+      "unicode_decimal": 59248
+    },
+    {
+      "icon_id": "41207891",
+      "name": "fire_firmware",
+      "font_class": "fire_firmware",
+      "unicode": "e767",
+      "unicode_decimal": 59239
+    },
+    {
+      "icon_id": "41207890",
+      "name": "fire_app",
+      "font_class": "fire_app",
+      "unicode": "e768",
+      "unicode_decimal": 59240
+    },
+    {
+      "icon_id": "39135106",
+      "name": "fire_statistics",
+      "font_class": "fire_statistics",
+      "unicode": "e74d",
+      "unicode_decimal": 59213
+    },
+    {
+      "icon_id": "38948724",
+      "name": "query_home",
+      "font_class": "query_home",
+      "unicode": "e74b",
+      "unicode_decimal": 59211
+    },
+    {
+      "icon_id": "37076167",
+      "name": "list-detail",
+      "font_class": "list-detail",
+      "unicode": "e740",
+      "unicode_decimal": 59200
+    },
+    {
+      "icon_id": "33692561",
+      "name": "without",
+      "font_class": "without",
+      "unicode": "e70e",
+      "unicode_decimal": 59150
+    },
+    {
+      "icon_id": "33322597",
+      "name": "keyboard",
+      "font_class": "keyboard",
+      "unicode": "e70d",
+      "unicode_decimal": 59149
+    },
+    {
+      "icon_id": "27198776",
+      "name": "recover",
+      "font_class": "recover",
+      "unicode": "e693",
+      "unicode_decimal": 59027
+    },
+    {
+      "icon_id": "27198777",
+      "name": "repeal",
+      "font_class": "repeal",
+      "unicode": "e694",
+      "unicode_decimal": 59028
+    },
+    {
+      "icon_id": "33292114",
+      "name": "broken_l",
+      "font_class": "broken_l",
+      "unicode": "e6fd",
+      "unicode_decimal": 59133
+    },
+    {
+      "icon_id": "33292115",
+      "name": "arrows",
+      "font_class": "arrows",
+      "unicode": "e6fe",
+      "unicode_decimal": 59134
+    },
+    {
+      "icon_id": "33292116",
+      "name": "blood",
+      "font_class": "blood",
+      "unicode": "e6ff",
+      "unicode_decimal": 59135
+    },
+    {
+      "icon_id": "33292117",
+      "name": "circle",
+      "font_class": "circle",
+      "unicode": "e700",
+      "unicode_decimal": 59136
+    },
+    {
+      "icon_id": "33292118",
+      "name": "cigarette_e",
+      "font_class": "cigarette_e",
+      "unicode": "e701",
+      "unicode_decimal": 59137
+    },
+    {
+      "icon_id": "33292119",
+      "name": "corpse",
+      "font_class": "corpse",
+      "unicode": "e702",
+      "unicode_decimal": 59138
+    },
+    {
+      "icon_id": "33292120",
+      "name": "icon_n",
+      "font_class": "icon_n",
+      "unicode": "e703",
+      "unicode_decimal": 59139
+    },
+    {
+      "icon_id": "33292121",
+      "name": "form",
+      "font_class": "form",
+      "unicode": "e704",
+      "unicode_decimal": 59140
+    },
+    {
+      "icon_id": "33292122",
+      "name": "footprint_l",
+      "font_class": "footprint_l",
+      "unicode": "e705",
+      "unicode_decimal": 59141
+    },
+    {
+      "icon_id": "33292123",
+      "name": "footprint_r",
+      "font_class": "footprint_r",
+      "unicode": "e706",
+      "unicode_decimal": 59142
+    },
+    {
+      "icon_id": "33292124",
+      "name": "fire_p",
+      "font_class": "fire_p",
+      "unicode": "e707",
+      "unicode_decimal": 59143
+    },
+    {
+      "icon_id": "33292125",
+      "name": "rectangle",
+      "font_class": "rectangle",
+      "unicode": "e708",
+      "unicode_decimal": 59144
+    },
+    {
+      "icon_id": "33292126",
+      "name": "shoeprints_l",
+      "font_class": "shoeprints_l",
+      "unicode": "e709",
+      "unicode_decimal": 59145
+    },
+    {
+      "icon_id": "33292127",
+      "name": "text",
+      "font_class": "text",
+      "unicode": "e70a",
+      "unicode_decimal": 59146
+    },
+    {
+      "icon_id": "33292128",
+      "name": "fingerprint",
+      "font_class": "fingerprint",
+      "unicode": "e70b",
+      "unicode_decimal": 59147
+    },
+    {
+      "icon_id": "33292129",
+      "name": "shoeprints_r",
+      "font_class": "shoeprints_r",
+      "unicode": "e70c",
+      "unicode_decimal": 59148
+    },
+    {
+      "icon_id": "25631133",
+      "name": "nav-setup",
+      "font_class": "nav-setup",
+      "unicode": "e64b",
+      "unicode_decimal": 58955
+    },
+    {
+      "icon_id": "31503933",
+      "name": "a-film",
+      "font_class": "a-film",
+      "unicode": "e6e8",
+      "unicode_decimal": 59112
+    },
+    {
+      "icon_id": "25631122",
+      "name": "nav-edit",
+      "font_class": "nav-edit",
+      "unicode": "e642",
+      "unicode_decimal": 58946
+    },
+    {
+      "icon_id": "23786363",
+      "name": "pic",
+      "font_class": "pic",
+      "unicode": "e648",
+      "unicode_decimal": 58952
+    },
+    {
+      "icon_id": "31485449",
+      "name": "list-scene",
+      "font_class": "list-scene",
+      "unicode": "e6e4",
+      "unicode_decimal": 59108
+    },
+    {
+      "icon_id": "31485450",
+      "name": "list-file",
+      "font_class": "list-file",
+      "unicode": "e6e5",
+      "unicode_decimal": 59109
+    },
+    {
+      "icon_id": "31485451",
+      "name": "list-record",
+      "font_class": "list-record",
+      "unicode": "e6e6",
+      "unicode_decimal": 59110
+    },
+    {
+      "icon_id": "31485452",
+      "name": "list-view",
+      "font_class": "list-view",
+      "unicode": "e6e7",
+      "unicode_decimal": 59111
+    },
+    {
+      "icon_id": "23781429",
+      "name": "video",
+      "font_class": "video1",
+      "unicode": "e63b",
+      "unicode_decimal": 58939
+    },
+    {
+      "icon_id": "30948192",
+      "name": "order",
+      "font_class": "order",
+      "unicode": "e6dd",
+      "unicode_decimal": 59101
+    },
+    {
+      "icon_id": "31365069",
+      "name": "pin",
+      "font_class": "pin1",
+      "unicode": "e6e3",
+      "unicode_decimal": 59107
+    },
+    {
+      "icon_id": "25631400",
+      "name": "nav-measure",
+      "font_class": "nav-measure",
+      "unicode": "e64a",
+      "unicode_decimal": 58954
+    },
+    {
+      "icon_id": "26077352",
+      "name": "v-l",
+      "font_class": "v-l",
+      "unicode": "e66f",
+      "unicode_decimal": 58991
+    },
+    {
+      "icon_id": "26077353",
+      "name": "h-r",
+      "font_class": "h-r",
+      "unicode": "e670",
+      "unicode_decimal": 58992
+    },
+    {
+      "icon_id": "26077356",
+      "name": "f-l",
+      "font_class": "f-l",
+      "unicode": "e673",
+      "unicode_decimal": 58995
+    },
+    {
+      "icon_id": "25631464",
+      "name": "search",
+      "font_class": "search",
+      "unicode": "e64c",
+      "unicode_decimal": 58956
+    },
+    {
+      "icon_id": "27765016",
+      "name": "left",
+      "font_class": "left1",
+      "unicode": "e6ae",
+      "unicode_decimal": 59054
+    },
+    {
+      "icon_id": "27765017",
+      "name": "right",
+      "font_class": "right",
+      "unicode": "e6af",
+      "unicode_decimal": 59055
+    },
+    {
+      "icon_id": "22132762",
+      "name": "state_e",
+      "font_class": "state_e",
+      "unicode": "e624",
+      "unicode_decimal": 58916
+    },
+    {
+      "icon_id": "22132763",
+      "name": "state_f",
+      "font_class": "state_f",
+      "unicode": "e625",
+      "unicode_decimal": 58917
+    },
+    {
+      "icon_id": "22132764",
+      "name": "state_s",
+      "font_class": "state_s",
+      "unicode": "e626",
+      "unicode_decimal": 58918
+    },
+    {
+      "icon_id": "22099675",
+      "name": "eye-n",
+      "font_class": "eye-n",
+      "unicode": "e621",
+      "unicode_decimal": 58913
+    },
+    {
+      "icon_id": "22099676",
+      "name": "eye-s",
+      "font_class": "eye-s",
+      "unicode": "e622",
+      "unicode_decimal": 58914
+    },
+    {
+      "icon_id": "11304931",
+      "name": "more read",
+      "font_class": "more",
+      "unicode": "e600",
+      "unicode_decimal": 58880
+    },
+    {
+      "icon_id": "25764812",
+      "name": "element",
+      "font_class": "element",
+      "unicode": "e666",
+      "unicode_decimal": 58982
+    },
+    {
+      "icon_id": "27032625",
+      "name": "extend",
+      "font_class": "extend",
+      "unicode": "e690",
+      "unicode_decimal": 59024
+    },
+    {
+      "icon_id": "27032630",
+      "name": "shrink",
+      "font_class": "shrink",
+      "unicode": "e691",
+      "unicode_decimal": 59025
+    },
+    {
+      "icon_id": "23773343",
+      "name": "pause",
+      "font_class": "pause",
+      "unicode": "e636",
+      "unicode_decimal": 58934
+    },
+    {
+      "icon_id": "23773344",
+      "name": "preview",
+      "font_class": "preview",
+      "unicode": "e63a",
+      "unicode_decimal": 58938
+    },
+    {
+      "icon_id": "23781433",
+      "name": "clear",
+      "font_class": "clear",
+      "unicode": "e63f",
+      "unicode_decimal": 58943
+    },
+    {
+      "icon_id": "29255507",
+      "name": "play_stop",
+      "font_class": "play_stop",
+      "unicode": "e6b4",
+      "unicode_decimal": 59060
+    },
+    {
+      "icon_id": "30499411",
+      "name": "transparency",
+      "font_class": "transparency",
+      "unicode": "e6d7",
+      "unicode_decimal": 59095
+    },
+    {
+      "icon_id": "22099518",
+      "name": "pull-down",
+      "font_class": "pull-down",
+      "unicode": "e61d",
+      "unicode_decimal": 58909
+    },
+    {
+      "icon_id": "22099519",
+      "name": "pull-up",
+      "font_class": "pull-up",
+      "unicode": "e61e",
+      "unicode_decimal": 58910
+    },
+    {
+      "icon_id": "23773068",
+      "name": "add",
+      "font_class": "add",
+      "unicode": "e631",
+      "unicode_decimal": 58929
+    },
+    {
+      "icon_id": "23773070",
+      "name": "close",
+      "font_class": "close",
+      "unicode": "e633",
+      "unicode_decimal": 58931
+    },
+    {
+      "icon_id": "26621105",
+      "name": "pin",
+      "font_class": "pin",
+      "unicode": "e67c",
+      "unicode_decimal": 59004
+    },
+    {
+      "icon_id": "26625827",
+      "name": "flip",
+      "font_class": "flip",
+      "unicode": "e67e",
+      "unicode_decimal": 59006
+    },
+    {
+      "icon_id": "26625859",
+      "name": "move",
+      "font_class": "move",
+      "unicode": "e680",
+      "unicode_decimal": 59008
+    },
+    {
+      "icon_id": "23773069",
+      "name": "del",
+      "font_class": "del",
+      "unicode": "e632",
+      "unicode_decimal": 58930
+    },
+    {
+      "icon_id": "23842269",
+      "name": "sel",
+      "font_class": "checkbox",
+      "unicode": "e649",
+      "unicode_decimal": 58953
+    },
+    {
+      "icon_id": "27200779",
+      "name": "nor",
+      "font_class": "nor",
+      "unicode": "e696",
+      "unicode_decimal": 59030
+    },
+    {
+      "icon_id": "31132312",
+      "name": "joint",
+      "font_class": "joint",
+      "unicode": "e6e0",
+      "unicode_decimal": 59104
+    },
+    {
+      "icon_id": "31132318",
+      "name": "path",
+      "font_class": "path",
+      "unicode": "e6e1",
+      "unicode_decimal": 59105
+    },
+    {
+      "icon_id": "31132319",
+      "name": "label",
+      "font_class": "label",
+      "unicode": "e6e2",
+      "unicode_decimal": 59106
+    },
+    {
+      "icon_id": "30885164",
+      "name": "case",
+      "font_class": "case",
+      "unicode": "e6da",
+      "unicode_decimal": 59098
+    },
+    {
+      "icon_id": "30885165",
+      "name": "scene",
+      "font_class": "scene",
+      "unicode": "e6db",
+      "unicode_decimal": 59099
+    }
+  ]
+}

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


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


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


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


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

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

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

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

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


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


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


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


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


二进制
src/assets/image/decoration_collect@2x.png


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


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


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


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


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


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


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


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


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


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


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


二进制
src/assets/image/jmfirebg@2x.png


+ 0 - 0
src/assets/image/jmlogo.png


部分文件因为文件数量过多而无法显示