Sfoglia il codice sorgente

Merge branch 'staffNoFix' of ssh://172.16.23.188:9022/tian-group/skyeye-admin-fe into staffNoFix

zhudie 2 anni fa
parent
commit
6146230f48
52 ha cambiato i file con 755 aggiunte e 2115 eliminazioni
  1. 1 4
      .env.development
  2. 1 1
      .env.production
  3. 1 1
      .env.test
  4. 1 0
      .gitignore
  5. 13 0
      CHANGELOG.md
  6. 35 21
      build/vite/plugin/html.ts
  7. 120 104
      index.html
  8. 5 5
      mock/_util.ts
  9. 4 2
      mock/system/dictionary.ts
  10. 5 9
      package.json
  11. 1 0
      postcss.config.js
  12. BIN
      public/upload-user-templete/templete.xlsx
  13. 16 3
      src/components/Form/src/BasicForm.vue
  14. 8 1
      src/components/Form/src/types/form.ts
  15. 28 17
      src/hooks/web/useECharts.ts
  16. 2 2
      src/hooks/web/useTags.ts
  17. 2 0
      src/main.ts
  18. 1 1
      src/utils/http/axios/index.ts
  19. 39 34
      src/views/cameras/preview/components/CameraViewSetting/CameraViewSetting.vue
  20. 0 15
      src/views/cameras/preview/components/FenceEditor/FenceEditor.vue
  21. 258 0
      src/views/cameras/preview/components/FenceEditorV2/FenceEditor.vue
  22. 55 0
      src/views/cameras/preview/components/FenceEditorV2/FenceItem.vue
  23. 17 0
      src/views/cameras/preview/components/FenceEditorV2/constants.ts
  24. 17 0
      src/views/cameras/preview/components/FenceEditorV2/types.ts
  25. 41 0
      src/views/cameras/preview/components/FenceEditorV2/utils.ts
  26. 8 0
      src/views/comp/form/basic.vue
  27. 0 1
      src/views/list/basicList/index.vue
  28. 0 71
      src/views/map-config/mini-map/MapBase/CameraGroup-bak.ts
  29. 0 88
      src/views/map-config/mini-map/MapBase/CameraGroup.ts
  30. 0 216
      src/views/map-config/mini-map/MapBase/CameraMapBak.ts
  31. 0 73
      src/views/map-config/mini-map/MapBase/CameraPreview.vue
  32. 0 68
      src/views/map-config/mini-map/MapBase/CameraStarGroup.ts
  33. 0 21
      src/views/map-config/mini-map/MapBase/DefaultCameraIcon.vue
  34. 14 33
      src/views/map-config/mini-map/MapBase/KonvaMap.vue
  35. 0 58
      src/views/map-config/mini-map/MapBase/fabricSetting.ts
  36. 0 42
      src/views/map-config/mini-map/MapBase/mapData.json
  37. 0 40
      src/views/map-config/mini-map/MapBase/types.ts
  38. 0 249
      src/views/map-config/mini-map/MapBase/useCameraMap.ts
  39. 0 22
      src/views/map-config/mini-map/MapBase/utils.ts
  40. 0 319
      src/views/map-config/mini-map/MiniMapConfig copy.vue
  41. 3 11
      src/views/map-config/mini-map/MiniMapConfig.vue
  42. 0 52
      src/views/map-config/mini-map/components/CameraOptBar.vue
  43. 0 33
      src/views/map-config/mini-map/components/EditDimension.vue
  44. 0 52
      src/views/map-config/mini-map/components/SelectedCameraToolbar.vue
  45. 0 436
      src/views/map-config/mini-map/hooks/useMapEditor.ts
  46. 21 0
      src/views/map-config/mini-map/type.ts
  47. 6 6
      src/views/system/tenant/CreateDrawer.vue
  48. 2 2
      src/views/system/tenant/types/index.ts
  49. 5 1
      tsconfig.json
  50. 17 1
      types/config.d.ts
  51. 7 0
      types/global.d.ts
  52. 1 0
      vite.config.ts

+ 1 - 4
.env.development

@@ -7,9 +7,6 @@ VITE_PUBLIC_PATH = /skyeye-admin/
 # 是否开启mock
 VITE_USE_MOCK = false
 
-# 网站前缀
-VITE_BASE_URL = /
-
 # 是否删除console
 VITE_DROP_CONSOLE = true
 
@@ -22,7 +19,7 @@ VITE_PROXY=[["/skyeye-admin-api","http://192.168.1.102:8800/api"]]
 # API 接口地址
 VITE_GLOB_API_URL = 
 # 图片上传地址
-VITE_GLOB_UPLOAD_URL= http://172.16.23.144:8086
+VITE_GLOB_UPLOAD_URL=  
 
 # 图片前缀地址
 VITE_GLOB_IMG_URL = //192.168.1.102/skyeye_static/

+ 1 - 1
.env.production

@@ -12,7 +12,7 @@ VITE_DROP_CONSOLE = true
 VITE_GLOB_API_URL = 
 
 # 图片上传地址
-VITE_GLOB_UPLOAD_URL= http://172.16.23.144:8086
+VITE_GLOB_UPLOAD_URL= 
 
 # 图片前缀地址
 VITE_GLOB_IMG_URL= /skyeye_static

+ 1 - 1
.env.test

@@ -2,7 +2,7 @@
 VITE_USE_MOCK = true
 
 # 网站根目录
-VITE_PUBLIC_PATH = /
+VITE_PUBLIC_PATH = /cloud-admin/
 
 
 # 是否删除console

+ 1 - 0
.gitignore

@@ -24,3 +24,4 @@ pnpm-debug.log*
 *.sln
 *.sw?
 /components.d.ts
+pnpm-lock.yaml

+ 13 - 0
CHANGELOG.md

@@ -2,6 +2,19 @@
 
 ## Pending
 
+## 1.4.0 (2023-11-25)
+
+- 🌟 新增 `BasicForm.schemas` 支持 `hidden`,可配置成函数,示例:组件示例-表单-基础使用
+- 💎 优化 `useECharts` 方法,考虑菜单收起宽度变化
+- `依赖升级`
+
+## 1.3.9 (2023-07-26)
+
+- 🐞 修复 `mock` 配置异常
+- 🐞 修复 `GlobConfig` 类型缺失
+- 💎 优化 `ImportMeta` 类型定义
+- `依赖升级`
+
 ## 1.3.8 (2023-06-12)
 
 - 🐞 修复 `修改密码异常`

+ 35 - 21
build/vite/plugin/html.ts

@@ -6,8 +6,17 @@ import type { PluginOption } from 'vite';
 import { createHtmlPlugin } from 'vite-plugin-html';
 import pkg from '../../../package.json';
 import { GLOB_CONFIG_FILE_NAME } from '../../constant';
+import { simpleGit } from 'simple-git';
+const git = simpleGit();
 
-export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
+async function getLatestHash() {
+  const gitLog = await git.log();
+  /** 截取git hash值最后8位 */
+  const lastStrNum = 8;
+  return gitLog.latest?.hash.substring(-lastStrNum, lastStrNum);
+}
+
+export function configHtmlPlugin(env: ViteEnv, isBuild: boolean): Promise<PluginOption[]> {
   const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
 
   const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
@@ -16,25 +25,30 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
     return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
   };
 
-  const htmlPlugin: PluginOption[] = createHtmlPlugin({
-    minify: isBuild,
-    inject: {
-      // Inject data into ejs template
-      data: {
-        title: VITE_GLOB_APP_TITLE,
-      },
-      // Embed the generated app.config.js file
-      tags: isBuild
-        ? [
-            {
-              tag: 'script',
-              attrs: {
-                src: getAppConfigSrc(),
-              },
-            },
-          ]
-        : [],
-    },
+  return new Promise((resolve) => {
+    getLatestHash().then((hash) => {
+      const htmlPlugin: PluginOption[] = createHtmlPlugin({
+        minify: isBuild,
+        inject: {
+          // Inject data into ejs template
+          data: {
+            title: VITE_GLOB_APP_TITLE,
+            hash,
+          },
+          // Embed the generated app.config.js file
+          tags: isBuild
+            ? [
+                {
+                  tag: 'script',
+                  attrs: {
+                    src: getAppConfigSrc(),
+                  },
+                },
+              ]
+            : [],
+        },
+      });
+      resolve(htmlPlugin);
+    });
   });
-  return htmlPlugin;
 }

+ 120 - 104
index.html

@@ -1,124 +1,140 @@
 <!DOCTYPE html>
 <html lang="zh-cmn-Hans" id="htmlRoot" data-theme="light">
-  <head>
-    <meta charset="UTF-8" />
-    <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
-    <meta content="webkit" name="renderer" />
-    <meta
-      content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
-      name="viewport"
-    />
-    <link href="/favicon.ico" rel="icon" />
-    <title><%= title %></title>
-  </head>
-  <body>
-    <div id="app">
-      <style>
-        .first-loading-wrap {
-          display: flex;
-          width: 100%;
-          height: 100vh;
-          justify-content: center;
-          align-items: center;
-          flex-direction: column;
-        }
 
-        .first-loading-wrap > h1 {
-          font-size: 128px;
-        }
+<head>
+  <meta charset="UTF-8" />
+  <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
+  <meta content="webkit" name="renderer" />
+  <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
+    name="viewport" />
+  <link href="/favicon.ico" rel="icon" />
+  <title>
+    <%= title %>
+  </title>
+</head>
 
-        .first-loading-wrap .loading-wrap {
-          padding: 98px;
-          display: flex;
-          justify-content: center;
-          align-items: center;
-        }
+<body>
+  <div id="app">
+    <style>
+      .first-loading-wrap {
+        display: flex;
+        width: 100%;
+        height: 100vh;
+        justify-content: center;
+        align-items: center;
+        flex-direction: column;
+      }
 
-        .dot {
-          animation: antRotate 1.2s infinite linear;
-          transform: rotate(45deg);
-          position: relative;
-          display: inline-block;
-          font-size: 32px;
-          width: 32px;
-          height: 32px;
-          box-sizing: border-box;
-        }
+      .first-loading-wrap>h1 {
+        font-size: 128px;
+      }
 
-        .dot i {
-          width: 14px;
-          height: 14px;
-          position: absolute;
-          display: block;
-          background-color: #1890ff;
-          border-radius: 100%;
-          transform: scale(0.75);
-          transform-origin: 50% 50%;
-          opacity: 0.3;
-          animation: antSpinMove 1s infinite linear alternate;
-        }
+      .first-loading-wrap .loading-wrap {
+        padding: 98px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
 
-        .dot i:nth-child(1) {
-          top: 0;
-          left: 0;
-        }
+      .dot {
+        animation: antRotate 1.2s infinite linear;
+        transform: rotate(45deg);
+        position: relative;
+        display: inline-block;
+        font-size: 32px;
+        width: 32px;
+        height: 32px;
+        box-sizing: border-box;
+      }
 
-        .dot i:nth-child(2) {
-          top: 0;
-          right: 0;
-          -webkit-animation-delay: 0.4s;
-          animation-delay: 0.4s;
-        }
+      .dot i {
+        width: 14px;
+        height: 14px;
+        position: absolute;
+        display: block;
+        background-color: #1890ff;
+        border-radius: 100%;
+        transform: scale(0.75);
+        transform-origin: 50% 50%;
+        opacity: 0.3;
+        animation: antSpinMove 1s infinite linear alternate;
+      }
 
-        .dot i:nth-child(3) {
-          right: 0;
-          bottom: 0;
-          -webkit-animation-delay: 0.8s;
-          animation-delay: 0.8s;
-        }
+      .dot i:nth-child(1) {
+        top: 0;
+        left: 0;
+      }
 
-        .dot i:nth-child(4) {
-          bottom: 0;
-          left: 0;
-          -webkit-animation-delay: 1.2s;
-          animation-delay: 1.2s;
-        }
+      .dot i:nth-child(2) {
+        top: 0;
+        right: 0;
+        -webkit-animation-delay: 0.4s;
+        animation-delay: 0.4s;
+      }
 
-        @keyframes antRotate {
-          to {
-            -webkit-transform: rotate(405deg);
-            transform: rotate(405deg);
-          }
+      .dot i:nth-child(3) {
+        right: 0;
+        bottom: 0;
+        -webkit-animation-delay: 0.8s;
+        animation-delay: 0.8s;
+      }
+
+      .dot i:nth-child(4) {
+        bottom: 0;
+        left: 0;
+        -webkit-animation-delay: 1.2s;
+        animation-delay: 1.2s;
+      }
+
+      @keyframes antRotate {
+        to {
+          -webkit-transform: rotate(405deg);
+          transform: rotate(405deg);
         }
+      }
 
-        @-webkit-keyframes antRotate {
-          to {
-            -webkit-transform: rotate(405deg);
-            transform: rotate(405deg);
-          }
+      @-webkit-keyframes antRotate {
+        to {
+          -webkit-transform: rotate(405deg);
+          transform: rotate(405deg);
         }
+      }
 
-        @keyframes antSpinMove {
-          to {
-            opacity: 1;
-          }
+      @keyframes antSpinMove {
+        to {
+          opacity: 1;
         }
+      }
 
-        @-webkit-keyframes antSpinMove {
-          to {
-            opacity: 1;
-          }
+      @-webkit-keyframes antSpinMove {
+        to {
+          opacity: 1;
         }
-      </style>
-      <div class="first-loading-wrap">
-        <div class="loading-wrap">
-          <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
-        </div>
+      }
+    </style>
+    <div class="first-loading-wrap">
+      <div class="loading-wrap">
+        <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
       </div>
     </div>
-    <script>
-      var globalThis = window;
-    </script>
-    <script src="/src/main.ts" type="module"></script>
-  </body>
-</html>
+  </div>
+  <div class="versionHash">
+    <span>version hash: <%= hash %></span>
+    <style>
+      .versionHash {
+        position: fixed;
+        bottom: 10px;
+        right: 20px;
+        font-size: 12px;
+        color: #ccc;
+        z-index: 10000;
+      }
+    </style>
+  </div>
+  <script>
+    var globalThis = window;
+  </script>
+  <script src="/src/main.ts" type="module"></script>
+</body>
+
+</html>

+ 5 - 5
mock/_util.ts

@@ -13,7 +13,7 @@ export function resultPageSuccess<T = any>(
   page: number,
   pageSize: number,
   list: T[],
-  { message = 'ok' } = {},
+  { msg = 'ok' } = {},
 ) {
   const pageData = pagination(page, pageSize, list);
 
@@ -24,15 +24,15 @@ export function resultPageSuccess<T = any>(
       pageCount: list.length,
       list: pageData,
     }),
-    message,
+    msg,
   };
 }
 
-export function resultError(message = 'Request failed', { code = -1, result = null } = {}) {
+export function resultError(msg = 'Request failed', { code = -1, data = null } = {}) {
   return {
     code,
-    result,
-    message,
+    data,
+    msg,
     type: 'error',
   };
 }

+ 4 - 2
mock/system/dictionary.ts

@@ -136,12 +136,14 @@ export default [
           return item.label.indexOf(keywords) != -1;
         });
       }
-      const count = list.length > Number(pageSize) ? Math.ceil(list.length / Number(pageSize)) : 0;
+      const count =
+        list.length > Number(pageSize) ? Math.ceil(list.length / Number(pageSize)) : list.length;
+      const itemCount = count > Number(pageSize) ? count * Number(pageSize) : count;
       return resultSuccess({
         page: Number(page),
         pageSize: Number(pageSize),
         pageCount: count,
-        itemCount: count * Number(pageSize),
+        itemCount,
         list,
       });
     },

+ 5 - 9
package.json

@@ -1,11 +1,6 @@
 {
   "name": "naive-admin-element-tenant",
-  "version": "1.3.8",
-  "author": {
-    "name": "Ahjung",
-    "email": "735878602@qq.com",
-    "url": "https://github.com/jekip"
-  },
+  "version": "1.3.9",
   "private": true,
   "scripts": {
     "bootstrap": "pnpm install",
@@ -108,6 +103,7 @@
     "rimraf": "3.0.2",
     "rollup-plugin-visualizer": "5.8.3",
     "sass": "1.53.0",
+    "simple-git": "3.22.0",
     "stylelint": "14.9.1",
     "stylelint-config-prettier": "9.0.3",
     "stylelint-config-standard": "25.0.0",
@@ -116,9 +112,9 @@
     "tailwindcss": "3.3.2",
     "ts-node": "10.9.1",
     "typescript": "4.7.4",
-    "vite": "5.0.10",
+    "vite": "5.1.3",
     "vite-plugin-compression": "0.5.1",
-    "vite-plugin-html": "3.2.0",
+    "vite-plugin-html": "3.2.2",
     "vite-plugin-mock": "2.9.6",
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "2.0.1",
@@ -155,4 +151,4 @@
       ]
     }
   }
-}
+}

+ 1 - 0
postcss.config.js

@@ -1,5 +1,6 @@
 module.exports = {
   plugins: {
+    'tailwindcss/nesting': {},
     tailwindcss: {},
     autoprefixer: {},
   },

BIN
public/upload-user-templete/templete.xlsx


+ 16 - 3
src/components/Form/src/BasicForm.vue

@@ -5,14 +5,14 @@
         v-bind="getCol"
         v-for="schema in getSchema"
         :key="schema.field"
-        :span="schema.hidden ? 0 : getCol.span"
+        :span="getHidden(schema) ? 0 : getCol.span"
       >
         <el-form-item
           v-bind="schema"
           :label="schema.label"
           :prop="schema.field"
           :showFeedback="schema.showFeedback"
-          v-if="!schema.hidden"
+          v-if="!getHidden(schema)"
         >
           <!--标签名右侧温馨提示-->
           <template #label v-if="schema.labelMessage">
@@ -170,7 +170,7 @@
   import type { Ref } from 'vue';
   import type { FormSchema, FormProps, FormActionType } from './types/form';
 
-  import { isArray } from '@/utils/is/index';
+  import { isArray, isBoolean, isFunction } from '@/utils/is/index';
   import { deepMerge } from '@/utils';
 
   const props = defineProps({ ...basicProps });
@@ -217,6 +217,19 @@
     };
   }
 
+  function getHidden(schema): boolean {
+    const hidden = schema.hidden;
+    const field = schema.field;
+    if (isBoolean(hidden)) return hidden;
+
+    if (isFunction(hidden)) {
+      const values = getFieldsValue();
+      const status = hidden({ schema, values, model: formModel, field });
+      return status;
+    }
+    return false;
+  }
+
   function getSpecComponentProps(schema) {
     const compProps = schema.componentProps ?? {};
     return {

+ 8 - 1
src/components/Form/src/types/form.ts

@@ -4,6 +4,13 @@ import type { CSSProperties } from 'vue';
 import type { ColProps } from 'element-plus/es/components/col/src/col';
 import type { ButtonProps } from 'element-plus/es/components/button/src/button';
 
+export interface RenderReturnParams {
+  schema: FormSchema;
+  values: Recordable;
+  model: Recordable;
+  field: string;
+}
+
 export interface componentProps {
   options?: any[];
   placeholder?: string;
@@ -29,7 +36,7 @@ export interface FormSchema {
   showFeedback?: boolean;
   showLabel?: boolean;
   requireMarkPlacement?: string;
-  hidden?: boolean;
+  hidden?: boolean | ((renderCallbackParams: RenderReturnParams) => boolean);
 }
 
 export interface FormProps {

+ 28 - 17
src/hooks/web/useECharts.ts

@@ -10,12 +10,14 @@ import { useBreakpoint } from '@/hooks/event/useBreakpoint';
 
 import echarts from '@/utils/lib/echarts';
 import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
+import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
 
 export function useECharts(
   elRef: Ref<HTMLDivElement>,
   theme: 'light' | 'dark' | 'default' = 'light',
 ) {
   const { getDarkTheme } = useDesignSetting();
+  const { getMenuSetting } = useProjectSetting();
 
   const getDarkMode = computed(() => {
     const appTheme = getDarkTheme.value ? 'dark' : 'light';
@@ -62,23 +64,26 @@ export function useECharts(
 
   function setOptions(options: EChartsOption, clear = true) {
     cacheOptions.value = options;
-    if (unref(elRef)?.offsetHeight === 0) {
-      useTimeoutFn(() => {
-        setOptions(unref(getOptions));
-      }, 30);
-      return;
-    }
-    nextTick(() => {
-      useTimeoutFn(() => {
-        if (!chartInstance) {
-          initCharts(getDarkMode.value as 'default');
-
-          if (!chartInstance) return;
-        }
-        clear && chartInstance?.clear();
-
-        chartInstance?.setOption(unref(getOptions));
-      }, 30);
+    return new Promise((resolve) => {
+      if (unref(elRef)?.offsetHeight === 0) {
+        useTimeoutFn(() => {
+          setOptions(unref(getOptions));
+          resolve(null);
+        }, 30);
+      }
+      nextTick(() => {
+        useTimeoutFn(() => {
+          if (!chartInstance) {
+            initCharts(getDarkMode.value as 'default');
+
+            if (!chartInstance) return;
+          }
+          clear && chartInstance?.clear();
+
+          chartInstance?.setOption(unref(getOptions));
+          resolve(null);
+        }, 30);
+      });
     });
   }
 
@@ -111,6 +116,12 @@ export function useECharts(
     return chartInstance;
   }
 
+  watch(getMenuSetting.value, (_) => {
+    useTimeoutFn(() => {
+      resizeFn();
+    }, 300);
+  });
+
   return {
     setOptions,
     resize,

+ 2 - 2
src/hooks/web/useTags.ts

@@ -1,5 +1,5 @@
 import { unref } from 'vue';
-import { useRouter } from 'vue-router';
+import { RouteLocationNormalized, useRouter } from 'vue-router';
 import type { Router } from 'vue-router';
 import { useTabsViewStore } from '@/store/modules/tabsView';
 import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
@@ -23,7 +23,7 @@ export function useTabs(_router?: Router) {
   }
 
   //更新tab标题
-  async function updateTabTitle(title: string, tab?: object) {
+  async function updateTabTitle(title: string, tab?: RouteLocationNormalized) {
     const targetTab = tab || getCurrentTab();
     await tabsViewStore.setTabTitle(title, targetTab);
   }

+ 2 - 0
src/main.ts

@@ -4,6 +4,7 @@ import './styles/index.scss';
 import 'element-plus/theme-chalk/display.css';
 import 'element-plus/theme-chalk/dark/css-vars.css';
 import 'nprogress/nprogress.css';
+import VueKonva from 'vue-konva';
 
 import { createApp } from 'vue';
 import App from './App.vue';
@@ -16,6 +17,7 @@ import { setupElement, setupDirectives, setupCustomComponents } from '@/plugins'
 
 async function bootstrap() {
   const app = createApp(App);
+  app.use(VueKonva);
 
   // 全局完整引入 element 组件
   setupElement(app);

+ 1 - 1
src/utils/http/axios/index.ts

@@ -10,7 +10,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
 
 import { useGlobSetting } from '@/hooks/setting';
 
-import { isString } from '@/utils/is/';
+import { isString } from '@/utils/is/index';
 import { deepMerge, isUrl } from '@/utils';
 import { setObjToUrlParams } from '@/utils/urlUtils';
 

+ 39 - 34
src/views/cameras/preview/components/CameraViewSetting/CameraViewSetting.vue

@@ -11,32 +11,38 @@
         :is-edit="isEdit"
       />
     </div>
-    <div class="cameraViewOverflow" :style="{ width: domWidth + 'px', height: domHeight + 'px' }">
-      <div
-        class="cameraViewSettingWrapper"
-        :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px', scale: scale }"
-      >
-        <FenceEditor ref="fenceEditorRef" />
-        <div class="cameraVideo">
-          <CameraLiveVideo />
-        </div>
+    <div
+      class="cameraViewSettingWrapper"
+      :style="{ width: domWidth + 'px', height: domHeight + 'px' }"
+    >
+      <div class="fenceEditorWrapper">
+        <FenceEditor
+          ref="fenceEditorRef"
+          :dom-width="domWidth"
+          :canvas-size="{ width: canvasWidth, height: canvasHeight }"
+          :line-points="fenceStore.serverFencePoints || []"
+        />
       </div>
-      <div
-        class="presetAddWrapper"
-        :class="{ hidePresetControlCls: isEdit }"
-        v-if="!!cameraDetailStore.detail?.isPtz"
-      >
-        <CameraDirectionControl />
-        <ElButton
-          type="primary"
-          @click="handleAddPreset"
-          size="small"
-          style="margin-top: 20px; width: 100px"
-          >添加预置位</ElButton
-        >
-        <AddPresetModal v-if="addPresetModalVisible" @close="handleClose" @ok="handleAddPresetOk" />
+
+      <div class="cameraVideo">
+        <CameraLiveVideo />
       </div>
     </div>
+    <div
+      class="presetAddWrapper"
+      :class="{ hidePresetControlCls: isEdit }"
+      v-if="!!cameraDetailStore.detail?.isPtz"
+    >
+      <CameraDirectionControl />
+      <ElButton
+        type="primary"
+        @click="handleAddPreset"
+        size="small"
+        style="margin-top: 20px; width: 100px"
+        >添加预置位</ElButton
+      >
+      <AddPresetModal v-if="addPresetModalVisible" @close="handleClose" @ok="handleAddPresetOk" />
+    </div>
   </div>
   <div class="cameraParamsSettingWrapper">
     <div class="cameraParamsSetting">
@@ -48,7 +54,7 @@
 <script lang="ts" setup>
   import { computed, ref, watchEffect } from 'vue';
   import FenceToolbar from '../FenceToolbar/FenceToolbar.vue';
-  import FenceEditor from '../FenceEditor/FenceEditor.vue';
+  import FenceEditor from '../FenceEditorV2/FenceEditor.vue';
   import CameraLiveVideo from '../CameraLiveVideo/CameraLiveVideo.vue';
   import ViewWindowSetting from '../ViewWindowSetting/ViewWindowSetting.vue';
   import PresetSelect from '../PresetSelect/PresetSelect.vue';
@@ -152,17 +158,10 @@
         fenceEditorRef.value?.clear();
         return;
       }
-      const rawLinePoints = points.map((x) => {
-        const points: number[] = [];
-        x.forEach((line) => {
-          points.push(line[0], line[1]);
-        });
-        return points;
-      });
-      if (!rawLinePoints) return;
+
       /** 先清空原有的 */
       fenceEditorRef.value?.clear();
-      fenceEditorRef.value?.createLines(rawLinePoints);
+      // fenceEditorRef.value?.createLines(rawLinePoints);
       fenceEditorRef.value?.setEditMode();
       isEdit.value = true;
       return;
@@ -181,7 +180,6 @@
   .cameraViewSettingWrapper {
     position: relative;
     border: 1px solid #ccc;
-    transform-origin: left top;
   }
   .cameraViewOverflow {
     overflow: hidden;
@@ -190,6 +188,9 @@
 
   .cameraVideo {
     position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 8;
     background: #ccc;
     width: 100%;
     height: 100%;
@@ -230,4 +231,8 @@
   .hidePresetControlCls {
     display: none;
   }
+  .fenceEditorWrapper {
+    position: relative;
+    z-index: 9;
+  }
 </style>

+ 0 - 15
src/views/cameras/preview/components/FenceEditor/FenceEditor.vue

@@ -655,14 +655,6 @@
     return gropuPoints;
   };
 
-  const initStageByJSON = (param: { width: number; height: number }) => {
-    stage?.setAttrs({ width: param.width, height: param.height });
-  };
-
-  const toRawObject = () => {
-    return stage?.toObject();
-  };
-
   /** 退出编辑模式 */
   const exitEditMode = () => {
     setCurrentGroup(null);
@@ -678,20 +670,13 @@
     layer?.removeChildren();
   };
 
-  const setScale = (scale: number) => {
-    stage?.setAttr('scaleX', scale);
-  };
-
   defineExpose({
     remove: removeCurrent,
     toObject,
-    toRawObject,
     createLines,
-    initStageByJSON,
     exitEditMode,
     setEditMode,
     clear,
-    setScale,
   });
 </script>
 

+ 258 - 0
src/views/cameras/preview/components/FenceEditorV2/FenceEditor.vue

@@ -0,0 +1,258 @@
+<template>
+  <div class="overflowWrapper" :style="{ width: props.domWidth + 'px', height: domHeight + 'px' }">
+    <div
+      class="scaleWrapper"
+      :style="{
+        scale: scale,
+        width: props.canvasSize.width + 'px',
+        height: props.canvasSize.height + 'px',
+      }"
+    >
+      <v-stage
+        :config="configKonva"
+        @mouse-down="handleStageMouseDown"
+        @mouse-move="handleStageMouseMove"
+        ref="stageRef"
+      >
+        <v-layer>
+          <FenceItem
+            :fenceGroups="fenceGroups"
+            :draggable="!drawingGroupId"
+            @select-group="handleSelectGroup"
+            :is-edit="isEdit"
+          />
+        </v-layer>
+      </v-stage>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  import Konva from 'konva';
+  import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
+  import FenceItem from './FenceItem.vue';
+  import { createCircleConfigItem, createGroupConfig } from './utils';
+  import { FenceGroup } from './types';
+  import { ElMessage } from 'element-plus';
+  import { GROUP_NAME } from './constants';
+
+  const props = defineProps<{
+    /** 电子围栏的坐标 */
+    linePoints: [number, number][][];
+    /** 画布的大小 */
+    canvasSize: { width: number; height: number };
+    /** dom的真实尺寸 */
+    domWidth: number;
+  }>();
+
+  const scale = computed(() => {
+    return props.domWidth / props.canvasSize.width;
+  });
+
+  const stageRef = ref();
+  const isEdit = ref(false);
+
+  const fenceGroups = ref<FenceGroup[]>([]);
+
+  /** 当前正在画的多边形的groupId */
+  const drawingGroupId = ref('');
+  /** 当前选中的多边形groupId,点击、拖拽、画线都会给它赋值 */
+  const currentGroupId = ref('');
+
+  watch(
+    () => props.linePoints,
+    (newLinePoints) => {
+      const configs: FenceGroup[] =
+        newLinePoints.map((points) => {
+          const flattenedPoints = points.reduce((total, next) => {
+            return [...total, ...next];
+          }, [] as number[]);
+          return createGroupConfig(flattenedPoints, scale.value);
+        }) || [];
+      fenceGroups.value = configs;
+    },
+    {
+      immediate: true,
+    },
+  );
+
+  onMounted(() => {
+    /** 取消默认的右键 */
+    document.oncontextmenu = function () {
+      return false;
+    };
+  });
+
+  onUnmounted(() => {
+    /** 取消默认的右键 */
+    document.oncontextmenu = function () {
+      return true;
+    };
+  });
+
+  const configKonva = computed(() => {
+    return props.canvasSize;
+  });
+
+  const canvasRatio = computed(() => {
+    const size = props.canvasSize;
+    if (!size) return 1;
+    return size.height / size.width;
+  });
+
+  const domHeight = computed(() => {
+    return props.domWidth * canvasRatio.value;
+  });
+
+  const handleStageMouseDown = (e) => {
+    if (!isEdit.value) return;
+    /**
+     * parent存在,说明点击的不是stage
+     * !drawingGroupId,说明当前不处于绘制多边形中
+     * 这两种情况,都不能执行stage的点击事件,要执行点击对象的默认事件
+     */
+    if (e.target.parent && !drawingGroupId.value) return;
+    const stage = e.currentTarget as Konva.Stage;
+    // 获取当前鼠标相对舞台的位置
+    const mousePosition = stage.getPointerPosition();
+    if (!mousePosition?.x || !mousePosition?.y) return;
+    const point = [mousePosition.x, mousePosition.y] as [number, number];
+
+    /** 如果还没开始画线,那么增加第一个点 */
+    if (!drawingGroupId.value) {
+      const groupConfig = createGroupConfig(point, scale.value);
+      drawingGroupId.value = groupConfig.uid;
+      groupConfig._temp.points = point;
+      fenceGroups.value.push(groupConfig);
+    } else {
+      /** 右键点击,取消最后一个点 */
+      if (e.evt.button === 2) {
+        /** 否则就追加点 */
+        const groupConfig = fenceGroups.value.find((x) => x.uid === drawingGroupId.value);
+        if (!groupConfig) {
+          console.error('drawingGroupId无效', drawingGroupId.value);
+          return;
+        }
+        if ((groupConfig._temp.points.length || 0) <= 4) {
+          ElMessage({
+            message: '顶点数必须大于2个!',
+            type: 'warning',
+            center: true,
+            duration: 1000,
+          });
+          groupConfig.lineConfig.points = [];
+          groupConfig.circleConfigs = [];
+        } else {
+          groupConfig.lineConfig.points = groupConfig._temp.points;
+        }
+        drawingGroupId.value = '';
+        groupConfig._temp.points = [];
+        return;
+      }
+      /** 否则就追加点 */
+      const groupConfig = fenceGroups.value.find((x) => x.uid === drawingGroupId.value);
+      if (!groupConfig) {
+        console.error('drawingGroupId无效', drawingGroupId.value);
+        return;
+      }
+      const tempPoints = groupConfig._temp?.points || [];
+      const finalPoints = [...tempPoints, ...point];
+      groupConfig.lineConfig.points = finalPoints;
+      groupConfig._temp.points = finalPoints;
+
+      const circleConfig = createCircleConfigItem(
+        point,
+        groupConfig.circleConfigs.length,
+        scale.value,
+      );
+      groupConfig.circleConfigs.push(circleConfig);
+    }
+  };
+
+  const handleStageMouseMove = (e) => {
+    if (!isEdit.value) return;
+    const stage = e.currentTarget as Konva.Stage;
+    /** 获取当前鼠标的坐标 */
+    const mousePosition = stage.getPointerPosition();
+    if (!mousePosition?.x || !mousePosition?.y) return;
+    const newPoint = [mousePosition.x, mousePosition.y];
+    if (drawingGroupId.value) {
+      const groupConfig: FenceGroup | undefined = fenceGroups.value.find(
+        (x) => x.uid === drawingGroupId.value,
+      );
+      if (!groupConfig) {
+        console.error('drawingGroupId无效', drawingGroupId.value);
+        return;
+      }
+
+      /** 如果正在画线,那么替换最后一个点 */
+      const initialPoints = groupConfig.lineConfig.points as number[];
+      if (groupConfig._temp.points.length > 0) {
+        groupConfig.lineConfig.points = [...groupConfig._temp.points, ...newPoint];
+      } else {
+        groupConfig._temp.points = initialPoints;
+      }
+    }
+  };
+
+  const handleSelectGroup = (groupId: string) => {
+    currentGroupId.value = groupId;
+  };
+
+  /** 清空所有元素 */
+  const clear = () => {
+    fenceGroups.value = [];
+  };
+
+  /** 删除当前选中的group项 */
+  const remove = () => {
+    fenceGroups.value = fenceGroups.value.filter((x) => x.uid !== currentGroupId.value);
+  };
+
+  /** 导出为json格式 */
+  const toObject = () => {
+    const stage = stageRef.value.getStage();
+    const fenceGroups = stage?.find('.' + GROUP_NAME);
+    const gropuPoints = fenceGroups?.map((item) => {
+      const groupX = item.x();
+      const groupY = item.y();
+
+      const line = (item as Konva.Group).findOne((x: any) => x.className === 'Line') as Konva.Line;
+      const points = line?.points();
+      const newPoints: number[][] = [];
+      /** 存到后端的时候,只给点的坐标信息,不会给group的位置信息,所以要将点的坐标加上group的位移,才是之后点的最终坐标 */
+      for (let i = 0; i < points.length; i += 2) {
+        newPoints.push([Math.floor(points[i] + groupX), Math.floor(points[i + 1] + groupY)]);
+      }
+      return newPoints;
+    });
+    return gropuPoints;
+  };
+
+  /** 退出编辑模式 */
+  const exitEditMode = () => {
+    currentGroupId.value = '';
+    isEdit.value = false;
+  };
+  /** 进入编辑模式 */
+  const setEditMode = () => {
+    isEdit.value = true;
+  };
+
+  defineExpose({
+    clear,
+    remove,
+    toObject,
+    exitEditMode,
+    setEditMode,
+  });
+</script>
+
+<style scoped>
+  .scaleWrapper {
+    transform-origin: left top;
+  }
+  .overflowWrapper {
+    overflow: hidden;
+    border: 1px solid #ccc;
+  }
+</style>

+ 55 - 0
src/views/cameras/preview/components/FenceEditorV2/FenceItem.vue

@@ -0,0 +1,55 @@
+<!-- eslint-disable vue/no-use-v-if-with-v-for -->
+<template>
+  <v-group
+    v-for="group in props.fenceGroups"
+    :key="group.uid"
+    :groupId="group.uid"
+    :draggable="props.draggable && props.isEdit"
+    :name="group.name"
+    @mouse-down="handleGroupMouseDown"
+  >
+    <v-line :config="group.lineConfig" />
+    <v-circle
+      v-if="props.isEdit"
+      v-for="circleConfig in group.circleConfigs"
+      :config="circleConfig"
+      :key="circleConfig"
+      @mouse-down="handleCircleMouseDown"
+      @drag-move="handleCircleDragMove(circleConfig, $event)"
+    />
+  </v-group>
+</template>
+<script lang="ts" setup>
+  import { FenceCircleConfig, FenceGroup } from './types';
+
+  const props = defineProps<{
+    fenceGroups: FenceGroup[];
+    draggable: boolean;
+    isEdit: boolean;
+  }>();
+
+  const emits = defineEmits<{ (e: 'selectGroup', groupId: string): unknown }>();
+
+  const handleCircleDragMove = (circleConfig: FenceCircleConfig, e) => {
+    console.log('circle move', e);
+    console.log('circle move circleConfig', circleConfig);
+    const lineAttrs = e.target.parent.find('Line')[0].attrs;
+    const { x, y, idx } = e.target.attrs;
+    lineAttrs.points[idx * 2] = x;
+    lineAttrs.points[idx * 2 + 1] = y;
+    circleConfig.x = x;
+    circleConfig.y = y;
+  };
+
+  const handleCircleMouseDown = (e: { cancelBubble: boolean }) => {
+    /** 阻止冒泡 */
+    e.cancelBubble = true;
+  };
+
+  const handleGroupMouseDown = (e) => {
+    if (!props.isEdit) return;
+    e.target.parent.moveToTop();
+    emits('selectGroup', e.target.parent.attrs.groupId);
+  };
+</script>
+<style scoped></style>

+ 17 - 0
src/views/cameras/preview/components/FenceEditorV2/constants.ts

@@ -0,0 +1,17 @@
+export const defaultLineStyle = {
+  stroke: '#52FFDA',
+  strokeWidth: 3,
+  closed: true,
+  // fill: '#ff0000',
+};
+
+export const defaultCircleStyle = {
+  /** 圆的半径 */
+  radius: 4,
+  /** 点击区域 */
+  hitStrokeWidth: 10,
+  fill: '#52FFDA',
+  draggable: true,
+};
+
+export const GROUP_NAME = 'fenceGroup';

+ 17 - 0
src/views/cameras/preview/components/FenceEditorV2/types.ts

@@ -0,0 +1,17 @@
+import Konva from 'konva';
+
+export type FenceLineConfig = Konva.LineConfig;
+export interface FenceCircleConfig extends Konva.CircleConfig {
+  uid: string;
+}
+
+export interface FenceGroup {
+  lineConfig: FenceLineConfig;
+  /** 临时存放点坐标 */
+  _temp: {
+    points: number[];
+  };
+  circleConfigs: FenceCircleConfig[];
+  uid: string;
+  name: string;
+}

+ 41 - 0
src/views/cameras/preview/components/FenceEditorV2/utils.ts

@@ -0,0 +1,41 @@
+import { GROUP_NAME, defaultCircleStyle, defaultLineStyle } from './constants';
+import { uid } from 'uid';
+import { FenceGroup } from './types';
+
+export const getCircleConfig = (points: number[], scale: number) => {
+  const circlePoints = [];
+  for (let i = 0; i < points.length - 1; i += 2) {
+    circlePoints.push([points[i], points[i + 1]]);
+  }
+  return circlePoints.map((point, idx) => {
+    return createCircleConfigItem(point as [number, number], idx, scale);
+  });
+};
+
+export const createCircleConfigItem = (point: [number, number], idx: number, scale: number) => {
+  return {
+    ...defaultCircleStyle,
+    radius: defaultCircleStyle.radius / scale,
+    hitStrokeWidth: defaultCircleStyle.hitStrokeWidth / scale,
+    x: point[0],
+    y: point[1],
+    uid: uid(),
+    idx,
+  };
+};
+
+export const createGroupConfig = (points: number[], scale: number): FenceGroup => {
+  const lineConfig = {
+    ...defaultLineStyle,
+    strokeWidth: defaultLineStyle.strokeWidth / scale,
+    points: points,
+  };
+  const circleConfigs = getCircleConfig(points, scale);
+  return {
+    lineConfig,
+    name: GROUP_NAME,
+    circleConfigs,
+    uid: uid(),
+    _temp: { points: [] },
+  };
+};

+ 8 - 0
src/views/comp/form/basic.vue

@@ -92,8 +92,10 @@
       field: 'type',
       component: 'Select',
       label: '类型',
+      labelMessage: '选择类型会出现预约时间表单',
       componentProps: {
         placeholder: '请选择类型',
+        clearable: true,
         options: [
           {
             label: '舒适性',
@@ -133,6 +135,12 @@
           console.log(e);
         },
       },
+      // 根据 上面选择的类型,获取页面其他逻辑字段 处理显示表单
+      // 可用字段 schema, values, model, field
+      hidden: ({ model }) => {
+        return !model.type;
+      },
+      rules: [{ required: true, type: 'number', message: '请选择预约时间', trigger: ['change'] }],
     },
     {
       field: 'makeTime',

+ 0 - 1
src/views/list/basicList/index.vue

@@ -21,7 +21,6 @@
         :actionColumn="actionColumn"
         @checked-row-change="onCheckedRow"
         :scroll-x="1090"
-        :pagination="false"
       >
         <template #tableTitle>
           <el-button type="primary" @click="addTable">

+ 0 - 71
src/views/map-config/mini-map/MapBase/CameraGroup-bak.ts

@@ -1,71 +0,0 @@
-import { fabric } from 'fabric';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { getRandomPosition } from './utils';
-import { CameraImage } from './types';
-
-class CameraGroup extends fabric.Group {
-  g: fabric.Group | null = null;
-  cameraImg: CameraImage | null = null;
-  favImg: CameraImage | null = null;
-  cameraId = '';
-
-  constructor() {
-    super();
-    fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-      cImg.set({
-        left: 0,
-        top: 0,
-      });
-      this.cameraImg = cImg;
-      fabric.Image.fromURL(favoritesImg, (favImg) => {
-        favImg.set({
-          left: 50,
-          top: 0,
-        });
-        this.favImg = favImg as CameraImage;
-        this.g = new fabric.Group([cImg, favImg]);
-      });
-    });
-  }
-
-  init() {}
-
-  /** 提供copy功能,每次新建的时候执行copy就行了 */
-  clone(): Promise<CameraGroup> {
-    console.log('clone');
-    return new Promise((resolve) => {
-      this.g?.clone((e) => {
-        console.log('clone', e);
-        resolve(e as CameraGroup);
-      });
-    });
-  }
-
-  setSelected() {
-    this.cameraImg?.setSrc(cameraActiveImg);
-  }
-
-  setUnSelected() {
-    this.cameraImg?.setSrc(cameraImg);
-  }
-  /** 设为默认摄像头 */
-  setDefault() {
-    this.favImg?.set('visible', true);
-  }
-  /** 取消默认摄像头 */
-  cancelDefault() {
-    this.favImg?.set('visible', false);
-  }
-
-  setAttr(attr: { cameraId: string; left: number; top: number }) {
-    if (attr.cameraId) {
-      this.cameraId = attr.cameraId;
-    }
-    this.g?.set(attr);
-    return this.g;
-  }
-}
-
-export default CameraGroup;

+ 0 - 88
src/views/map-config/mini-map/MapBase/CameraGroup.ts

@@ -1,88 +0,0 @@
-import { fabric } from 'fabric';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { CameraImage } from './types';
-
-class CameraGroup extends fabric.Group {
-  g: fabric.Group;
-  cameraImg: CameraImage | null = null;
-  favImg: CameraImage | null = null;
-  cameraId = '';
-
-  constructor() {
-    super();
-    this.init();
-  }
-
-  init() {
-    fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-      cImg.set({
-        left: 0,
-        top: 0,
-        width: 100,
-        height: 100,
-        imageName: 'cameraImage',
-      });
-      console.log('cameraActiveImg', cImg);
-      this.cameraImg = cImg;
-      fabric.Image.fromURL(favoritesImg, (favImg) => {
-        favImg.set({
-          width: 50,
-          height: 50,
-          left: 50,
-          top: 0,
-          imageName: 'favImage',
-        });
-        this.favImg = favImg as CameraImage;
-        this.g = new fabric.Group([cImg, favImg]);
-      });
-    });
-  }
-
-  /** 提供copy功能,每次新建的时候执行copy就行了 */
-  clone(): Promise<CameraGroup> {
-    console.log('clone');
-    console.log('this', this);
-    return new Promise((resolve) => {
-      this.g.clone((e) => {
-        console.log('clone', e);
-        const newG = e as fabric.Group;
-        const newGroup = new CameraGroup();
-        const cameraImg = newG.getObjects().find((x) => x.imageName === 'cameraImage');
-        const favImage = newG.getObjects().find((x) => x.imageName === 'favImage');
-        newGroup.cameraImg =                            ;
-        newGroup.favImg = favImage;
-        newGroup.g = e;
-        resolve(newGroup as CameraGroup);
-      });
-    });
-  }
-
-  setSelected() {
-    
-    this.cameraImg?.setSrc(cameraActiveImg);
-  }
-
-  setUnSelected() {
-    this.cameraImg?.setSrc(cameraImg);
-  }
-  /** 设为默认摄像头 */
-  setDefault() {
-    this.favImg?.set('visible', true);
-  }
-  /** 取消默认摄像头 */
-  cancelDefault() {
-    this.favImg?.set('visible', false);
-  }
-
-  setAttr(attr: { cameraId: string; left: number; top: number }) {
-    if (attr.cameraId) {
-      this.cameraId = attr.cameraId;
-    }
-    this.g?.set(attr);
-    return this.g;
-  }
-}
-
-export default new CameraGroup();

+ 0 - 216
src/views/map-config/mini-map/MapBase/CameraMapBak.ts

@@ -1,216 +0,0 @@
-import { fabric } from 'fabric';
-import { ref } from 'vue';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { CameraImage, MapData, OnMoving, OnRightClick, OnSelect, isCanvas } from './types';
-import { fabricSetting } from './fabricSetting';
-import { getRandomPosition } from './utils';
-// import templateGroup from './CameraGroup';
-import { createGroup, toggleGroupSelected, toggleCameraDefault } from './CameraStarGroup';
-
-fabricSetting();
-
-class CameraMap {
-  public canvas = ref<fabric.Canvas | null>();
-  private onSelect: OnSelect;
-  private onRightClick: OnRightClick;
-  private onMoving: OnMoving;
-
-  constructor(param: {
-    canvasId: string;
-    onSelect: OnSelect;
-    onRightClick: OnRightClick;
-    onMoving: OnMoving;
-  }) {
-    this.canvas.value = new fabric.Canvas(param.canvasId, {
-      fireRightClick: true, // 启用右键,button的数字为3
-      stopContextMenu: true, // 禁止默认右键菜单
-    });
-    this.addListener();
-    this.onSelect = param.onSelect;
-    this.onRightClick = param.onRightClick;
-    this.onMoving = param.onMoving;
-    window.canvas = this.canvas.value;
-  }
-
-  /** 监听点击事件 */
-  private addListener() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-
-    canvas.on('mouse:down', (options) => {
-      const target = options.target as CameraImage;
-      const cameraId = target?.cameraId;
-      // console.log('当前选中的id是', cameraId);
-      console.log('mouse:down');
-      console.log(options);
-
-      // 判断:右键,且在元素上右键
-      // opt.button: 1-左键;2-中键;3-右键
-      // 在画布上点击:opt.target 为 null
-      if (options.button === 3 && options.target) {
-        this.onRightClick(options);
-        return;
-      }
-
-      this.setAllGroupsUnselected();
-      if (!cameraId || !target) {
-        this.onSelect(null);
-        return;
-      }
-      toggleGroupSelected(target, true).then(() => {
-        canvas.renderAll();
-      });
-      this.onSelect(target);
-    });
-  }
-
-  private setAllGroupsUnselected() {
-    const canvas = this.canvas;
-    if (!isCanvas(canvas)) return;
-    canvas.getObjects('group').forEach((object) => {
-      // if (object === options.target) return;
-      toggleGroupSelected(object, false).then((res) => {
-        canvas.renderAll();
-      });
-    });
-  }
-
-  /** 上传背景图 */
-  public uploadBg(imgUrl: string) {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    fabric.Image.fromURL(imgUrl, (img) => {
-      console.log('image', img);
-      canvas!.setWidth(img.width!);
-      canvas!.setHeight(img.height!);
-      img.lockScalingX = true;
-      img.lockScalingY = true;
-      // 设置背景图
-      canvas.setBackgroundImage(img, this.canvas.value!.renderAll.bind(this.canvas));
-    });
-  }
-
-  /** 将所有的摄像头都设置为非激活状态 */
-  private setAllCameraUnActive() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    canvas.forEachObject((object) => {
-      (object as fabric.Image).setSrc(cameraImg, () => {
-        canvas.renderAll();
-      });
-    });
-  }
-
-  /** 增加一个摄像头 */
-  public addCamera(cameraId: string): Promise<CameraImage> {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return Promise.reject();
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    const that = this;
-    return new Promise((resolve) => {
-      fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-        const cameraImg = ref(cImg as unknown as CameraImage);
-        this.setAllCameraUnActive();
-        cameraImg.value.set({
-          left: getRandomPosition(),
-          top: getRandomPosition(),
-          cameraId,
-        });
-        cameraImg.value.lockScalingX = true;
-        cameraImg.value.lockScalingY = true;
-        canvas.add(cameraImg.value);
-        cameraImg.value.on('moving', function (e) {
-          console.log('move', e);
-          that.onMoving(e);
-        });
-        resolve(cameraImg);
-      });
-    });
-  }
-
-  /** 删除一个摄像头 */
-  public removeCamera(cameraImage: CameraImage) {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    console.log('removeCamera', cameraImage);
-    canvas.remove(cameraImage);
-  }
-
-  /** 导出JSON格式 */
-  public toJSON() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    const initialJSON = canvas.toJSON(['cameraId']);
-    /** toJSON返回值的类型它写错了,应该是有backgroundImage的 */
-    const { src, type, version, width, height, left, top, angle } =
-      (initialJSON as any).backgroundImage || {};
-
-    const newObjects = initialJSON.objects.map((item) => {
-      return {
-        type: item.type,
-        width: item.width,
-        height: item.height,
-        left: item.left,
-        top: item.top,
-        angle: item.angle,
-        cameraId: (item as CameraImage).cameraId,
-      };
-    });
-    const newJson = {
-      version: initialJSON.version,
-      backgroundImage: { src, type, version, width, height, left, top, angle },
-      objects: newObjects,
-    };
-    return newJson;
-  }
-
-  /** 从json中加载 */
-  public loadFromJSON(json: MapData): Promise<void> {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return Promise.reject();
-    return new Promise((resolve) => {
-      const { width, height } = json.backgroundImage;
-      canvas.setWidth(width);
-      canvas.setHeight(height);
-      const objects = json.objects.map((item) => {
-        return {
-          ...item,
-          src: cameraImg,
-        };
-      });
-      canvas.loadFromJSON({ ...json, objects }, () => {
-        resolve();
-      });
-    });
-  }
-
-  /** 更新摄像头的渲染 */
-  public renderCamera() {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    canvas.renderAll();
-  }
-
-  public clear() {
-    this.canvas.value?.clear();
-  }
-
-  /** 是否已经存在这个cameraId */
-  public hasCamera(cameraId: string) {
-    const canvas = this.canvas.value;
-    if (!isCanvas(canvas)) return;
-    const cameraIds = canvas
-      .toJSON(['cameraId'])
-      .objects.map((item) => (item as CameraImage).cameraId);
-    return cameraIds.includes(cameraId);
-  }
-
-  /** 根据cameraId查找某个元素 */
-  public getCameraById(cameraId: string) {
-    return this.canvas.value?.getObjects().find((x) => (x as CameraImage).cameraId === cameraId);
-  }
-}
-
-export default CameraMap;

+ 0 - 73
src/views/map-config/mini-map/MapBase/CameraPreview.vue

@@ -1,73 +0,0 @@
-<template>
-  <div>
-    <div style="overflow: auto; position: relative">
-      <canvas width="400" height="400" ref="canvasRef" style="border: 1px solid #ccc"></canvas>
-      <DefaultCameraIcon :position="favPosition" />
-    </div>
-  </div>
-</template>
-<script lang="ts" setup>
-  import { fabric } from 'fabric';
-  import { onMounted, ref, watch } from 'vue';
-  import cameraImg from '@/assets/camera/camera.png';
-  import { CameraImage, MapData } from './types';
-  import DefaultCameraIcon from './DefaultCameraIcon.vue';
-  import { getFavPositionByCamera } from './utils';
-
-  const props = defineProps<{ json: MapData }>();
-
-  let canvas;
-  const canvasRef = ref<HTMLCanvasElement>();
-
-  const favPosition = ref<{ left: number; top: number } | null>(null);
-
-  const createMap = () => {
-    if (!canvasRef.value) return;
-    canvas = new fabric.Canvas(canvasRef.value, {
-      fireRightClick: true, // 启用右键,button的数字为3
-      stopContextMenu: true, // 禁止默认右键菜单
-    });
-    canvas.selectable = false;
-    // window.cvs = canvas;
-  };
-
-  onMounted(() => {
-    createMap();
-  });
-
-  watch(
-    () => props.json,
-    () => {
-      const json = props.json;
-      console.log('props.json', props.json);
-      if (json) {
-        const { width, height } = json.backgroundImage;
-        canvas?.setWidth(width);
-        canvas?.setHeight(height);
-        const objects = json.objects.map((item) => {
-          return {
-            ...item,
-            src: cameraImg,
-            selectable: false,
-            hasControls: false,
-            hasBoards: false,
-          };
-        });
-        canvas?.loadFromJSON({ ...json, objects }, () => {
-          const defaultCamera = getCameraById(json.defaultCameraId);
-          if (defaultCamera) {
-            favPosition.value = getFavPositionByCamera(defaultCamera);
-          }
-        });
-      }
-    },
-    { deep: true },
-  );
-
-  const getCameraById = (cameraId: string) => {
-    return canvas
-      ?.getObjects()
-      .find((x) => (x as CameraImage).cameraId === cameraId) as CameraImage;
-  };
-</script>
-<style scoped></style>

+ 0 - 68
src/views/map-config/mini-map/MapBase/CameraStarGroup.ts

@@ -1,68 +0,0 @@
-import { fabric } from 'fabric';
-import cameraActiveImg from '@/assets/camera/camera-active.png';
-import cameraImg from '@/assets/camera/camera.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import { CameraImage } from './types';
-
-const cameraImageName = 'cameraImage';
-const favImageName = 'favImageImage';
-
-const cameraInfo = { width: 60, height: 40 };
-const favInfo = { width: 10, height: 10 };
-
-export function createGroup(): Promise<fabric.Group> {
-  return new Promise((resolve) => {
-    fabric.Image.fromURL(cameraActiveImg, (cImg) => {
-      cImg.set({
-        left: 0,
-        top: 0,
-        width: cameraInfo.width,
-        height: cameraInfo.height,
-        imageName: cameraImageName,
-      });
-      console.log('cameraActiveImg', cImg);
-
-      fabric.Image.fromURL(favoritesImg, (favImg) => {
-        favImg.set({
-          width: favInfo.width,
-          height: favInfo.height,
-          left: 40,
-          top: -10,
-          visible: false,
-          imageName: favImageName,
-        });
-        resolve(
-          new fabric.Group([cImg, favImg], {
-            width: cameraInfo.width,
-            height: cameraInfo.height,
-            // 不要缓存,否则无法修改图片地址
-            objectCaching: false,
-          }),
-        );
-      });
-    });
-  });
-}
-
-/** 设置是否是选中后的效果 */
-export function toggleGroupSelected(group: fabric.Group, isSelected: boolean) {
-  return new Promise((resolve, reject) => {
-    const groupCamera = group.getObjects()[0] as fabric.Image;
-    // .find((item) => item.imageName === cameraImageName) as fabric.Image;
-    if (!groupCamera) {
-      reject();
-      return;
-    }
-    const src = isSelected ? cameraActiveImg : cameraImg;
-    groupCamera.setSrc(src, () => {
-      resolve();
-    });
-  });
-}
-
-/** 设置是否为默认摄像头 */
-export function toggleCameraDefault(group: fabric.Group, visible: boolean) {
-  const star = group.getObjects()[1];
-  if (!star) return;
-  star.set({ visible });
-}

+ 0 - 21
src/views/map-config/mini-map/MapBase/DefaultCameraIcon.vue

@@ -1,21 +0,0 @@
-<template>
-  <!-- 默认选中图片的icon -->
-  <img
-    v-if="props.position"
-    :src="favIcon"
-    class="defaultCameraImg"
-    :style="{ left: props.position?.left + 'px', top: props.position?.top + 'px' }"
-  />
-</template>
-<script lang="ts" setup>
-  import favIcon from '@/assets/camera/favorites.png';
-
-  const props = defineProps<{ position: { left: number; top: number } | null }>();
-</script>
-<style scoped>
-  .defaultCameraImg {
-    position: absolute;
-    width: 15px;
-    /* height: 50px; */
-  }
-</style>

+ 14 - 33
src/views/map-config/mini-map/MapBase/KonvaMap.vue

@@ -6,8 +6,6 @@
         <v-group
           v-for="camera in cameras"
           :key="camera.id"
-          :id="camera.id"
-          :attrs="camera"
           :config="camera.groupConfig"
           @click="handleCameraClick(camera)"
           @mouseover="(e) => handleMouseOver(e)"
@@ -43,32 +41,13 @@
   import urlJoin from 'url-join';
   import cameraImgSrc from '@/assets/camera/cameraImg.png';
   import favoritesImgSrc from '@/assets/camera/favorites.png';
-  import { TipPositionEnum } from '../type';
+  import { TipPositionEnum, camerasGroupType } from '../type';
 
   const globSetting = useGlobSetting();
 
   const emit = defineEmits(['changeDefaultCamera', 'sendCameraId']);
 
-  interface caremasImgType {
-    x?: number;
-    y?: number;
-    width?: number;
-    height?: number;
-    draggable?: boolean;
-    rotation?: number;
-    scaleX?: number;
-    scaleY?: number;
-    image?: HTMLImageElement;
-    name?: string;
-    url?: string;
-  }
-
-  interface caremasGroupType {
-    id?: string;
-    groupConfig: caremasImgType;
-    config: caremasImgType;
-    isDefault?: boolean;
-  }
+  const camImg = new Image();
 
   const stageConfig = ref({
     width: 800,
@@ -90,7 +69,7 @@
   const lastClickedGroupId = ref<string | null>(null);
 
   const bgImgUrl = ref<string | null>('');
-  const cameras = ref<caremasGroupType[]>([]);
+  const cameras = ref<camerasGroupType[]>([]);
   //默认相机id
   const defaultCameraId = ref('');
   const tipShow = ref(false);
@@ -216,8 +195,8 @@
   const addCamera = (id: string) => {
     const existingCamera = cameras.value.find((camera) => camera.id === id);
     if (existingCamera) return;
-    const camImg = new Image();
-    camImg.src = cameraImgSrc;
+    // const camImg = new Image();
+    // camImg.src = cameraImgSrc;
     const config = {
       width: 52,
       height: 37,
@@ -268,7 +247,7 @@
   );
 
   watch(
-    () => cameras,
+    () => cameras.value,
     () => {
       emit('sendCameraId', cameras.value);
     },
@@ -278,6 +257,10 @@
   //添加背景
   const addBg = (imgBg) => {
     bgImgUrl.value = imgBg;
+    if (!bgImgUrl.value) {
+      bgImg.src = null as any as string;
+      return;
+    }
     bgImg.src = urlJoin(globSetting.imgUrl!, imgBg) as string;
     bgImg.onload = () => {
       bgConfig.value.width = bgImg.width;
@@ -342,8 +325,8 @@
   const createMap = (layout) => {
     addBg(layout.bgImgUrl);
     stageConfig.value = layout.stageConfig;
-    const camImg = new Image();
-    camImg.src = cameraImgSrc;
+    // const camImg = new Image();
+    // camImg.src = cameraImgSrc;
     defaultCameraId.value = layout.defaultCameraId;
     layout.cameraList = layout.cameraList?.map((item) => {
       item.config.image = camImg;
@@ -356,6 +339,7 @@
 
   onMounted(() => {
     window.addEventListener('keydown', handleKeyDown);
+    camImg.src = cameraImgSrc;
   });
 
   onBeforeUnmount(() => {
@@ -363,10 +347,7 @@
   });
 </script>
 
-<style scoped>
-  .drawContainer {
-    position: relative;
-  }
+<style scoped lang="scss">
   .opt-container {
     width: 160px;
     padding: 10px;

+ 0 - 58
src/views/map-config/mini-map/MapBase/fabricSetting.ts

@@ -1,58 +0,0 @@
-import { fabric } from 'fabric';
-// import favoritesImg from '@/assets/camera/favorites.png';
-
-export function fabricSetting() {
-  fabric.Object.prototype.padding = 10;
-
-  // 修改控制点的形状,默认为`rect`矩形,可选的值还有`circle`圆形
-  // fabric.Object.prototype.cornerStyle = 'circle';
-
-  // 修改控制点的大小为10px
-  fabric.Object.prototype.cornerSize = 8;
-
-  fabric.Object.prototype.controls.mtr.withConnection = true;
-  // 单独修改旋转控制点距离主体的纵向距离为-20px
-  fabric.Object.prototype.controls.mtr.offsetY = -20;
-
-  // 单独修改旋转控制点,光标移动到该点上时的样式为`pointer`,一个手的形状
-  fabric.Object.prototype.controls.mtr.cursorStyle = 'pointer';
-  /** 边框颜色 */
-  fabric.Object.prototype.borderColor = '#5687f1';
-  // 修改控制点的填充色为白色
-  fabric.Object.prototype.cornerColor = '#5687f1';
-
-  /** 隐藏x y轴的变形,只能等比例变形 */
-  fabric.Object.prototype.controls.mr.visible = false;
-  fabric.Object.prototype.controls.ml.visible = false;
-  fabric.Object.prototype.controls.mt.visible = false;
-  fabric.Object.prototype.controls.mb.visible = false;
-
-  window.fabric = fabric;
-}
-
-// // 渲染元素的icon按钮
-// function renderIcon(icon) {
-//   return function (ctx, left, top, styleOverride, fabricObject) {
-//     const size = this.cornerSize;
-//     ctx.save();
-//     ctx.translate(left, top);
-//     ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
-//     ctx.drawImage(icon, -size / 2, -size / 2, size, size);
-//     ctx.restore();
-//   };
-// }
-// function setControlIcon() {
-//   const favImg = document.createElement('img');
-//   favImg.src = favoritesImg;
-//   // 删除按钮控件
-//   fabric.Object.prototype.controls.favIcon = new fabric.Control({
-//     x: 0.5,
-//     y: -0.5,
-//     offsetY: -16,
-//     offsetX: 26,
-//     cursorStyle: 'pointer',
-//     // mouseUpHandler: deleteObject,
-//     render: renderIcon(favImg),
-//     cornerSize: 24,
-//   });
-// }

+ 0 - 42
src/views/map-config/mini-map/MapBase/mapData.json

@@ -1,42 +0,0 @@
-{
-  "version": "5.3.0",
-  "backgroundImage": {
-    "src": "http://127.0.0.1:5174/src/assets/img/canvasBg.png?t=1700735801636",
-    "type": "image",
-    "version": "5.3.0",
-    "width": 800,
-    "height": 409,
-    "left": 0,
-    "top": 0,
-    "angle": 0
-  },
-  "objects": [
-    {
-      "type": "image",
-      "width": 30,
-      "height": 19,
-      "left": 34.77,
-      "top": 15.88,
-      "angle": 37.64,
-      "cameraId": "1"
-    },
-    {
-      "type": "image",
-      "width": 30,
-      "height": 19,
-      "left": 18.52,
-      "top": 362.26,
-      "angle": 331.41,
-      "cameraId": "2"
-    },
-    {
-      "type": "image",
-      "width": 30,
-      "height": 20,
-      "left": 339.99,
-      "top": 368.21,
-      "angle": 317.8,
-      "cameraId": "3"
-    }
-  ]
-}

+ 0 - 40
src/views/map-config/mini-map/MapBase/types.ts

@@ -1,40 +0,0 @@
-export interface MapData {
-  version: string;
-  backgroundImage: {
-    src: string;
-    type: string;
-    width: number;
-    height: number;
-    left: number;
-    top: number;
-    angle: number;
-  };
-  objects: CameraImgObject[];
-  defaultCameraId: string;
-}
-
-export interface CameraImgObject {
-  type: string;
-  width: number;
-  height: number;
-  left: number;
-  top: number;
-  angle: number;
-  cameraId: string;
-}
-
-export interface CameraImage extends fabric.Image {
-  cameraId?: string;
-}
-
-export type Canvas = fabric.Canvas;
-
-export const isCanvas = (canvas: Canvas | null | undefined): canvas is Canvas => {
-  return Boolean(canvas);
-};
-
-export type OnSelect = (image: CameraImage | null) => unknown;
-export type OnRightClick = (e: fabric.IEvent<MouseEvent>) => unknown;
-export type OnMoving = (e: fabric.IEvent<MouseEvent>) => unknown;
-export type OnRotating = (e: fabric.IEvent<MouseEvent>) => unknown;
-export type SetSelectedCamera = (e: CameraImage | null) => unknown;

+ 0 - 249
src/views/map-config/mini-map/MapBase/useCameraMap.ts

@@ -1,249 +0,0 @@
-import { fabric } from 'fabric';
-import { ref } from 'vue';
-import cameraImg from '@/assets/camera/camera.png';
-import {
-  CameraImage,
-  MapData,
-  OnMoving,
-  OnRightClick,
-  OnSelect,
-  isCanvas,
-  OnRotating,
-  SetSelectedCamera,
-} from './types';
-import { fabricSetting } from './fabricSetting';
-import { getRandomPosition } from './utils';
-
-fabricSetting();
-
-interface Props {
-  onSelect: OnSelect;
-  onRightClick: OnRightClick;
-  onMoving: OnMoving;
-  onRotating: OnRotating;
-  setSelectedCamera: SetSelectedCamera;
-  onObjectsAdded: () => unknown;
-}
-
-function useCameraMap(props: Props) {
-  let canvas;
-
-  const createMap = (canvasId: string) => {
-    canvas = new fabric.Canvas(canvasId, {
-      fireRightClick: true, // 启用右键,button的数字为3
-      stopContextMenu: true, // 禁止默认右键菜单
-    });
-    addListener();
-    // window.canvas = canvas;
-  };
-
-  /** 监听点击事件 */
-  const addListener = () => {
-    if (!isCanvas(canvas)) return;
-
-    canvas.on('mouse:down', (options) => {
-      if (!canvas) return;
-      const target = options.target as CameraImage;
-      const cameraId = target?.cameraId;
-      // console.log('当前选中的id是', cameraId);
-      console.log('mouse:down');
-      console.log(options);
-
-      // 判断:右键,且在元素上右键
-      // opt.button: 1-左键;2-中键;3-右键
-      // 在画布上点击:opt.target 为 null
-      if (options.button === 3 && options.target) {
-        props.onRightClick(options);
-        return;
-      }
-
-      if (!cameraId || !target) {
-        props.onSelect(null);
-        return;
-      }
-
-      props.onSelect(target);
-    });
-
-    canvas.on('object:moving', (e) => {
-      // console.log('object moving', e);
-      props.onMoving(e);
-    });
-    canvas.on('object:rotating', function (e) {
-      props.onRotating(e);
-    });
-
-    /** 监听点击选中的时候 */
-    canvas.on('selection:created', function (e) {
-      console.log('selection created', e);
-      props.setSelectedCamera((e.selected?.[0] as CameraImage) || null);
-    });
-    /** 监听有选中更新的时候 */
-    canvas.on('selection:updated', function (e) {
-      console.log('selection updated', e);
-      props.setSelectedCamera((e.selected?.[0] as CameraImage) || null);
-    });
-    /** 监听选中取消的时候 */
-    canvas.on('selection:cleared', function (e) {
-      console.log('selection cleared', e);
-      props.setSelectedCamera(null);
-    });
-    /** 监听object增加的时候 */
-    canvas.on('object:added', function (e) {
-      console.log('object add', e);
-      props.onObjectsAdded();
-    });
-    /** 监听object删除的时候 */
-    canvas.on('object:removed', function (e) {
-      console.log('object removed', e);
-      props.onObjectsAdded();
-    });
-  };
-
-  /**  上传背景图 */
-  const uploadBg = (imgUrl: string) => {
-    if (!isCanvas(canvas)) return;
-    fabric.Image.fromURL(imgUrl, (img) => {
-      const refImg = ref(img);
-      console.log('image', img);
-      canvas!.setWidth(img.width!);
-      canvas!.setHeight(img.height!);
-      refImg.value.lockScalingX = true;
-      refImg.value.lockScalingY = true;
-      // 设置背景图
-      canvas?.setBackgroundImage(refImg.value, canvas!.renderAll.bind(canvas));
-    });
-  };
-
-  /**  增加一个摄像头 */
-  const addCamera = (cameraId: string): Promise<CameraImage> => {
-    if (!isCanvas(canvas)) return Promise.reject();
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    return new Promise((resolve) => {
-      fabric.Image.fromURL(cameraImg, (img) => {
-        const cImg = ref(img as unknown as CameraImage);
-        cImg.value.set({
-          left: getRandomPosition(),
-          top: getRandomPosition(),
-          cameraId,
-        });
-        cImg.value.lockScalingX = true;
-        cImg.value.lockScalingY = true;
-
-        canvas?.add(cImg.value);
-        canvas?.setActiveObject(cImg.value);
-        // cImg.value.on('moving', function (e) {
-        //   props.onMoving(e);
-        // });
-        // cImg.value.on('rotating', function (e) {
-        //   props.onRotating(e);
-        // });
-
-        resolve(cImg.value);
-      });
-    });
-  };
-
-  /**  删除一个摄像头 */
-  const removeActiveCamera = () => {
-    if (!isCanvas(canvas)) return;
-    const activeObject = canvas?.getActiveObject();
-    if (!activeObject) return;
-    canvas.remove(activeObject);
-  };
-
-  /** 导出JSON格式 */
-  const toJSON = () => {
-    if (!isCanvas(canvas)) return;
-    const initialJSON = canvas.toJSON(['cameraId']);
-    /** toJSON返回值的类型它写错了,应该是有backgroundImage的 */
-    const { src, type, version, width, height, left, top, angle } =
-      (initialJSON as any).backgroundImage || {};
-
-    const newObjects = initialJSON.objects.map((item) => {
-      return {
-        type: item.type,
-        width: item.width,
-        height: item.height,
-        left: item.left,
-        top: item.top,
-        angle: item.angle,
-        cameraId: (item as CameraImage).cameraId,
-      };
-    });
-    const newJson = {
-      version: initialJSON.version,
-      backgroundImage: { src, type, version, width, height, left, top, angle },
-      objects: newObjects,
-    };
-    return newJson;
-  };
-
-  /**  从json中加载 */
-  const loadFromJSON = (json: MapData): Promise<void> => {
-    if (!isCanvas(canvas)) return Promise.reject();
-    canvas.clear();
-    return new Promise((resolve) => {
-      const { width, height } = json.backgroundImage;
-      canvas?.setWidth(width);
-      canvas?.setHeight(height);
-      const objects = json.objects.map((item) => {
-        return {
-          ...item,
-          src: cameraImg,
-        };
-      });
-      canvas?.loadFromJSON({ ...json, objects }, () => {
-        resolve();
-      });
-    });
-  };
-
-  /**  更新摄像头的渲染 */
-  const renderCamera = () => {
-    canvas?.renderAll();
-  };
-  /**  */
-  const clear = () => {
-    canvas?.clear();
-  };
-
-  /**  是否已经存在这个cameraId */
-  const hasCamera = (cameraId: string) => {
-    const cameraIds =
-      canvas?.toJSON(['cameraId']).objects.map((item) => (item as CameraImage).cameraId) || [];
-    return cameraIds.includes(cameraId);
-  };
-
-  const getObjects = () => {
-    return canvas?.getObjects() as CameraImage[];
-  };
-  const getActiveObject = () => {
-    return canvas?.getActiveObject() as CameraImage;
-  };
-
-  /**  根据cameraId查找某个元素 */
-  const getCameraById = (cameraId: string) => {
-    return canvas
-      ?.getObjects()
-      .find((x) => (x as CameraImage).cameraId === cameraId) as CameraImage;
-  };
-
-  return {
-    canvas,
-    createMap,
-    uploadBg,
-    addCamera,
-    removeActiveCamera,
-    toJSON,
-    loadFromJSON,
-    renderCamera,
-    clear,
-    hasCamera,
-    getCameraById,
-    getObjects,
-    getActiveObject,
-  };
-}
-
-export default useCameraMap;

+ 0 - 22
src/views/map-config/mini-map/MapBase/utils.ts

@@ -1,22 +0,0 @@
-import { fabric } from 'fabric';
-export function getRandomPosition() {
-  return 100 + Math.floor(Math.random() * 30);
-}
-
-/** 根据camera位置得到fav icon的位置 */
-export function getFavPositionByCamera(target?: fabric.Object | null) {
-  if (!target || !target.oCoords) return null;
-  return { left: target.oCoords?.tr.x, top: target.oCoords?.tr.y };
-}
-
-export function createSelectedPositionHash(target) {
-  if (!target) return '';
-  return (
-    target.cameraId +
-    String(target.oCoords.tr.x) +
-    '_' +
-    String(target.oCoords.tr.y) +
-    '_' +
-    String(target.angle)
-  );
-}

+ 0 - 319
src/views/map-config/mini-map/MiniMapConfig copy.vue

@@ -1,319 +0,0 @@
-<template>
-  <div class="page" @click="handleWholeClick">
-    <div class="page-head flex items-center">
-      <el-icon size="20"><ArrowLeft /></el-icon>
-      <div class="head-opt flex-1 flex justify-between items-center">
-        <div>
-          <span>场景:</span>
-          <el-tree-select
-            v-model="selectedShopCode"
-            :data="scenesTree"
-            accordion
-            :render-after-expand="false"
-            :default-expand-all="true"
-            :teleported="false"
-            placeholder="请选择相关场景"
-            @change="changeShop"
-          />
-        </div>
-        <div class="flex">
-          <!-- <el-button @click="mapEditor.toJson">tojson</el-button> -->
-          <el-upload
-            class="avatar-uploader flex justify-center items-center"
-            action="/skyeye-admin-api/layout/uploadPicture"
-            :show-file-list="false"
-            :on-success="handleAvatarSuccess"
-            :with-credentials="true"
-            name="file"
-            :data="{ workshopId: selectedShopDetail?.id }"
-          >
-            <el-button style="font-size: 12px" :icon="Refresh" :disabled="!hasBg">
-              替换照片
-            </el-button>
-          </el-upload>
-
-          <el-button
-            @click="handleSave"
-            style="margin-left: 40px"
-            type="primary"
-            :disabled="!selectedShopCode"
-            >保存为布局
-          </el-button>
-        </div>
-      </div>
-    </div>
-    <div class="paint-tool flex">
-      <div class="camera-list">
-        <div>
-          <span class="label-text flex">相机列表:</span>
-          <ElInput
-            class="search-put"
-            style="margin: 10px 0; width: 230px"
-            placeholder="请输入搜索内容"
-            v-model="searchKey"
-            :suffix-icon="Search"
-          />
-        </div>
-        <span v-if="filterShopCameraList.length == 0" class="ml-1" style="color: #3f3f3f">
-          提示:请先选择相应场景和图片
-        </span>
-        <el-scrollbar v-else style="position: relative; height: calc(100% - 90px)">
-          <div
-            v-for="item in filterShopCameraList"
-            :key="item.code"
-            class="camera-item flex justify-start items-center"
-            :class="{
-              isAdded: isAddedCamera(item.code),
-              isActive: item.code === activeCameraId,
-            }"
-            @click="handleAddCamera(item.code)"
-          >
-            <span class="camera-id">{{ item.name }}</span>
-            <el-popover
-              placement="bottom-start"
-              trigger="hover"
-              :content="item.name"
-              :teleported="false"
-            >
-              <template #reference>
-                <span class="space-name">{{ item.workSpaceName }}</span>
-              </template>
-            </el-popover>
-          </div>
-        </el-scrollbar>
-      </div>
-      <div ref="drawContainer" id="drawContainer" class="draw-container">
-        <div id="editContainer" v-moveable:1></div>
-        <el-upload
-          v-if="!hasBg"
-          class="upload-icon flex justify-center items-center"
-          action="/skyeye-admin-api/layout/uploadPicture"
-          :show-file-list="false"
-          :before-upload="handleBeforeUpload"
-          :on-success="handleAvatarSuccess"
-          :with-credentials="true"
-          name="file"
-          :data="{ workshopId: selectedShopDetail?.id }"
-        >
-          <img src="~@/assets/images/img-upload.png" />
-        </el-upload>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-  import useMiniMap from './use-mini-map';
-  import { storeToRefs } from 'pinia';
-  import { ElMessage, ElInput } from 'element-plus';
-  import { onMounted, ref } from 'vue';
-  import { updateMinMapViewLayoutApi } from '@/api/scene/scene';
-  import { computed } from 'vue';
-  import { Search, Refresh } from '@element-plus/icons-vue';
-  import useMapEditor from './hooks/useMapEditor';
-
-  const mapEditor = useMapEditor();
-  const { activeCameraId, addedCameras, bgImgUrl } = mapEditor;
-  const miniMap = useMiniMap();
-  const { scenesTree, shopCameraList, selectedShopCode, selectedShopDetail } = storeToRefs(miniMap);
-  const { getScenesTree, getShowCameras, getMapLayout } = miniMap;
-
-  const drawContainer = ref<HTMLDivElement>();
-
-  const searchKey = ref('');
-  // 是否已有背景图
-  const hasBg = ref(false);
-
-  const handleBeforeUpload = () => {
-    if (!selectedShopCode.value) {
-      ElMessage.error({
-        message: '请先选择车间',
-      });
-      return false;
-    }
-  };
-
-  /** 判断相机是否已经添加 */
-  const isAddedCamera = (cameraId: string) => {
-    const index = addedCameras.value.findIndex((item) => item === cameraId);
-    return index >= 0;
-  };
-
-  const handleAvatarSuccess = (e) => {
-    bgImgUrl.value = e.data;
-    mapEditor.addBg();
-    hasBg.value = true;
-  };
-
-  const changeShop = (code: string) => {
-    mapEditor.resetMap();
-    getShopContent(code);
-    hasBg.value = false;
-  };
-
-  const getShopContent = (code: string) => {
-    getShowCameras(code);
-    getMapLayout(code).then((res) => {
-      if (!res) {
-        return;
-      }
-      hasBg.value = true;
-      mapEditor.createMap(res);
-    });
-  };
-
-  const handleWholeClick = (e) => {
-    if (e.button === 0) {
-      mapEditor.destoryOptBlock();
-    }
-  };
-
-  onMounted(() => {
-    getScenesTree({ level: 2, valueKey: 'code', labelKey: 'name', disabled: true });
-    mapEditor.initContainer({
-      container: 'editContainer',
-      width: drawContainer.value!.clientWidth,
-      height: drawContainer.value!.clientHeight,
-    });
-    if (selectedShopCode.value) {
-      getShopContent(selectedShopCode.value);
-    }
-  });
-
-  const filterShopCameraList = computed(() => {
-    const k = searchKey.value.trim();
-    if (!k) return shopCameraList.value;
-    return shopCameraList.value.filter((x) => x.code?.includes(k) || x.workSpaceName?.includes(k));
-  });
-
-  const handleAddCamera = (cameraId: string) => {
-    console.log('cameraId', cameraId);
-
-    if (!hasBg.value) {
-      ElMessage.warning({
-        message: '请先添加背景图片',
-      });
-      return;
-    }
-    mapEditor.addCamera(cameraId);
-  };
-
-  const handleSave = () => {
-    const layout = mapEditor.toJson();
-    updateMinMapViewLayoutApi({ layout, targetId: String(selectedShopDetail.value?.id) }).then(
-      (res) => {
-        console.log('updateMinMapViewLayoutApi', res);
-        ElMessage.success('保存成功');
-      },
-    );
-  };
-</script>
-
-<style scoped lang="scss">
-  .page {
-  }
-  .page-head {
-    height: 54px;
-    padding-left: 15px;
-    background-color: #ffffff;
-  }
-  .head-opt {
-    margin-left: 20px;
-    padding-right: 15px;
-    font-size: 14px;
-    color: #3f3f3f;
-  }
-
-  .avatar-uploader {
-    /* width: 120px; */
-    /* height: 30px; */
-    /* border: 1px solid #eee; */
-    border-radius: 4px;
-    margin-left: 30px;
-  }
-  .upload-icon {
-    position: absolute;
-    top: 0;
-    right: 0;
-    left: 0;
-    bottom: 0;
-    margin: auto;
-  }
-
-  .paint-tool {
-    position: relative;
-    height: calc(100vh - 138px);
-    margin-top: 2px;
-  }
-
-  .camera-list {
-    width: 250px;
-    padding: 0 10px;
-    background-color: #ffffff;
-  }
-  .label-text {
-    font-size: 14px;
-    font-weight: 600;
-    margin: 10px 0 5px 10px;
-  }
-  .camera-item {
-    height: 32px;
-    font-size: 14px;
-    padding-left: 8px;
-    font-weight: 400;
-    color: #404040;
-    line-height: 14px;
-    cursor: pointer;
-
-    &:hover {
-      background-color: #e6f7ff;
-      color: #1890ff;
-    }
-  }
-  .isAdded {
-    color: #1890ff;
-    cursor: not-allowed;
-  }
-  .isActive {
-    background-color: #e6f7ff;
-    color: #1890ff;
-  }
-  .camera-item-disabled {
-    color: #c6c6c6;
-  }
-  .camera-id {
-    width: 110px;
-  }
-  .space-name {
-    width: 120px;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-
-  .draw-container {
-    position: relative;
-    width: calc(100% - 300px);
-    margin: 20px;
-    overflow: hidden;
-  }
-
-  :deep(.search-put .el-input__wrapper) {
-    background-color: #f0f2f5;
-  }
-  :deep(.el-popper__arrow) {
-    display: none;
-  }
-  :deep(.el-tree-node__content:hover) {
-    background: #e6f4ff;
-  }
-  :deep(.el-button--primary) {
-    --el-button-disabled-bg-color: #bfbfbf;
-  }
-  :deep(.el-popover) {
-    width: unset !important;
-    min-width: 110px;
-    text-align: center;
-    font-weight: 400;
-  }
-</style>

+ 3 - 11
src/views/map-config/mini-map/MiniMapConfig.vue

@@ -17,7 +17,6 @@
           />
         </div>
         <div class="flex">
-          <!-- <el-button @click="mapEditor.toJson">tojson</el-button> -->
           <el-upload
             class="avatar-uploader flex justify-center items-center"
             action="/skyeye-admin-api/layout/uploadPicture"
@@ -82,7 +81,6 @@
           </div>
         </el-scrollbar>
       </div>
-      <!-- <div ref="drawContainer" id="drawContainer" class="draw-container"> -->
       <div ref="drawContainer" class="draw-container">
         <KonvaMap
           ref="konvaMap"
@@ -113,16 +111,12 @@
   import useMiniMap from './use-mini-map';
   import { storeToRefs } from 'pinia';
   import { ElMessage, ElInput } from 'element-plus';
-  import { onMounted, ref, nextTick } from 'vue';
+  import { onMounted, ref } from 'vue';
   import { updateMinMapViewLayoutApi } from '@/api/scene/scene';
   import { computed } from 'vue';
-  import { Search, Refresh } from '@element-plus/icons-vue';
-  // import useMapEditor from './hooks/useMapEditor';
+  import { Search, Refresh, ArrowLeft } from '@element-plus/icons-vue';
   import KonvaMap from './MapBase/KonvaMap.vue';
-  // import { nextTick } from 'process';
 
-  // const mapEditor = useMapEditor();
-  // const { activeCameraId, addedCameras, bgImgUrl } = mapEditor;
   const miniMap = useMiniMap();
   const { scenesTree, shopCameraList, selectedShopCode, selectedShopDetail } = storeToRefs(miniMap);
   const { getScenesTree, getShowCameras, getMapLayout } = miniMap;
@@ -150,12 +144,11 @@
     camerasAdded.value = camerasList.map((item) => {
       return item.id;
     });
-    console.log('camerasAdded.value', camerasAdded.value);
+    // console.log('camerasAdded.value', camerasAdded.value);
   };
 
   /** 判断相机是否已经添加 */
   const isAddedCamera = (cameraId: string) => {
-    // const index = addedCameras.value.findIndex((item) => item === cameraId);
     const index = camerasAdded.value.findIndex((item) => item === cameraId);
     return index >= 0;
   };
@@ -172,7 +165,6 @@
   };
 
   const changeShop = (code: string) => {
-    // mapEditor.resetMap();
     konvaMap.value.resetMap();
     getShopContent(code);
     hasBg.value = false;

+ 0 - 52
src/views/map-config/mini-map/components/CameraOptBar.vue

@@ -1,52 +0,0 @@
-<template>
-  <div class="opt-container">
-    <div class="opt-item" :class="{ disabled: props.disabled }" @click="setCamera">
-      <span>设为默认相机</span>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-  const props = defineProps({
-    disabled: { type: Boolean, default: () => true },
-    onSetDefault: { type: Function },
-  });
-
-  const setCamera = () => {
-    if (!props.disabled) {
-      props.onSetDefault!();
-    }
-  };
-</script>
-
-<style scoped lang="scss">
-  .opt-container {
-    width: 160px;
-    padding: 10px;
-    border-radius: 5px;
-    background-color: #ffffff;
-    box-shadow: 5px 5px 5px #a3a5a5;
-  }
-
-  .opt-item {
-    height: 30px;
-    font-size: 14px;
-    color: #404040;
-    display: flex;
-    justify-content: flex-start;
-    align-items: center;
-    padding-left: 8px;
-    border-radius: 3px;
-    cursor: pointer;
-
-    &:hover {
-      background-color: #f1f2f5;
-    }
-  }
-
-  .disabled {
-    background-color: #f1f2f5;
-    color: #bcbdc0;
-    cursor: not-allowed;
-  }
-</style>

+ 0 - 33
src/views/map-config/mini-map/components/EditDimension.vue

@@ -1,33 +0,0 @@
-<template>
-  <span style="padding-left: 10px">
-    <span>{{ props.label }}: </span>
-    <ElInput
-      style="width: 50px"
-      size="small"
-      @input="handleChange"
-      v-model="val"
-      :disabled="props.disabled"
-    />
-  </span>
-</template>
-<script lang="ts" setup>
-  import { ref, watch } from 'vue';
-  import { ElInput } from 'element-plus';
-  const props = defineProps<{ label: string; modelValue: number; disabled?: boolean }>();
-
-  const val = ref();
-  watch(
-    () => props.modelValue,
-    () => {
-      val.value = Math.floor(props.modelValue);
-    },
-    { immediate: true },
-  );
-
-  const emits = defineEmits<{ (e: 'update:modelValue', val: number): unknown }>();
-
-  const handleChange = (e) => {
-    emits('update:modelValue', Number(e));
-  };
-</script>
-<style scoped></style>

+ 0 - 52
src/views/map-config/mini-map/components/SelectedCameraToolbar.vue

@@ -1,52 +0,0 @@
-<template>
-  <div>
-    已选中相机<span>: {{ selectedCamera?.cameraId }}</span>
-    <span
-      ><EditDimension
-        v-model="selectedCamera.width"
-        label="width"
-        @update:model-value="renderMap"
-        disabled
-    /></span>
-    <span
-      ><EditDimension
-        v-model="selectedCamera.height"
-        label="height"
-        @update:model-value="renderMap"
-        disabled
-    /></span>
-    <span
-      ><EditDimension v-model="selectedCamera.left" label="left" @update:model-value="renderMap"
-    /></span>
-    <span
-      ><EditDimension v-model="selectedCamera.top" label="top" @update:model-value="renderMap"
-    /></span>
-    <span
-      ><EditDimension v-model="selectedCamera.angle" label="angle" @update:model-value="renderMap"
-    /></span>
-  </div>
-</template>
-<script lang="ts" setup>
-  import { ref } from 'vue';
-  import EditDimension from './EditDimension.vue';
-
-  const props = defineProps<{
-    selectedCamera: {
-      cameraId: string;
-      width: number;
-      height: number;
-      left: number;
-      top: number;
-      angle: number;
-    };
-  }>();
-
-  const selectedCamera = ref(props.selectedCamera);
-
-  const emits = defineEmits<{ (e: 'renderMap'): unknown }>();
-
-  const renderMap = () => {
-    emits('renderMap');
-  };
-</script>
-<style scoped></style>

+ 0 - 436
src/views/map-config/mini-map/hooks/useMapEditor.ts

@@ -1,436 +0,0 @@
-import { computed, h, onBeforeUnmount, onMounted, ref, render } from 'vue';
-import Konva from 'konva';
-import cameraImg from '@/assets/camera/cameraImg.png';
-import favoritesImg from '@/assets/camera/favorites.png';
-import OptBar from '../components/CameraOptBar.vue';
-import DefaultTip from '../components/DefaultTip.vue';
-import { TipPositionEnum } from '../type';
-import { ElMessage } from 'element-plus';
-import { useGlobSetting } from '@/hooks/setting';
-import urlJoin from 'url-join';
-import useMiniMap from '../use-mini-map';
-import { storeToRefs } from 'pinia';
-
-export function useMapEditor() {
-  const miniMap = useMiniMap();
-  const { shopCameraList } = storeToRefs(miniMap);
-
-  // let initWidth; // 默认宽度
-  // let initHeight; // 默认高度
-  let stage: Konva.Stage | null = null;
-  let layer: Konva.Layer | null = null;
-  let copyLayer: Konva.Layer | null = null;
-  let defaultIcon: Konva.Image | null = null; // 默认相机的图标shape
-  const addedCameras = ref<string[]>([]); // 已添加相机列表
-  const activeGroup = ref<Konva.Group | null>(null); // transformer激活的相机
-  const defaultCameraId = ref(''); // 默认相机的ID
-  let optBlock: HTMLDivElement | null = null; // 鼠标右击弹出的选项组
-  let defaultTip: HTMLDivElement | null = null; // 默认相机悬浮tip
-  let isTransform = false; // 是否再变换中
-  const activeCameraId = computed(() => activeGroup.value?.id()); // 当前选中相机ID
-  const bgImgUrl = ref<string>('');
-
-  const globSetting = useGlobSetting();
-
-  /** 容器初始化 */
-  const initContainer = (opt: Konva.StageConfig) => {
-    // initWidth = opt.width || 0;
-    // initHeight = opt.height || 0;
-    stage = new Konva.Stage(opt);
-    stage.on('click tap', handleStageClick);
-    window.stage = stage;
-    layer = new Konva.Layer();
-    copyLayer = new Konva.Layer();
-    stage.add(layer);
-    stage.add(copyLayer);
-    addDefaultIcon();
-  };
-
-  /** 初始生成默认相机的图标shape,但不可见 */
-  const addDefaultIcon = () => {
-    const favImg = new Image();
-    favImg.onload = () => {
-      defaultIcon = new Konva.Image({
-        x: 18,
-        y: -16,
-        width: 16,
-        height: 16,
-        image: favImg,
-        id: 'defaultIcon',
-        visible: false,
-        rotation: 0,
-      });
-      bindBaseEvt(defaultIcon);
-      layer?.add(defaultIcon);
-      layer?.batchDraw();
-    };
-    favImg.src = favoritesImg;
-  };
-
-  /** 更换背景图时根据图片大小重置容器宽高 */
-  const resizeContainer = (width, height) => {
-    // const newWidth = width > initWidth ? width : initWidth;
-    // const newHeight = height > initHeight ? height : initHeight;
-    // stage?.width(newWidth);
-    // stage?.height(newHeight);
-    stage?.width(width);
-    stage?.height(height);
-  };
-
-  /** 添加背景 */
-  const addBg = () => {
-    const imgUrl = urlJoin(globSetting.imgUrl!, bgImgUrl.value);
-    const bgNode = layer?.find('#bgImg')[0] as Konva.Image;
-    const bgImg = new Image();
-    bgImg.onload = () => {
-      // 判断是否已有背景
-      if (!bgNode) {
-        const mapBg = new Konva.Image({
-          x: 0,
-          y: 0,
-          image: bgImg,
-          width: bgImg.width,
-          height: bgImg.height,
-          id: 'bgImg',
-        });
-        layer?.add(mapBg);
-        mapBg.moveToBottom();
-      } else {
-        bgNode.width(bgImg.width);
-        bgNode.height(bgImg.height);
-        bgNode.image(bgImg);
-      }
-      resizeContainer(bgImg.width, bgImg.height);
-      layer?.batchDraw();
-    };
-    bgImg.src = imgUrl;
-  };
-
-  /** 变更需要激活transform的相机 */
-  const attachTransformer = (group: Konva.Group): Konva.Transformer => {
-    activeGroup.value?.draggable(false);
-    activeGroup.value = group;
-    group.draggable(true);
-    stage?.find?.('Transformer')[0]?.destroy(); // 清除现有transformer
-    const id = group.id();
-    const tr = new Konva.Transformer({
-      keepRatio: true,
-      rotateAnchorOffset: 30,
-      rotationSnaps: [0, 45, 90, 135, 180, 225, 270, 315],
-      enabledAnchors: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
-      id: 'tr_' + id,
-    });
-    tr.nodes([group]);
-    layer?.add(tr);
-    layer?.draw();
-
-    group.on('dragstart', handleDragStart);
-    group.on('dragstart', handleDragEnd);
-
-    return tr;
-  };
-
-  /** 添加相机 */
-  const addCamera = (id: string) => {
-    const group = new Konva.Group({
-      x: 50,
-      y: 50,
-      id,
-      draggable: true,
-      name: 'group',
-    });
-    const camImg = new Image();
-    camImg.onload = () => {
-      const cameraIcon = new Konva.Image({
-        width: 52,
-        height: 37,
-        image: camImg,
-        name: 'image',
-      });
-      group.add(cameraIcon);
-      layer?.add(group);
-      bindBaseEvt(cameraIcon);
-      const tr = attachTransformer(group); // 添加的相机默认激活transformer
-
-      addedCameras.value.push(id);
-      // 如果是唯一相机,设置为默认相机
-      if (addedCameras.value.length === 1) {
-        defaultIcon?.show();
-        setDefaultCamera(group, tr);
-        tr.forceUpdate();
-      }
-    };
-    camImg.src = cameraImg;
-  };
-
-  /** 变更默认相机 */
-  const setDefaultCamera = (node: Konva.Group, tr?: Konva.Transformer) => {
-    defaultIcon?.moveTo(node);
-    tr?.forceUpdate();
-    defaultCameraId.value = node.id();
-  };
-
-  /** 创建右键选项组 */
-  const createOptBlock = (node: Konva.Group, x: number, y: number) => {
-    const id = node.id();
-    optBlock = document.createElement('div') as HTMLDivElement;
-    optBlock.setAttribute('style', `position: absolute; left: ${x}px; top: ${y}px;`);
-    const optBar = h(OptBar, {
-      disabled: id === defaultCameraId.value,
-      onSetDefault: () => {
-        const tr = layer?.find(`#tr_${id}`)[0] as Konva.Transformer;
-        setDefaultCamera(node, tr);
-        destoryOptBlock();
-      },
-    });
-    render(optBar, optBlock);
-    const parentEl = document.getElementById('drawContainer') as HTMLDivElement;
-    parentEl.append(optBlock);
-  };
-
-  /** 删除右键选项组 */
-  const destoryOptBlock = () => {
-    optBlock?.remove();
-    optBlock = null;
-  };
-
-  /** 创建默认tip */
-  const createDefaultTip = (x: number, y: number, pos: TipPositionEnum) => {
-    if (isTransform) {
-      return;
-    }
-    defaultTip = document.createElement('div') as HTMLDivElement;
-    defaultTip.setAttribute('style', `position: absolute; left: ${x}px; top: ${y}px;`);
-    const tipInstance = h(DefaultTip, { position: pos });
-    render(tipInstance, defaultTip);
-    const parentEl = document.getElementById('drawContainer') as HTMLDivElement;
-    parentEl.append(defaultTip);
-  };
-
-  /** 删除默认tip */
-  const destoryDefaultTip = () => {
-    defaultTip?.remove();
-    defaultTip = null;
-  };
-
-  /** 删除相机 */
-  const deleteCamera = () => {
-    // 判断是否为默认相机,默认相机不允许删除
-    if (activeGroup.value?.id() === defaultCameraId.value) {
-      ElMessage.error({
-        message: '无法删除默认相机',
-      });
-      return;
-    }
-    const index = addedCameras.value.findIndex((item) => item === activeGroup.value?.id());
-    index >= 0 && addedCameras.value.splice(index, 1);
-    activeGroup.value?.destroy();
-    stage!.find('Transformer')[0]?.destroy();
-    layer?.draw();
-  };
-
-  /** 鼠标悬浮事件 */
-  const handleMouseOver = (e) => {
-    // 禁用浏览器默认鼠标事件
-    document.oncontextmenu = () => {
-      return false;
-    };
-    const group = e.target.parent;
-    // 如果悬浮的相机是默认相机,弹出默认tip
-    if (group.id() === defaultCameraId.value) {
-      let pos = TipPositionEnum.TOP;
-      const tipPosition = defaultIcon?.absolutePosition();
-      let x = Number(tipPosition?.x.toFixed(2)) || 0;
-      let y = Number(tipPosition?.y.toFixed(2)) || 0;
-      const angle = group.rotation() >= 0 ? group.rotation() : group.rotation() + 360;
-      if (angle >= 30) {
-        if (angle <= 150) {
-          pos = TipPositionEnum.RIGHT;
-          x += 26;
-          y -= 17;
-        } else if (angle <= 210) {
-          pos = TipPositionEnum.BOTTOM;
-          y += 26;
-          x -= 50;
-        } else {
-          pos = TipPositionEnum.LEFT;
-          x -= 121;
-          y -= 25;
-        }
-      } else {
-        y -= 61;
-        x -= 43;
-      }
-      createDefaultTip(x, y, pos);
-    }
-  };
-
-  /** 鼠标离开事件 */
-  const handleMouseLeave = () => {
-    // 恢复浏览器默认事件
-    document.oncontextmenu = () => {
-      return true;
-    };
-    defaultTip && destoryDefaultTip();
-  };
-
-  /** 开始拖拽事件 */
-  const handleDragStart = () => {
-    isTransform = true;
-    destoryDefaultTip();
-    destoryOptBlock();
-  };
-
-  /** 结束拖拽事件 */
-  const handleDragEnd = () => {
-    isTransform = false;
-  };
-
-  /** 全局点击事件 */
-  const handleStageClick = (e) => {
-    // 点击舞台取消现有激活的transformer
-    if (e.target === stage) {
-      stage!.find('Transformer')[0].destroy();
-      layer!.draw();
-      return;
-    }
-
-    // 判断点击对象是否为相机
-    if (!e.target.hasName('image')) {
-      return;
-    }
-    const parent = e.target.parent;
-    if (!parent.hasName('group')) {
-      return;
-    }
-    const group = e.target.parent;
-    attachTransformer(group);
-    // 判断是否为右键点击
-    if (e.evt.button === 2) {
-      createOptBlock(group, e.evt.offsetX + 20, e.evt.offsetY);
-    }
-  };
-
-  /** 键盘点击事件 */
-  const handleKeyDown = (e) => {
-    // 删除键
-    if (e.keyCode === 46 || e.code === 'Delete') {
-      deleteCamera();
-    }
-  };
-
-  // 基础监听事件绑定
-  const bindBaseEvt = (node: Konva.Node) => {
-    // node.on('transform', handleDragStart);
-    // node.on('transformend', handleDragEnd);
-    node.on('mouseover', handleMouseOver);
-    node.on('mouseleave', handleMouseLeave);
-  };
-
-  /** 输出布局json */
-  const toJson = () => {
-    const json = stage!.toJSON();
-    const cameras = JSON.parse(json)
-      .children[0].children.filter((node) => node.className === 'Group')
-      .map((item) => {
-        return {
-          cameraId: item.attrs.id,
-          rotation: Number((item.attrs.rotation | 0).toFixed(2)),
-          x: Math.round(item.attrs.x | 0),
-          y: Math.round(item.attrs.y | 0),
-          scaleX: Number((item.attrs.scaleX | 1).toFixed(1)),
-          scaleY: Number((item.attrs.scaleY | 1).toFixed(1)),
-          url: shopCameraList.value.find((cam) => cam.code === item.attrs.id)?.pushstreamIp || '',
-        };
-      });
-    const layout = {
-      bgInfo: {
-        bgImg: bgImgUrl.value,
-        width: layer?.find('#bgImg')[0].width(),
-        height: layer?.find('#bgImg')[0].height(),
-      },
-      defaultCameraId: defaultCameraId.value,
-      cameraList: cameras,
-    };
-
-    return JSON.stringify(layout);
-  };
-
-  /** 导入布局json */
-  const createMap = (layout) => {
-    // const layout = JSON.parse(json);
-    bgImgUrl.value = layout.bgInfo.bgImg;
-    addBg();
-    layout.cameraList.forEach((camera) => {
-      const group = new Konva.Group({
-        x: camera.x,
-        y: camera.y,
-        id: camera.cameraId,
-        rotation: camera.rotation,
-        scaleX: camera.scaleX,
-        scaleY: camera.scaleY,
-        draggable: false,
-        name: 'group',
-      });
-      const camImg = new Image();
-      camImg.onload = () => {
-        const cameraIcon = new Konva.Image({
-          width: 52,
-          height: 37,
-          image: camImg,
-          name: 'image',
-        });
-        group.add(cameraIcon);
-        layer?.add(group);
-        bindBaseEvt(cameraIcon);
-        addedCameras.value.push(camera.cameraId);
-
-        if (camera.cameraId === layout.defaultCameraId) {
-          setDefaultCamera(group);
-          defaultIcon?.show();
-        }
-
-        if (addedCameras.value.length === layout.cameraList.length) {
-          layer?.batchDraw();
-        }
-      };
-      camImg.src = cameraImg;
-    });
-  };
-
-  const resetMap = () => {
-    defaultIcon?.moveTo(copyLayer);
-    layer?.clear();
-    layer?.removeChildren();
-    defaultIcon?.moveTo(layer);
-    addedCameras.value = [];
-    activeGroup.value = null;
-    defaultCameraId.value = '';
-    isTransform = false;
-    bgImgUrl.value = '';
-  };
-
-  onMounted(() => {
-    window.addEventListener('keydown', handleKeyDown);
-  });
-
-  onBeforeUnmount(() => {
-    window.removeEventListener('keydown', handleKeyDown);
-  });
-
-  return {
-    defaultCameraId,
-    activeCameraId,
-    addedCameras,
-    bgImgUrl,
-    initContainer,
-    addBg,
-    addCamera,
-    destoryOptBlock,
-    toJson,
-    createMap,
-    resetMap,
-  };
-}
-
-export default useMapEditor;

+ 21 - 0
src/views/map-config/mini-map/type.ts

@@ -4,3 +4,24 @@ export enum TipPositionEnum {
   BOTTOM = 'bottom',
   LEFT = 'left',
 }
+
+export interface camerasImgType {
+  x?: number;
+  y?: number;
+  width?: number;
+  height?: number;
+  draggable?: boolean;
+  rotation?: number;
+  scaleX?: number;
+  scaleY?: number;
+  image?: HTMLImageElement;
+  name?: string;
+  url?: string;
+}
+
+export interface camerasGroupType {
+  id?: string;
+  groupConfig: camerasImgType;
+  config: camerasImgType;
+  isDefault?: boolean;
+}

+ 6 - 6
src/views/system/tenant/CreateDrawer.vue

@@ -69,13 +69,13 @@
       required: true,
       message: '请选择开始时间',
       trigger: 'change',
-      type: 'number',
+      type: 'date',
     },
     endDate: {
       required: true,
       message: '请选择结束时间',
       trigger: 'change',
-      type: 'number',
+      type: 'date',
     },
   };
 
@@ -130,8 +130,8 @@
         return message.error('请填写完整信息');
       }
       const params = cloneDeep(formParams.value);
-      params.beginDate = formatToDateTime(params.beginDate);
-      params.endDate = formatToDateTime(params.endDate);
+      params.beginDate = formatToDateTime(params.beginDate || '');
+      params.endDate = formatToDateTime(params.endDate || '');
       if (formParams.value.tenantId) {
         editTenant(params).then((_) => {
           message.success('编辑成功');
@@ -161,8 +161,8 @@
         tenantId: res.tenantId,
         tenantName: res.tenantName,
         tenantCode: res.tenantCode,
-        beginDate: new Date(res.beginDate).getTime(),
-        endDate: new Date(res.endDate).getTime(),
+        beginDate: new Date(res.beginDate).getTime().toString(),
+        endDate: new Date(res.endDate).getTime().toString(),
         tenantStatus: res.tenantStatus,
       };
       formParams.value = info;

+ 2 - 2
src/views/system/tenant/types/index.ts

@@ -2,7 +2,7 @@ export interface formParamsType {
   tenantId?: number | undefined;
   tenantName: string;
   tenantCode: string;
-  beginDate: number | undefined;
-  endDate: number | undefined;
+  beginDate: string | undefined;
+  endDate: string | undefined;
   tenantStatus: number | undefined;
 }

+ 5 - 1
tsconfig.json

@@ -43,6 +43,10 @@
     "src/**/*.ts",
     "src/**/*.d.ts",
     "src/**/*.tsx",
+    "src/**/*.svg",
+    "src/**/*.png",
+    "src/**/*.jpg",
+    "src/**/*.gif",
     "src/**/*.vue",
     "types/**/*.d.ts",
     "types/**/*.ts",
@@ -57,4 +61,4 @@
     "dist",
     "**/*.js"
   ]
-}
+}

+ 17 - 1
types/config.d.ts

@@ -63,7 +63,7 @@ export interface GlobEnvConfig {
   VITE_GLOB_API_URL: string;
   // 接口前缀
   VITE_GLOB_API_URL_PREFIX?: string;
-  // Project abbreviation
+  // 项目简称
   VITE_GLOB_APP_SHORT_NAME: string;
   // 图片上传地址
   VITE_GLOB_UPLOAD_URL?: string;
@@ -72,3 +72,19 @@ export interface GlobEnvConfig {
   //生产环境开启mock
   VITE_GLOB_PROD_MOCK: boolean;
 }
+export interface GlobConfig {
+  // 标题
+  title: string;
+  // 接口地址
+  apiUrl: string;
+  // 图片上传地址
+  uploadUrl?: string;
+  // api 接口前缀
+  urlPrefix?: string;
+  // 项目简称
+  shortName: string;
+  // 生产环境开启 mock
+  prodMock: boolean;
+  // 图片访问地址
+  imgUrl: string | undefined;
+}

+ 7 - 0
types/global.d.ts

@@ -22,6 +22,13 @@ declare global {
   //   __APP__: App<Element>;
   // }
 
+  // 此处 重新定义 ImportMeta 避免 ts 类型报错
+  // 目前框架只用到 env 和 glob
+  interface ImportMeta {
+    env: Record<string, string>;
+    glob: Record<function>;
+  }
+
   // vue
   declare type PropType<T> = VuePropType<T>;
   declare type VueNode = VNodeChild | JSX.Element;

+ 1 - 0
vite.config.ts

@@ -55,6 +55,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
       __APP_INFO__: JSON.stringify(__APP_INFO__),
     },
     css: {
+      devSourcemap: true,
       preprocessorOptions: {
         scss: {
           modifyVars: {},