Ver código fonte

Merge branch 'master' into system-assemble

sunhongyao341504 2 anos atrás
pai
commit
a846be0994
47 arquivos alterados com 2767 adições e 368 exclusões
  1. 3 3
      .env
  2. 3 3
      .env.development
  3. 3 3
      .env.production
  4. 16 16
      mock/comtemp/article.ts
  5. 6 6
      mock/comtemp/make.ts
  6. 15 15
      mock/comtemp/video.ts
  7. 14 0
      mock/login/routers.ts
  8. 1 1
      package.json
  9. 42 5
      src/api/camera/camera-preview.ts
  10. 96 0
      src/api/datamanagement/dataplatform.ts
  11. 189 0
      src/api/template/template.ts
  12. 6 6
      src/enums/avatarEnum.ts
  13. 2 10
      src/layout/components/Header/NotifierProPlus.vue
  14. 6 3
      src/utils/dateUtil.ts
  15. 2 1
      src/utils/http/axios/index.ts
  16. 13 7
      src/views/cameras/preview/CameraPreview.vue
  17. 7 11
      src/views/cameras/preview/components/AlgorithmsSetting/AddAlgoDialog.vue
  18. 20 48
      src/views/cameras/preview/components/AlgorithmsSetting/AlgoSettingCard.vue
  19. 13 2
      src/views/cameras/preview/components/AlgorithmsSetting/AlgoTag.vue
  20. 38 20
      src/views/cameras/preview/components/AlgorithmsSetting/AlgorithmsSetting.vue
  21. 6 1
      src/views/cameras/preview/components/AlgorithmsSetting/utils.ts
  22. 54 0
      src/views/cameras/preview/components/CameraDirectionControl/CameraDirectionControl.vue
  23. 70 0
      src/views/cameras/preview/components/CameraDirectionControl/DirectionItem.vue
  24. 14 3
      src/views/cameras/preview/components/CameraLiveVideo/CameraLiveVideo.vue
  25. 59 52
      src/views/cameras/preview/components/CameraParams/CameraParams.vue
  26. 10 0
      src/views/cameras/preview/components/CameraParams/types.ts
  27. 82 16
      src/views/cameras/preview/components/CameraTree/CameraTree.vue
  28. 64 43
      src/views/cameras/preview/components/CameraViewSetting/CameraViewSetting.vue
  29. 33 16
      src/views/cameras/preview/components/FenceEditor/FenceEditor.vue
  30. 2 46
      src/views/cameras/preview/components/FenceToolbar/FenceToolbar.vue
  31. 2 1
      src/views/cameras/preview/components/PresetSelect/PresetSelect.vue
  32. 1 1
      src/views/cameras/preview/components/ToolbarIcon/ToolbarIcon.vue
  33. 18 27
      src/views/cameras/preview/store/useCameraAlgoStore.ts
  34. 31 2
      src/views/cameras/preview/store/useCameraDetailStore.ts
  35. 46 0
      src/views/datamanager/systemdata/PlatformData.vue
  36. 129 0
      src/views/datamanager/systemdata/Score.vue
  37. 129 0
      src/views/datamanager/systemdata/Table.vue
  38. 253 0
      src/views/datamanager/systemdata/TableCommon.vue
  39. 400 0
      src/views/datamanager/systemdata/TableEcharts.vue
  40. 33 0
      src/views/datamanager/systemdata/config.ts
  41. 207 0
      src/views/system-config/template/CardCommon.vue
  42. 113 0
      src/views/system-config/template/DrawerCommon.vue
  43. 121 0
      src/views/system-config/template/LabelManager.vue
  44. 121 0
      src/views/system-config/template/SceneManager.vue
  45. 50 0
      src/views/system-config/template/TemplateManager.vue
  46. 102 0
      src/views/system-config/template/TitleCommon.vue
  47. 122 0
      src/views/system-config/template/WorkspaceManager.vue

+ 3 - 3
.env

@@ -2,10 +2,10 @@
 VITE_PORT = 8001
 
 # spa-title
-VITE_GLOB_APP_TITLE = cloud-admin
+VITE_GLOB_APP_TITLE = skyeye-admin
 
 # spa shortname
-VITE_GLOB_APP_SHORT_NAME = NaiveAdminElementTenant
+VITE_GLOB_APP_SHORT_NAME = SkyeyeAdmin
 
 # 生产环境 开启mock
-VITE_GLOB_PROD_MOCK = true
+VITE_GLOB_PROD_MOCK = false

+ 3 - 3
.env.development

@@ -2,7 +2,7 @@
 VITE_PORT = 8092
 
 # 网站根目录
-VITE_PUBLIC_PATH = /
+VITE_PUBLIC_PATH = /skyeye-admin/
 
 # 是否开启mock
 VITE_USE_MOCK = false
@@ -16,7 +16,7 @@ VITE_DROP_CONSOLE = true
 # 跨域代理,可以配置多个,请注意不要换行
 #VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
 # VITE_PROXY=[["/temp","http://172.16.23.144:8800"],["/upload","http://172.16.23.144:8086"]]
-VITE_PROXY=[["/api","http://172.16.23.144:8800/api"]]
+VITE_PROXY=[["/skyeye-admin-api","http://172.16.23.144:8800/api"]]
 
 # API 接口地址
 VITE_GLOB_API_URL = 
@@ -28,4 +28,4 @@ VITE_GLOB_IMG_URL = //172.16.23.144/tiancangstatic
 
 
 # 接口前缀
-VITE_GLOB_API_URL_PREFIX = /api
+VITE_GLOB_API_URL_PREFIX = /skyeye-admin-api

+ 3 - 3
.env.production

@@ -2,14 +2,14 @@
 VITE_USE_MOCK = false
 
 # 网站根目录
-VITE_PUBLIC_PATH = /
+VITE_PUBLIC_PATH = /skyeye-admin/
 
 
 # 是否删除console
 VITE_DROP_CONSOLE = true
 
 # API
-VITE_GLOB_API_URL = http://172.16.23.144:8086
+VITE_GLOB_API_URL = 
 
 # 图片上传地址
 VITE_GLOB_UPLOAD_URL= http://172.16.23.144:8086
@@ -18,7 +18,7 @@ VITE_GLOB_UPLOAD_URL= http://172.16.23.144:8086
 VITE_GLOB_IMG_URL= http://172.16.23.144:8086
 
 # 接口前缀
-VITE_GLOB_API_URL_PREFIX = /api
+VITE_GLOB_API_URL_PREFIX = /skyeye-admin-api
 
 # 是否启用gzip压缩或brotli压缩
 # 可选: gzip | brotli | none

+ 16 - 16
mock/comtemp/article.ts

@@ -2,25 +2,25 @@ import { Random } from 'mockjs';
 import { resultSuccess, doCustomTimes } from '../_util';
 
 const avatarList = [
-  'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-6.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-6.jpg',
 ];
 
 const coverList = [
-  'https://img.naiveadmin.com/assets/article/1.jpeg',
-  'https://img.naiveadmin.com/assets/article/2.jpeg',
-  'https://img.naiveadmin.com/assets/article/3.jpeg',
-  'https://img.naiveadmin.com/assets/article/4.jpg',
-  'https://img.naiveadmin.com/assets/article/5.jpeg',
-  'https://img.naiveadmin.com/assets/article/6.jpeg',
-  'https://img.naiveadmin.com/assets/article/7.jpeg',
-  'https://img.naiveadmin.com/assets/article/8.jpeg',
-  'https://img.naiveadmin.com/assets/article/9.jpeg',
-  'https://img.naiveadmin.com/assets/article/10.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/1.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/2.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/3.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/4.jpg',
+  // 'https://img.naiveadmin.com/assets/article/5.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/6.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/7.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/8.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/9.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/10.jpeg',
 ];
 
 const articleList = (pageSize) => {

+ 6 - 6
mock/comtemp/make.ts

@@ -2,12 +2,12 @@ import { Random } from 'mockjs';
 import { resultSuccess, doCustomTimes } from '../_util';
 
 const avatarList = [
-  'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
-  'https://img.naiveadmin.com/assets/avatar/avatar-6.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
+  // 'https://img.naiveadmin.com/assets/avatar/avatar-6.jpg',
 ];
 
 const makeList = (pageSize) => {

+ 15 - 15
mock/comtemp/video.ts

@@ -4,37 +4,37 @@ import { resultSuccess, doCustomTimes } from '../_util';
 const avatargroupList = [
   {
     name: '张三',
-    src: 'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
+    // src: 'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
   },
   {
     name: '李四',
-    src: 'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
+    // src: 'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
   },
   {
     name: '王五',
-    src: 'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
+    // src: 'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
   },
   {
     name: '赵六',
-    src: 'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
+    // src: 'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
   },
   {
     name: '七仔',
-    src: 'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
+    // src: 'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
   },
 ];
 
 const coverList = [
-  'https://img.naiveadmin.com/assets/article/1.jpeg',
-  'https://img.naiveadmin.com/assets/article/2.jpeg',
-  'https://img.naiveadmin.com/assets/article/3.jpeg',
-  'https://img.naiveadmin.com/assets/article/4.jpg',
-  'https://img.naiveadmin.com/assets/article/5.jpeg',
-  'https://img.naiveadmin.com/assets/article/6.jpeg',
-  'https://img.naiveadmin.com/assets/article/7.jpeg',
-  'https://img.naiveadmin.com/assets/article/8.jpeg',
-  'https://img.naiveadmin.com/assets/article/9.jpeg',
-  'https://img.naiveadmin.com/assets/article/10.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/1.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/2.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/3.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/4.jpg',
+  // 'https://img.naiveadmin.com/assets/article/5.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/6.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/7.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/8.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/9.jpeg',
+  // 'https://img.naiveadmin.com/assets/article/10.jpeg',
 ];
 
 const videoList = (pageSize) => {

+ 14 - 0
mock/login/routers.ts

@@ -212,6 +212,20 @@ const list = [
         },
         children: null,
       },
+      {
+        path: 'template',
+        name: 'template',
+        component: '/system-config/template/TemplateManager',
+        meta: {
+          title: '模板管理',
+          noCache: false,
+          hidden: false,
+          isFrame: '1',
+          status: '0',
+          isRoot: false,
+          alwaysShow: false,
+        },
+      },
     ],
   },
   {

+ 1 - 1
package.json

@@ -152,4 +152,4 @@
       ]
     }
   }
-}
+}

+ 42 - 5
src/api/camera/camera-preview.ts

@@ -73,8 +73,8 @@ export interface CameraAlgoItem {
 }
 
 /** 查询某个camera下的所有算法 */
-export const getCameraAlgoListApi = (cameraId: number) => {
-  return http.request<CameraAlgoItem[]>({
+export const getCameraAlgoListApi = (cameraId: number): Promise<CameraAlgoItem[]> => {
+  return http.request({
     url: '/cameraPreview/getAlgo',
     method: 'get',
     params: { cameraId },
@@ -89,8 +89,14 @@ interface SaveCameraAlgoParam {
   electronicFence: string;
   status: 0 | 1;
 }
+
+interface CreateCameraAlgoParam {
+  algoIds: number[];
+  cameraId: number;
+}
+
 /** 保存相机的某个算法 */
-export const createCameraAlgoApi = (param: SaveCameraAlgoParam) => {
+export const createCameraAlgoApi = (param: CreateCameraAlgoParam) => {
   return http.request({
     url: '/cameraPreview/saveAlgo',
     data: param,
@@ -235,7 +241,7 @@ export const getPresetListApi = (cameraId: number) => {
 };
 
 /** 跳转到对应的预置位 */
-export const goToPresetApi = (data: { presetToken: string; cameraId: string }) => {
+export const goToPresetApi = (data: { presetToken: string; cameraId: number }) => {
   return http.request({
     url: `/camera/gotoPreset`,
     method: 'post',
@@ -244,7 +250,7 @@ export const goToPresetApi = (data: { presetToken: string; cameraId: string }) =
 };
 
 interface CameraMoveParam {
-  cameraId: string;
+  cameraId: number;
   x: number;
   zoom: number;
   y: number;
@@ -258,3 +264,34 @@ export const cameraMoveApi = (data: CameraMoveParam) => {
     data,
   });
 };
+
+interface SaveCameraParams {
+  startTime: string;
+  endTime: string;
+  imageResolution: string;
+  recordPeriod: number;
+  /** 这个要调整一下 */
+  reservation: string;
+  cameraId: number;
+}
+
+export const saveCameraParamsApi = (data: SaveCameraParams) => {
+  localStorage.setItem('cameraParams' + data.cameraId, JSON.stringify(data));
+  return Promise.resolve();
+};
+
+export const getCameraParamsApi = (cameraId: number) => {
+  const data = localStorage.getItem('cameraParams' + cameraId);
+  let jsonData = {
+    startTime: '',
+    endTime: '',
+    imageResolution: 1,
+    recordPeriod: 20,
+    /** 这个要调整一下 */
+    reservation: '10',
+  };
+  if (data) {
+    jsonData = JSON.parse(data);
+  }
+  return Promise.resolve(jsonData);
+};

+ 96 - 0
src/api/datamanagement/dataplatform.ts

@@ -0,0 +1,96 @@
+import { http } from '@/utils/http/axios';
+
+
+
+export interface Records {
+    deptId: number,
+    deptName: string,
+    monthVisits: number,
+    nickName: string,
+    staffNo: string,
+    todayVisits: number,
+    totalVisits: number,
+    userId: number
+}
+
+
+export interface Visits<Records> {
+    pageNumber: number,
+    pageSize: number,
+    records: Records[],
+    totalPage: number,
+    totalRow: number
+}
+
+
+export interface MonthVisit {
+    visits: number,
+    workshopName: string,
+}
+
+export interface DayVisit {
+    visits: number,
+    workshopName: string,
+}
+
+export interface AllVisit {
+    visits: number,
+    workshopName: string,
+}
+
+export interface VisitsModel {
+    workshopName: string,
+    visits: number,
+}
+
+// 查询访问次数列表
+export function getList(pageNumber: number, pageSize: number) {
+    return http.request<Visits<Records>>({
+        url: '/platformData/getList',
+        method: 'get',
+        params: { pageNumber: pageNumber, pageSize: pageSize },
+    });
+}
+
+
+
+// 查询本月访问次数
+export function getMonthVisits(userId: number) {
+    return http.request<VisitsModel[]>({
+        url: '/platformData/getMonthVisits',
+        method: 'get',
+        params: { userId: userId },
+    });
+}
+
+
+// 查询个人访问次数
+export function getPersonalVisits(pageNumber: number, pageSize: number, staffNo?: string, deptId?: number, nickName?: string) {
+    return http.request<Visits<Records>>({
+        url: '/platformData/getPersonalVisits',
+        method: 'get',
+        params: { pageNumber: pageNumber, pageSize: pageSize, staffNo: staffNo, deptId: deptId, nickName: nickName },
+    });
+}
+
+
+
+
+
+// 查询今日访问次数
+export function getTodayVisits(userId: number) {
+    return http.request<VisitsModel[]>({
+        url: '/platformData/getTodayVisits',
+        method: 'get',
+        params: { userId: userId },
+    });
+}
+
+// 查询累计访问次数
+export function getTotalVisits(userId: number) {
+    return http.request<VisitsModel[]>({
+        url: '/platformData/getTotalVisits',
+        method: 'get',
+        params: { userId: userId },
+    });
+}

+ 189 - 0
src/api/template/template.ts

@@ -0,0 +1,189 @@
+import { http } from '@/utils/http/axios';
+
+export interface SceneModule {
+    code: string,
+    // createdAt: string,
+    id: number,
+    // isDeleted: number,
+    name: string,
+    remark: string,
+    status: number,
+    // updatedAt: string,
+}
+
+export interface SceneLabel {
+    code: string,
+    // createdAt: string,
+    id: number,
+    // isDeleted: number,
+    name: string,
+    remark: string,
+    status: number,
+    // updatedAt: string,
+}
+
+export interface WorkshopModule {
+    code: string,
+    // createdAt: string,
+    id: number,
+    // isDeleted: number,
+    name: string,
+    remark: string,
+    status: number,
+    // updatedAt: string,
+}
+
+export interface Records {
+    code: string,
+    createdAt: string,
+    id: number,
+    isDeleted: number,
+    name: string,
+    remark: string,
+    status: number,
+    updatedAt: string,
+}
+
+export interface Response<Records> {
+    pageNumber: number,
+    pageSize: number,
+    records: Records[],
+    totalPage: number,
+    totalRow: number,
+}
+
+
+// 查询所有场景模板
+export function getSceneModuleList(pageNumber: number, pageSize: number) {
+    return http.request<Response<Records>>({
+        url: '/template/findSceneModule',
+        method: 'get',
+        params: { pageNumber: pageNumber, pageSize: pageSize },
+    });
+}
+
+// 根据场景名称查询场景模板
+export function findSceneModuleByName(sceneModuleName: string, pageNumber: number, pageSize: number) {
+    return http.request<Response<Records>>({
+        url: '/template/findSceneModule',
+        method: 'get',
+        params: { pageNumber: pageNumber, pageSize: pageSize, sceneModuleName: sceneModuleName, },
+    });
+}
+
+
+// 查询所有场景标签模板
+export function getSceneLabelList(pageNumber: number, pageSize: number) {
+    return http.request<Response<Records>>({
+        url: '/template/findSceneLabel',
+        method: 'get',
+        params: { pageNumber: pageNumber, pageSize: pageSize },
+    });
+}
+
+// 根据场景标签名称查询场景标签模板
+export function findSceneLabelByName(sceneLabelName: string, pageNumber: number, pageSize: number) {
+    return http.request<Response<Records>>({
+        url: '/template/findSceneLabel',
+        method: 'get',
+        params: { sceneLabelName: sceneLabelName, pageNumber: pageNumber, pageSize: pageSize },
+    });
+}
+
+// 查询所有车间模板
+export function getWorkshopModuleList(pageNumber: number, pageSize: number) {
+    return http.request<Response<Records>>({
+        url: '/template/findWorkshopModule',
+        method: 'get',
+        params: { pageNumber: pageNumber, pageSize: pageSize },
+    });
+}
+
+// 根据车间名称查询车间模板
+export function findWorkshopModuleByName(workshopModuleName: string, pageNumber: number, pageSize: number) {
+    return http.request<Response<Records>>({
+        url: '/template/findWorkshopModule',
+        method: 'get',
+        params: { workshopModuleName: workshopModuleName, pageNumber: pageNumber, pageSize: pageSize },
+    });
+}
+
+// 添加场景模板
+export function saveSceneModule(sceneModule: SceneModule) {
+    return http.request({
+        url: '/template/saveSceneModule',
+        method: 'post',
+        params: sceneModule,
+    });
+}
+
+// 添加场景标签模板
+export function saveSceneLabel(sceneLabel: SceneLabel) {
+    return http.request({
+        url: '/template/saveSceneLabel',
+        method: 'post',
+        params: sceneLabel,
+    });
+}
+
+// 添加车间模板
+export function saveWorkshopModule(workshopModule: WorkshopModule) {
+    return http.request({
+        url: '/template/saveWorkshopModule',
+        method: 'post',
+        params: workshopModule,
+    });
+}
+
+// 删除场景模板
+export function deleteSceneModule(sceneModuleId: number) {
+    return http.request({
+        url: `/template/deleteSceneModule?sceneModuleId=${sceneModuleId}`,
+        method: 'delete',
+    });
+}
+
+// 删除场景标签
+export function deleteSceneLabel(sceneLabelId: number) {
+    return http.request({
+        url: `/template/deleteSceneLabel?sceneLabelId=${sceneLabelId}`,
+        method: 'delete',
+
+    });
+}
+
+// 删除车间模板
+export function deleteWorkshopModule(workshopModuleId: number) {
+    return http.request({
+        url: `/template/deleteWorkshopModule?workshopModuleId=${workshopModuleId}`,
+        method: 'delete',
+
+    });
+}
+
+// 编辑场景模板
+export function updateSceneModule(sceneModule: SceneModule) {
+    return http.request({
+        url: '/template/updateSceneModule',
+        method: 'put',
+        params: sceneModule,
+    });
+}
+
+// 编辑场景标签
+export function updateSceneLabel(sceneLabel: SceneLabel) {
+    return http.request({
+        url: '/template/updateSceneLabel',
+        method: 'put',
+        params: sceneLabel,
+    });
+}
+
+// 编辑车间模板
+export function updateWorkshopModule(workshopModule: WorkshopModule) {
+    return http.request({
+        url: '/template/updateWorkshopModule',
+        method: 'put',
+        params: workshopModule,
+    });
+}

+ 6 - 6
src/enums/avatarEnum.ts

@@ -1,8 +1,8 @@
 export enum avatarEnum {
-  avatarImg1 = 'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
-  avatarImg2 = 'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
-  avatarImg3 = 'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
-  avatarImg4 = 'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
-  avatarImg5 = 'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
-  avatarImg6 = 'https://img.naiveadmin.com/assets/avatar/avatar-6.jpg',
+  // avatarImg1 = 'https://img.naiveadmin.com/assets/avatar/avatar-1.jpg',
+  // avatarImg2 = 'https://img.naiveadmin.com/assets/avatar/avatar-2.jpg',
+  // avatarImg3 = 'https://img.naiveadmin.com/assets/avatar/avatar-3.jpg',
+  // avatarImg4 = 'https://img.naiveadmin.com/assets/avatar/avatar-4.jpg',
+  // avatarImg5 = 'https://img.naiveadmin.com/assets/avatar/avatar-5.jpg',
+  // avatarImg6 = 'https://img.naiveadmin.com/assets/avatar/avatar-6.jpg',
 }

+ 2 - 10
src/layout/components/Header/NotifierProPlus.vue

@@ -15,11 +15,7 @@
       <el-tab-pane label="通知(1)" name="notice">
         <ul class="mx-3">
           <li class="flex items-center py-3 border-line" v-for="i in 3" :key="i">
-            <el-avatar
-              class="flex-shrink-0"
-              :size="40"
-              :src="`https://img.naiveadmin.com/assets/avatar/avatar-4.jpg`"
-            />
+            <el-avatar class="flex-shrink-0" :size="40" />
             <dl class="min-w-0 ml-3">
               <dt class="mb-1 text-sm truncate"
                 >约翰.维尔逊回复了你的邮件约翰.维尔逊回复了你的邮件</dt
@@ -37,11 +33,7 @@
       <el-tab-pane label="关注(2)" name="follow">
         <ul class="mx-3">
           <li class="flex items-center py-3 border-line" v-for="i in 3" :key="i">
-            <el-avatar
-              class="flex-shrink-0"
-              :size="40"
-              :src="`https://img.naiveadmin.com/assets/avatar/avatar-5.jpg`"
-            />
+            <el-avatar class="flex-shrink-0" :size="40" />
             <dl class="min-w-0 ml-3">
               <dt class="mb-1 text-sm truncate">约翰.维尔逊关注了你</dt>
               <dd class="text-xs c-gray">2022-01-08</dd>

+ 6 - 3
src/utils/dateUtil.ts

@@ -1,12 +1,15 @@
-import dayjs from 'dayjs';
+import dayjs, { Dayjs } from 'dayjs';
 
 const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
 const DATE_FORMAT = 'YYYY-MM-DD ';
 
-export function formatToDateTime(date: Date, formatStr = DATE_TIME_FORMAT): string {
+export function formatToDateTime(
+  date: Date | Dayjs | string,
+  formatStr = DATE_TIME_FORMAT,
+): string {
   return dayjs(date).format(formatStr);
 }
 
-export function formatToDate(date: Date, formatStr = DATE_FORMAT): string {
+export function formatToDate(date: Date | Dayjs | string, formatStr = DATE_FORMAT): string {
   return dayjs(date).format(formatStr);
 }

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

@@ -124,7 +124,8 @@ const transform: AxiosTransform = {
         })
           .then(() => {
             storage.clear();
-            window.location.href = LoginPath;
+            window.location.href = import.meta.env.VITE_PUBLIC_PATH + '#login';
+            window.location.reload();
           })
           .catch(() => {});
         break;

+ 13 - 7
src/views/cameras/preview/CameraPreview.vue

@@ -1,13 +1,13 @@
 <template>
   <div>
-    <h2> 相机预览 </h2>
     <div class="cameraMain">
       <div class="cameraTree">
-        <CameraTree :data="data" v-if="data" />
+        <CameraTree />
       </div>
       <div class="cameraSettingWrapper">
         <div class="cameraView">
-          <CameraViewSetting />
+          <CameraViewSetting v-if="cameraDetailStore.cameraId" />
+          <div class="cameraPlaceholder" v-else>请选择左侧相机</div>
         </div>
       </div>
     </div>
@@ -17,8 +17,8 @@
 <script lang="ts" setup>
   import CameraTree from './components/CameraTree/CameraTree.vue';
   import CameraViewSetting from './components/CameraViewSetting/CameraViewSetting.vue';
-  import useCameraTree from './hooks/useCameraTree';
-  const { data, loading } = useCameraTree();
+  import useCameraDetailStore from './store/useCameraDetailStore';
+  const cameraDetailStore = useCameraDetailStore();
 </script>
 <style lang="scss" scoped>
   .cameraView {
@@ -50,7 +50,13 @@
   }
   .cameraTree {
     width: 250px;
-    height: 800px;
-    border: 1px solid #ccc;
+    // height: 800px;
+    // border: 1px solid #ccc;
+  }
+
+  .cameraPlaceholder {
+    color: #ccc;
+    text-align: center;
+    margin-top: 100px;
   }
 </style>

+ 7 - 11
src/views/cameras/preview/components/AlgorithmsSetting/AddAlgoDialog.vue

@@ -4,7 +4,7 @@
   >
 
   <ElDialog v-model="visible" title="添加算法" @close="handleClose" width="500px">
-    <ElSelect v-model="selectedId" style="width: 150px" size="small">
+    <ElSelect v-model="selectedIds" multiple style="width: 100%" size="small">
       <ElOption
         v-for="item in allAlgoList"
         :key="item.id"
@@ -30,7 +30,7 @@
   import { storeToRefs } from 'pinia';
   import { createCameraAlgoApi } from '@/api/camera/camera-preview';
   import useCameraDetailStore from '../../store/useCameraDetailStore';
-  const selectedId = ref<number>();
+  const selectedIds = ref<number[]>([]);
   const cameraAlgoStore = useCameraAlgoStore();
 
   const { isAlgoBind } = cameraAlgoStore;
@@ -44,20 +44,16 @@
   };
 
   const handleSubmit = () => {
-    if (!selectedId.value) {
+    if (selectedIds.value?.length < 1) {
+      ElMessage.warning({ message: '请选择算法' });
       return;
     }
-
     createCameraAlgoApi({
-      algoId: selectedId.value,
+      algoIds: selectedIds.value || [],
       cameraId: cameraDetailStore.cameraId,
-      status: 0,
-      detectionFrequency: 0,
-      detectionTime: '',
-      electronicFence: '',
     }).then((res) => {
       console.log('createAlgo ok', res);
-      selectedAlgoId.value = selectedId.value;
+      selectedAlgoId.value = selectedIds.value?.[0];
       getCameraAlgoList(cameraDetailStore.cameraId);
       visible.value = false;
       ElMessage.success('添加算法成功');
@@ -66,7 +62,7 @@
 
   const showDialog = () => {
     visible.value = true;
-    selectedId.value = undefined;
+    selectedIds.value = undefined;
   };
 </script>
 <style scoped></style>

+ 20 - 48
src/views/cameras/preview/components/AlgorithmsSetting/AlgoSettingCard.vue

@@ -2,14 +2,14 @@
   <div class="algoCardWrapper">
     <div class="algoCardTitle">
       <div>{{ selectedAlgoDetail?.algoInfo?.name }}</div>
-      <ElSwitch v-model="enableCard" size="small" />
+      <ElSwitch v-model="selectedAlgoDetail.enableCardBool" size="small" />
     </div>
     <div class="algoCardMain">
       <div class="algoRow">
         <div class="algoLabel">电子围栏:</div>
         <div>
           <div>
-            <ElSwitch v-model="electronicFence" size="small" />
+            <ElSwitch v-model="selectedAlgoDetail.electronicFenceBool" size="small" />
             <span style="font-size: 10px; margin-left: 20px; color: #262626"
               >备注:请绘制电子围栏</span
             >
@@ -20,7 +20,7 @@
       <div class="algoRow">
         <div class="algoLabel">检测时间:</div>
         <div>
-          <div v-for="x in timeRangeArr" :key="x.id">
+          <div v-for="x in selectedAlgoDetail.timeRangeArr" :key="x.id">
             <el-time-picker
               v-model="x.value"
               is-range
@@ -31,7 +31,7 @@
               size="small"
               style="width: 180px; margin-bottom: 10px"
             />
-            <span @click="removeTime(x.id)" v-if="timeRangeArr.length > 1">
+            <span @click="removeTime(x.id)" v-if="selectedAlgoDetail.timeRangeArr.length > 1">
               <el-icon class="removeIcon"><CircleCloseFilled /></el-icon
             ></span>
           </div>
@@ -43,13 +43,17 @@
       <div class="algoRow" style="align-items: center">
         <div class="algoLabel">检测频率:</div>
         <ElInputNumber
-          v-model="detectionNum"
+          v-model="selectedAlgoDetail.detectionJSON.detectionNum"
           controls-position="right"
           :min="0"
           size="small"
           style="width: 80px"
         />
-        <ElSelect size="small" style="width: 60px; margin-left: 10px" v-model="detectionUnit">
+        <ElSelect
+          size="small"
+          style="width: 60px; margin-left: 10px"
+          v-model="selectedAlgoDetail.detectionJSON.detectionUnit"
+        >
           <ElOption
             v-for="x in frequencyOptions"
             :key="x.value"
@@ -71,24 +75,16 @@
 <script lang="ts" setup>
   import { ElSelect, ElOption, ElSwitch, ElInputNumber, ElTimePicker } from 'element-plus';
   import { CircleCloseFilled } from '@element-plus/icons-vue';
-  import { ref, watch } from 'vue';
   import { storeToRefs } from 'pinia';
   import dayjs, { Dayjs } from 'dayjs';
   import addTimeIcon from '@/assets/icons/addTimeIcon.png';
   import useCameraAlgoStore from '../../store/useCameraAlgoStore';
-  import { STATUS, TimeRangeItem } from './types';
-  import {
-    FrequencyEnum,
-    createDefaultTime,
-    frequencyOptions,
-    getDetectionJSON,
-    getDetectionTimeJSON,
-  } from './utils';
+  import { STATUS } from './types';
+  import { createDefaultTime, frequencyOptions } from './utils';
 
   // const { data: algoList, loading } = useAllAlgos();
   const cameraAlgoStore = useCameraAlgoStore();
-  const { isAlgoBind } = cameraAlgoStore;
-  const { allAlgoList, selectedAlgoId, selectedAlgoDetail } = storeToRefs(cameraAlgoStore);
+  const { selectedAlgoId, selectedAlgoDetail } = storeToRefs(cameraAlgoStore);
 
   interface Param {
     /** 算法id */
@@ -108,37 +104,13 @@
     (e: 'onRemove', algoId: number): Promise<unknown>;
   }>();
 
-  const timeRangeArr = ref<TimeRangeItem[]>([createDefaultTime()]);
-
-  const detectionUnit = ref(FrequencyEnum.miniute);
-
-  /** 电子围栏开关 */
-  const electronicFence = ref(true);
-
-  const detectionNum = ref(5);
-
-  const enableCard = ref(true);
-
-  watch(
-    () => selectedAlgoDetail.value,
-    (detail) => {
-      console.log('detail change', detail);
-      const detectionJSON = getDetectionJSON(detail?.detectionFrequency);
-      enableCard.value = Boolean(detail?.status);
-      electronicFence.value = Boolean(detail?.electronicFence);
-      detectionNum.value = detectionJSON.detectionNum;
-      detectionUnit.value = detectionJSON.detectionUnit;
-      timeRangeArr.value = getDetectionTimeJSON(detail?.detectionTime) || [createDefaultTime()];
-    },
-    { deep: true, immediate: true },
-  );
-
   const handleAddTimeRange = () => {
-    timeRangeArr.value.push(createDefaultTime());
+    selectedAlgoDetail.value.timeRangeArr.push(createDefaultTime());
   };
 
   const removeTime = (id: string) => {
-    timeRangeArr.value = timeRangeArr.value.filter((x) => x.id !== id);
+    const timeRangeArr = selectedAlgoDetail.value.timeRangeArr;
+    selectedAlgoDetail.value.timeRangeArr = timeRangeArr.filter((x) => x.id !== id);
   };
 
   const getTimeStr = (d: Dayjs) => {
@@ -154,14 +126,14 @@
     const param = {
       id: detail.id,
       algoId: detail.algoId,
-      detectionFrequency: detectionNum.value * detectionUnit.value,
-      detectionTime: timeRangeArr.value
+      detectionFrequency: detail.detectionJSON.detectionNum * detail.detectionJSON.detectionUnit,
+      detectionTime: detail.timeRangeArr
         .map((x) => {
           return getTimeStrs(x.value);
         })
         .join(';'),
-      electronicFence: electronicFence.value ? STATUS.enabled : STATUS.disabled,
-      status: enableCard.value ? STATUS.enabled : STATUS.disabled,
+      electronicFence: detail.electronicFenceBool ? STATUS.enabled : STATUS.disabled,
+      status: detail.enableCardBool ? STATUS.enabled : STATUS.disabled,
     };
     emits('onSubmit', param);
     console.log('param', param);

+ 13 - 2
src/views/cameras/preview/components/AlgorithmsSetting/AlgoTag.vue

@@ -1,14 +1,25 @@
 <template>
   <div class="tagWrapper">
-    <ElTag hit :type="props.isActive ? '' : 'info'">{{ props.label }}</ElTag>
+    <ElTag
+      hit
+      :type="props.isActive ? '' : 'info'"
+      :class="{ isOpen: props.isOpen, isClose: !props.isOpen }"
+      >{{ props.label }}</ElTag
+    >
   </div>
 </template>
 <script lang="ts" setup>
-  const props = defineProps<{ isActive: boolean; label: string }>();
+  const props = defineProps<{ isActive: boolean; label: string; isOpen: boolean }>();
 </script>
 <style scoped>
   .tagWrapper {
     margin: 10px 0;
     cursor: pointer;
   }
+
+  .el-tag--info.isClose {
+    background-color: #fafafa;
+    color: #bfbfbf;
+    border-color: #dadada;
+  }
 </style>

+ 38 - 20
src/views/cameras/preview/components/AlgorithmsSetting/AlgorithmsSetting.vue

@@ -11,6 +11,7 @@
           :label="item.algoInfo?.name"
           :is-active="item.algoId === selectedAlgoId"
           @click="handleSelectAlgo(item.algoId)"
+          :is-open="item.status === AlgoStatus.enabled"
         />
       </div>
       <div>
@@ -19,6 +20,7 @@
           @on-remove="handleRemove"
           v-if="selectedAlgoId"
         />
+        <div style="color: #ccc; margin-top: 20px" v-else>请选择左侧算法</div>
       </div>
     </div>
   </div>
@@ -28,24 +30,23 @@
   import useCameraAlgoStore from '../../store/useCameraAlgoStore';
   import AlgoSettingCard from './AlgoSettingCard.vue';
   import { storeToRefs } from 'pinia';
-  import {
-    deleteCameraAlgoApi,
-    createCameraAlgoApi,
-    updateCameraAlgoApi,
-  } from '@/api/camera/camera-preview';
+  import { deleteCameraAlgoApi, updateCameraAlgoApi } from '@/api/camera/camera-preview';
   import { ElMessage } from 'element-plus';
   import AlgoTag from './AlgoTag.vue';
   import useFenceStore from '../../store/useFenceStore';
   import useCameraDetailStore from '../../store/useCameraDetailStore';
   import usePresetListStore from '../../store/usePresetListStore';
   import AddAlgoDialog from './AddAlgoDialog.vue';
+  import { createDefaultTime, getDetectionJSON, getDetectionTimeJSON } from './utils';
+  import { AlgoStatus } from '@/api/camera/camera-preview';
+  import { watchEffect } from 'vue';
 
   const cameraAlgoStore = useCameraAlgoStore();
   const fenceStore = useFenceStore();
   const presetStore = usePresetListStore();
 
-  const { getCameraAlgoList, getAllAlgoList } = cameraAlgoStore;
-  const { cameraAlgoList, selectedAlgoId } = storeToRefs(cameraAlgoStore);
+  const { getCameraAlgoList, getAllAlgoList, getAlgoDetail } = cameraAlgoStore;
+  const { cameraAlgoList, selectedAlgoId, selectedAlgoDetail } = storeToRefs(cameraAlgoStore);
   const cameraDetailStore = useCameraDetailStore();
 
   onMounted(() => {
@@ -53,17 +54,38 @@
     getAllAlgoList();
   });
 
-  const handleSelectAlgo = (id: number) => {
-    if (id !== selectedAlgoId.value) {
-      selectedAlgoId.value = id;
-      fenceStore.getFence({
-        algoId: id,
-        cameraId: cameraDetailStore.cameraId,
-        presetToken: presetStore.currentPresetToken,
-      });
+  const handleSelectAlgo = (algoId: number) => {
+    if (algoId !== selectedAlgoId.value) {
+      selectedAlgoId.value = algoId;
     }
   };
 
+  watchEffect(() => {
+    const algoId = selectedAlgoId.value;
+    if (!algoId) return;
+    const detail = getAlgoDetail(algoId);
+    if (!detail) return;
+    console.log('detail change', detail);
+    const detectionJSON = getDetectionJSON(detail?.detectionFrequency);
+    const enableCard = Boolean(detail?.status);
+    const electronicFenceBool = Boolean(detail?.electronicFence);
+
+    const timeRangeArr = getDetectionTimeJSON(detail?.detectionTime) || [createDefaultTime()];
+
+    selectedAlgoDetail.value = {
+      ...detail,
+      detectionJSON,
+      enableCardBool: enableCard,
+      electronicFenceBool,
+      timeRangeArr,
+    };
+    fenceStore.getFence({
+      algoId: algoId,
+      cameraId: cameraDetailStore.cameraId,
+      presetToken: presetStore.currentPresetToken,
+    });
+  });
+
   const handleSubmit = (param) => {
     console.log('submitParam', param);
     const cameraId = cameraDetailStore.cameraId;
@@ -80,11 +102,6 @@
         ElMessage.success('更新成功');
         getCameraAlgoList(cameraId);
       });
-    } else {
-      createCameraAlgoApi(newParam).then(() => {
-        ElMessage.success('保存成功');
-        getCameraAlgoList(cameraId);
-      });
     }
   };
 
@@ -93,6 +110,7 @@
     deleteCameraAlgoApi({ algoId, cameraId: cameraDetailStore.cameraId }).then(() => {
       ElMessage.success('删除成功');
       getCameraAlgoList(cameraDetailStore.cameraId);
+      selectedAlgoId.value = undefined;
     });
   };
 </script>

+ 6 - 1
src/views/cameras/preview/components/AlgorithmsSetting/utils.ts

@@ -19,8 +19,13 @@ export const frequencyOptions = [
   { label: '分钟', value: FrequencyEnum.miniute },
   { label: '小时', value: FrequencyEnum.hour },
 ];
+
+export interface DetectionJSON {
+  detectionNum: number;
+  detectionUnit: FrequencyEnum;
+}
 /** 根据后端返回的时间(单位是秒),拆分成单位和数值 */
-export const getDetectionJSON = (time: number | undefined | null) => {
+export const getDetectionJSON = (time: number | undefined | null): DetectionJSON => {
   if (time && time > 0) {
     for (let i = frequencyOptions.length - 1; i >= 0; i--) {
       const unit = frequencyOptions[i].value;

+ 54 - 0
src/views/cameras/preview/components/CameraDirectionControl/CameraDirectionControl.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="cameraDirectionControlWrapper">
+    <div class="cameraDirectionControl">
+      <div class="roundCircle"></div>
+      <DirectionItem position="top" @click="handleMoveTop" />
+      <DirectionItem position="right" @click="handleMoveRight" />
+      <DirectionItem position="bottom" @click="handleMoveBottom" />
+      <DirectionItem position="left" @click="handleMoveLeft" />
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { cameraMoveApi } from '@/api/camera/camera-preview';
+  import DirectionItem from './DirectionItem.vue';
+  import useCameraDetailStore from '../../store/useCameraDetailStore';
+  import { storeToRefs } from 'pinia';
+
+  const cameraDetailStore = useCameraDetailStore();
+  const { cameraId } = storeToRefs(cameraDetailStore);
+
+  const STEP = 0.1;
+
+  const handleMoveTop = () => {
+    cameraMoveApi({ cameraId: cameraId.value, zoom: 0, x: 0, y: STEP });
+  };
+  const handleMoveRight = () => {
+    cameraMoveApi({ cameraId: cameraId.value, zoom: 0, x: STEP, y: 0 });
+  };
+  const handleMoveBottom = () => {
+    cameraMoveApi({ cameraId: cameraId.value, zoom: 0, x: 0, y: -STEP });
+  };
+  const handleMoveLeft = () => {
+    cameraMoveApi({ cameraId: cameraId.value, zoom: 0, x: -STEP, y: 0 });
+  };
+</script>
+<style scoped>
+  .cameraDirectionControlWrapper {
+    bottom: 100px;
+    right: 0px;
+  }
+  .cameraDirectionControl {
+    width: 180px;
+    height: 180px;
+
+    position: relative;
+    .roundCircle {
+      background: #fff;
+      opacity: 0.4;
+      width: 180px;
+      height: 180px;
+      border-radius: 180px;
+    }
+  }
+</style>

+ 70 - 0
src/views/cameras/preview/components/CameraDirectionControl/DirectionItem.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="sectorWrapper" :class="[props.position, { active: isActive }]" @click="handleClick">
+    <!-- 四分之一圆 -->
+    <div class="sector"></div>
+    <!-- 小三角形 -->
+    <div class="triangle"></div>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  const props = defineProps<{ position: 'top' | 'right' | 'bottom' | 'left' }>();
+  const isActive = ref(false);
+  const handleClick = () => {
+    isActive.value = true;
+    setTimeout(() => {
+      isActive.value = false;
+    }, 200);
+  };
+</script>
+<style scoped>
+  .sectorWrapper {
+    width: 90px;
+    height: 90px;
+    transform-origin: bottom right;
+    position: absolute;
+    cursor: pointer;
+    left: 0;
+    top: 0;
+    &.top {
+      transform: rotate(45deg);
+    }
+
+    &.right {
+      transform: rotate(135deg);
+    }
+
+    &.bottom {
+      transform: rotate(225deg);
+    }
+    &.left {
+      transform: rotate(315deg);
+    }
+    &.active {
+      .sector {
+        background-color: #1890ff;
+        opacity: 0.4;
+      }
+      .triangle {
+        border-bottom-color: #1677ff;
+      }
+    }
+  }
+  .sector {
+    width: 90px;
+    height: 90px;
+    border-radius: 90px 0 0 0;
+  }
+
+  .triangle {
+    width: 0;
+    height: 0;
+    border: 7px solid transparent;
+    border-bottom-color: #d8d8d8; /* 三角形的颜色 */
+    position: absolute;
+
+    top: 26px;
+    left: 26px;
+    transform: rotate(-43deg);
+  }
+</style>

+ 14 - 3
src/views/cameras/preview/components/CameraLiveVideo/CameraLiveVideo.vue

@@ -1,12 +1,23 @@
 <template>
-  <img :src="bg" alt="" class="videoLive" />
+  <img
+    :src="bg"
+    alt=""
+    class="videoLive"
+    :style="{
+      width: `${cameraDetailStore.videoSize.width}px`,
+      height: `${cameraDetailStore.videoSize.height}px`,
+    }"
+  />
 </template>
 <script lang="ts" setup>
   import bg from '@/assets/images/camera/video-live.png';
+
+  import useCameraDetailStore from '../../store/useCameraDetailStore';
+  const cameraDetailStore = useCameraDetailStore();
 </script>
 <style scoped>
   .videoLive {
-    width: 720px;
-    height: 405px;
+    /* width: 720px;
+    height: 405px; */
   }
 </style>

+ 59 - 52
src/views/cameras/preview/components/CameraParams/CameraParams.vue

@@ -1,72 +1,47 @@
-<script lang="ts" setup>
-  import { watch, ref } from 'vue';
-
-  const props = defineProps<{
-    detail: any;
-  }>();
-  const cameraParams = ref({
-    imageResolution: '',
-    recordPeriod: null,
-    startTime: '',
-    endTime: '',
-    reservation: '',
-  });
-
-  //   声明时赋初始值
-  //   const form = reactive({
-  //     imageResolution: props.cameraInits.imageResolution,
-  //     recordPeriod: props.cameraInits.recordPeriod,
-  //     startTime: props.cameraInits.startTime,
-  //     endTime: props.cameraInits.endTime,
-  //     reservation: props.cameraInits.reservation,
-  //   });
-
-  watch(
-    () => {
-      return props.detail;
-    },
-    () => {
-      cameraParams.value = props.detail;
-    },
-    {
-      immediate: true,
-    },
-  );
-
-  const emits = defineEmits(['submit']);
-  const onSubmit = () => {
-    emits('submit', cameraParams);
-  };
-</script>
 <template>
-  <el-form :model="cameraParams" label-width="130px" lable-position="left">
+  <el-form :model="cameraDetailStore" label-width="130px" lable-position="left">
     <el-form-item label="分辨率:">
-      <el-select v-model="cameraParams.imageResolution" style="width: 100%">
-        <el-option label="1920" value="1920" />
-        <el-option label="1280" value="1280" />
-        <el-option label="720" value="720" />
+      <el-select
+        v-model="cameraDetailStore.params.imageResolution"
+        style="width: 100%"
+        size="small"
+      >
+        <el-option
+          v-for="x in videoResolutionList"
+          :label="x.label"
+          :value="x.value"
+          :key="x.value"
+        />
       </el-select>
     </el-form-item>
     <el-form-item label="录制周期:">
-      <el-select v-model="cameraParams.recordPeriod" style="width: 100%">
-        <el-option label="10天" value="10天" />
-        <el-option label="5天" value="5天" />
-        <el-option label="1天" value="1天" />
+      <el-select v-model="cameraDetailStore.params.recordPeriod" style="width: 100%" size="small">
+        <el-option label="10天" :value="10" />
+        <el-option label="5天" :value="5" />
+        <el-option label="1天" :value="1" />
       </el-select>
     </el-form-item>
     <el-form-item label="录制时间:">
       <el-col :span="11">
-        <el-time-picker v-model="cameraParams.startTime" style="width: 100%" />
+        <el-time-picker
+          v-model="cameraDetailStore.params.startTime"
+          style="width: 100%"
+          size="small"
+        />
       </el-col>
       <el-col :span="1">
         <span class="text-center">-</span>
       </el-col>
       <el-col :span="11">
-        <el-time-picker v-model="cameraParams.endTime" style="width: 100%" />
+        <el-time-picker
+          v-model="cameraDetailStore.params.endTime"
+          style="width: 100%"
+          size="small"
+        />
       </el-col>
     </el-form-item>
     <el-form-item label="返回预置位:">
-      <el-input v-model="cameraParams.reservation" />
+      <el-input v-model="cameraDetailStore.params.reservation" size="small" />
     </el-form-item>
     <el-form-item>
       <el-button type="primary" @click="onSubmit">保存</el-button>
@@ -74,6 +49,38 @@
   </el-form>
 </template>
 
+<script lang="ts" setup>
+  import { getCameraParamsApi, saveCameraParamsApi } from '@/api/camera/camera-preview';
+  import { formatToDateTime } from '@/utils/dateUtil';
+  import { ElMessage } from 'element-plus';
+  import useCameraDetailStore from '../../store/useCameraDetailStore';
+  import { onMounted } from 'vue';
+  import { videoResolutionList } from './types';
+
+  const cameraDetailStore = useCameraDetailStore();
+
+  onMounted(() => {
+    getCameraParamsApi(cameraDetailStore.cameraId).then((res) => {
+      cameraDetailStore.params = res;
+    });
+  });
+
+  const onSubmit = () => {
+    const params = cameraDetailStore.params;
+    const DATE_TIME_STR = 'YYYY-MM-DD HH:mm:ss';
+    const endTime = formatToDateTime(params.endTime, DATE_TIME_STR);
+    const startTime = formatToDateTime(params.startTime, DATE_TIME_STR);
+    saveCameraParamsApi({
+      ...params,
+      startTime,
+      endTime,
+      cameraId: cameraDetailStore.cameraId,
+    }).then((res) => {
+      ElMessage.success('保存成功');
+    });
+  };
+</script>
+
 <style scoped>
   .text-center {
     /* text-align: center; */

+ 10 - 0
src/views/cameras/preview/components/CameraParams/types.ts

@@ -0,0 +1,10 @@
+/** 分辨率的枚举值 */
+export enum VideoResolution {
+  '1920*1080' = 1,
+  '720*405' = 0.375,
+}
+
+export const videoResolutionList = [
+  { label: '1920*1080', value: VideoResolution['1920*1080'] },
+  { label: '720*405', value: VideoResolution['720*405'] },
+];

+ 82 - 16
src/views/cameras/preview/components/CameraTree/CameraTree.vue

@@ -1,22 +1,55 @@
 <template>
-  <el-tree
-    :data="props.data"
-    :props="defaultProps"
-    @node-click="handleNodeClick"
-    node-key="code"
-    :default-checked-keys="['C12-200-01']"
-    ref="treeRef"
-    :default-expand-all="true"
-  />
+  <div class="cameraTreeWrapper">
+    <div class="cameraTreeTitle">场景树</div>
+    <el-input
+      v-model="filterText"
+      placeholder="请输入相机的名称进行搜索"
+      :suffix-icon="Search"
+      class="filterTextInput"
+    />
+
+    <el-tree
+      :data="data"
+      :props="defaultProps"
+      @node-click="handleNodeClick"
+      node-key="code"
+      :default-expand-all="true"
+      :filter-node-method="filterNode"
+      ref="treeRef"
+      v-loading="loading"
+    >
+      <template #default="{ node, data }">
+        <span class="custom-tree-node">
+          <span
+            ><span
+              class="cameraCommon"
+              :class="{
+                cameraSelect:
+                  data.nodeType === CameraTreeNodeType.camera && data.id === Number(cameraId),
+              }"
+            ></span
+            >{{ node.label }}</span
+          >
+        </span>
+      </template>
+    </el-tree>
+  </div>
 </template>
 <script lang="ts" setup>
   import { ElTree } from 'element-plus';
-  import { Ref, ref } from 'vue';
+  import { ref, watch } from 'vue';
   import { useRouteQuery } from '@vueuse/router';
+  import { Search } from '@element-plus/icons-vue';
+
   import useCameraDetail from '../../store/useCameraDetailStore';
   import { CameraTree, CameraTreeNodeType } from '@/api/camera/camera-preview';
-  import { onMounted } from 'vue';
-  const props = defineProps<{ data }>();
+  import useCameraTree from '../../hooks/useCameraTree';
+
+  const { data, loading } = useCameraTree();
+
+  interface Tree {
+    [key: string]: any;
+  }
 
   const { setDetail } = useCameraDetail();
 
@@ -34,10 +67,43 @@
     }
   };
 
-  onMounted(() => {
-    console.log('cameraId', cameraId.value);
+  const filterText = ref('');
+  const treeRef = ref<InstanceType<typeof ElTree>>();
+
+  watch(filterText, (val) => {
+    treeRef.value!.filter(val);
   });
 
-  const treeRef = ref(null);
+  const filterNode = (value: string, data: Tree) => {
+    if (!value) return true;
+    return data.name?.includes(value);
+  };
 </script>
-<style scoped></style>
+<style scoped>
+  .cameraCommon {
+    width: 6px;
+    height: 6px;
+    display: inline-block;
+    margin-right: 6px;
+  }
+
+  .cameraSelect {
+    width: 6px;
+    height: 6px;
+    background: #0052d9;
+    display: inline-block;
+    border-radius: 6px;
+    margin-right: 6px;
+  }
+  .cameraTreeTitle {
+    background: #f0f2f5;
+    padding: 12px;
+  }
+
+  .cameraTreeWrapper {
+    padding: 8px;
+  }
+  .filterTextInput {
+    margin: 8px 0;
+  }
+</style>

+ 64 - 43
src/views/cameras/preview/components/CameraViewSetting/CameraViewSetting.vue

@@ -3,6 +3,7 @@
     <div class="toolbarWrapper">
       <ViewWindowSetting v-model="viewType" @update:model-value="handleUpdateViewType" />
       <FenceToolbar
+        :style="{ visibility: drawable ? 'visible' : 'hidden' }"
         @remove="handleRemove"
         @save="handleSave"
         @toggle-editable="toggleEditable"
@@ -11,13 +12,29 @@
       <PresetSelect />
     </div>
 
-    <div class="cameraViewSettingWrapper">
-      <FenceEditor ref="fenceEditorRef" />
-
-      <div class="cameraVideo"><CameraLiveVideo /></div>
-      <div class="presetAddWrapper">
+    <div
+      class="cameraViewSettingWrapper"
+      :style="{
+        width: `${cameraDetailStore.videoSize.width}px`,
+        height: `${cameraDetailStore.videoSize.height}px`,
+      }"
+    >
+      <FenceEditor
+        ref="fenceEditorRef"
+        :width="cameraDetailStore.videoSize.width"
+        :height="cameraDetailStore.videoSize.height" />
+      <div class="cameraVideo">
+        <CameraLiveVideo />
+      </div>
+      <div class="presetAddWrapper" :class="{ hidePresetControlCls: isEdit }">
         <CameraDirectionControl />
-        <ElButton type="primary" @click="handleAddPreset" size="small">添加预置位</ElButton>
+        <ElButton
+          type="primary"
+          @click="handleAddPreset"
+          size="small"
+          style="margin-top: 20px; width: 100px"
+          >添加预置位</ElButton
+        >
         <AddPresetModal
           v-if="addPresetModalVisible"
           @close="handleClose"
@@ -27,13 +44,13 @@
   </div>
   <div class="cameraParamsSettingWrapper">
     <div class="cameraParamsSetting">
-      <CameraParams :detail="cameraParamsDetail" />
+      <CameraParams />
     </div>
     <div class="algorithmsSetting"> <AlgorithmsSetting /> </div>
   </div>
 </template>
 <script lang="ts" setup>
-  import { ref, watch } from 'vue';
+  import { computed, ref, watchEffect } from 'vue';
   import FenceToolbar from '../FenceToolbar/FenceToolbar.vue';
   import FenceEditor from '../FenceEditor/FenceEditor.vue';
   import CameraLiveVideo from '../CameraLiveVideo/CameraLiveVideo.vue';
@@ -49,6 +66,7 @@
   import AlgorithmsSetting from '../AlgorithmsSetting/AlgorithmsSetting.vue';
   import CameraParams from '../CameraParams/CameraParams.vue';
   import { ElMessage } from 'element-plus';
+  import CameraDirectionControl from '../CameraDirectionControl/CameraDirectionControl.vue';
 
   const fenceEditorRef = ref<typeof FenceEditor | null>(null);
 
@@ -62,8 +80,6 @@
 
   const addPresetModalVisible = ref(false);
 
-  const cameraParamsDetail = ref({});
-
   const handleClose = () => {
     addPresetModalVisible.value = false;
   };
@@ -74,7 +90,6 @@
   };
 
   const handleRemove = () => {
-    console.log('handleRemove');
     fenceEditorRef.value?.remove();
   };
 
@@ -116,7 +131,7 @@
         presetToken,
         electronicFencePolygon: JSON.stringify(json),
       })
-      ?.then((res) => {
+      ?.then(() => {
         ElMessage.success('更新成功');
       });
   };
@@ -125,20 +140,25 @@
     console.log('viewType', t);
   };
 
-  const handleUpdatePreset = (val: string) => {
-    console.log('val', val);
-  };
-  watch(
-    () => fenceStore.serverFencePoints,
-    (newVal) => {
-      console.log('newVal', newVal);
-      if (!newVal) {
+  const drawable = computed(() => {
+    if (!presetStore.currentPresetToken) return false;
+    if (!cameraAlgoStore.selectedAlgoId) return false;
+    if (!cameraAlgoStore.selectedAlgoDetail?.electronicFenceBool) return false;
+    return true;
+  });
+
+  watchEffect(() => {
+    if (
+      presetStore.currentPresetToken &&
+      cameraAlgoStore.selectedAlgoId &&
+      cameraAlgoStore.selectedAlgoDetail?.electronicFenceBool
+    ) {
+      const points = fenceStore.serverFencePoints;
+      if (!points) {
         fenceEditorRef.value?.clear();
         return;
       }
-      // const rawLinePoints = newVal[0];
-
-      const rawLinePoints = newVal.map((x) => {
+      const rawLinePoints = points.map((x) => {
         const points: number[] = [];
         x.forEach((line) => {
           points.push(line[0], line[1]);
@@ -149,24 +169,13 @@
       /** 先清空原有的 */
       fenceEditorRef.value?.clear();
       fenceEditorRef.value?.createLines(rawLinePoints);
-    },
-    { immediate: true },
-  );
-
-  watch(
-    () => cameraAlgoStore.selectedAlgoDetail?.status,
-    (newVal) => {
-      if (newVal) {
-        fenceEditorRef.value?.setEditMode();
-      } else {
-        fenceEditorRef.value?.exitEditMode();
-      }
-    },
-    {
-      immediate: true,
-      deep: true,
-    },
-  );
+      fenceEditorRef.value?.setEditMode();
+      isEdit.value = true;
+      return;
+    }
+    fenceEditorRef.value?.clear();
+    fenceEditorRef.value?.exitEditMode();
+  });
 
   const handleAddPreset = () => {
     addPresetModalVisible.value = true;
@@ -178,12 +187,18 @@
       presetStore.getPresetList(cameraId);
     }
   });
+
+  // watchEffect(() => {
+  //   const scale = cameraDetailStore.params.imageResolution;
+  //   console.log('scale change', scale);
+  //   fenceEditorRef.value?.setScale(scale);
+  // });
 </script>
 <style scoped>
   .cameraViewSettingWrapper {
     position: relative;
-    width: 720px;
-    height: 405px;
+    /* width: 720px;
+    height: 405px; */
     border: 1px solid #ccc;
   }
 
@@ -205,6 +220,9 @@
     position: absolute;
     bottom: 50px;
     right: 50px;
+    flex-direction: column;
+    display: flex;
+    align-items: center;
     z-index: 10;
   }
 
@@ -223,4 +241,7 @@
     flex-basis: 330px;
     flex-shrink: 0;
   }
+  .hidePresetControlCls {
+    display: none;
+  }
 </style>

+ 33 - 16
src/views/cameras/preview/components/FenceEditor/FenceEditor.vue

@@ -4,7 +4,7 @@
 
 <script lang="ts" setup>
   import Konva from 'konva';
-  import { ref, onMounted, onUnmounted } from 'vue';
+  import { ref, onMounted, onUnmounted, watch } from 'vue';
   import { GROUP_NAME, POLYGON_NAME, Points, ToolObjectItem, toolObject } from './constants';
   import { ElMessage } from 'element-plus';
   import { getDefaultScale } from './utils';
@@ -37,13 +37,22 @@
     stage?.destroy();
   });
 
+  const props = defineProps<{ width: number; height: number }>();
+
+  watch(
+    () => [props.width, props.height],
+    () => {
+      initKonvaStage();
+    },
+  );
+
   /**
    *初始化konva舞台
    */
   function initKonvaStage() {
     //1实例化stage层
-    stageWidth = mapRef.value?.clientWidth || 0;
-    stageHeight = mapRef.value?.clientHeight || 0;
+    stageWidth = props.width || 0;
+    stageHeight = props.height || 0;
     console.log('stageWidth', stageWidth);
     stage = new Konva.Stage({
       container: 'editorMap',
@@ -52,7 +61,9 @@
       ignoreStroke: true,
       background: '#00ff00',
     });
-    window.stage = stage;
+    if (import.meta.env.MODE === 'development') {
+      window.stage = stage;
+    }
     setStageCursor('pointer');
 
     //2实例化layer层
@@ -504,10 +515,10 @@
     layer?.draw();
   }
   /**
- *多边形
-  @param currentTool
-  * @param points 多边形绘画的各个顶点,类型数组
-  */
+  *多边形
+   @param currentTool
+   * @param points 多边形绘画的各个顶点,类型数组
+   */
   function drawPolygon(currentTool: ToolObjectItem, points: number[], group: Konva.Group) {
     let poly = new Konva.Line({
       name: currentTool.name + 'poly',
@@ -536,7 +547,7 @@
     pParent?.on('mousedown', (e) => {
       if (!isEdit) return;
 
-      console.log('group mouse down');
+      console.log('group mouse down', e);
       if (e.evt.button == 0) {
         //绘画结束
         if (!drawing) {
@@ -549,6 +560,7 @@
           stage?.find('Circle').forEach((element) => {
             element.moveToTop();
           });
+          pParent.moveToTop();
           //添加删除撤销对象
           currentDel = currentDrawingShape;
           setCurrentGroup(poly.getParent() as Konva.Group);
@@ -622,12 +634,13 @@
       currentDel.destroy();
       currentDel = null;
       currentDrawingShape = null;
-      ElMessage({
-        message: '删除成功!',
-        type: 'success',
-        center: true,
-        duration: 1000,
-      });
+      // ElMessage.success({
+      //   message: '删除成功!',
+      //   center: true,
+      //   duration: 1000,
+      // });
+    } else {
+      ElMessage.warning({ message: '请选择要删除的电子围栏' });
     }
     layer?.draw();
   };
@@ -673,6 +686,10 @@
     layer?.removeChildren();
   };
 
+  const setScale = (scale: number) => {
+    stage?.setAttr('scaleX', scale);
+  };
+
   defineExpose({
     remove: removeCurrent,
     toObject,
@@ -682,6 +699,7 @@
     exitEditMode,
     setEditMode,
     clear,
+    setScale,
   });
 </script>
 
@@ -693,6 +711,5 @@
     width: 100%;
     height: 100%;
     z-index: 8;
-    /* border: 2px solid #0f0; */
   }
 </style>

+ 2 - 46
src/views/cameras/preview/components/FenceToolbar/FenceToolbar.vue

@@ -1,12 +1,5 @@
 <template>
   <div class="toolbar">
-    <!-- <ElButton>编辑</ElButton>
-    <ElButton @click="remove">删除</ElButton>
-    <ElButton @click="toObject">保存到localStorage</ElButton>
-    <ElButton @click="loadGroup">从local加载group</ElButton>
-    <ElButton @click="toRawObject">保存Raw</ElButton> -->
-    <ToolbarIcon :src="mousePointerIcon" :active="false" />
-    <!-- <ToolbarIcon :src="editIcon" :active="false" @click="emits('setEditable')" /> -->
     <ToolbarIcon :src="deleteIcon" :active="false" @click="emits('remove')" />
     <ToolbarIcon :src="saveIcon" :active="false" @click="emits('save')" />
     <ElButton type="primary" size="small" @click="toggleEdit">{{
@@ -15,57 +8,20 @@
   </div>
 </template>
 <script setup lang="ts">
-  import { ref, defineEmits, watch } from 'vue';
-  import { ElButton, ElSwitch } from 'element-plus';
-  import PolygonEditor from '../FenceEditor/FenceEditor.vue';
-  import { ServerLines } from '../FenceEditor/constants';
+  import { defineEmits } from 'vue';
+  import { ElButton } from 'element-plus';
   import ToolbarIcon from '../ToolbarIcon/ToolbarIcon.vue';
   import saveIcon from '@/assets/images/camera/save.png';
   import deleteIcon from '@/assets/images/camera/delete.png';
-  import mousePointerIcon from '@/assets/images/camera/mousePointer.png';
-  import editIcon from '@/assets/images/camera/pen.png';
-  import useFenceStore from '../../store/useFenceStore';
 
-  const isFenceOn = ref(true);
   const props = defineProps<{ isEdit: boolean }>();
 
-  const polygonEditorRef = ref<typeof PolygonEditor | null>(null);
-
-  const fenceStore = useFenceStore();
   const emits = defineEmits<{
     (e: 'toggleEditable', editState: boolean): unknown;
     (e: 'remove'): unknown;
     (e: 'save'): unknown;
   }>();
 
-  const toObject = () => {
-    const json = polygonEditorRef.value?.toObject();
-    console.log('toObject json', json);
-    localStorage.setItem('mapDataV2', JSON.stringify(json));
-  };
-
-  const toRawObject = () => {
-    const objects = polygonEditorRef.value?.toRawObject();
-    console.log('objects', objects);
-    localStorage.setItem('mapData', JSON.stringify(objects));
-  };
-
-  const loadGroup = () => {
-    const data = localStorage.getItem('mapDataV2');
-    console.log('loadGroup data', data);
-    if (!data) return;
-    const dataJSON = JSON.parse(data) as ServerLines;
-    const groups = dataJSON;
-    const rawLinePoints = groups[0];
-    const points: number[] = [];
-    rawLinePoints.forEach((line) => {
-      points.push(line[0], line[1]);
-    });
-    console.log('points', points);
-
-    polygonEditorRef.value?.createLines(points);
-  };
-
   const toggleEdit = () => {
     emits('toggleEditable', !props.isEdit);
   };

+ 2 - 1
src/views/cameras/preview/components/PresetSelect/PresetSelect.vue

@@ -29,7 +29,7 @@
   import { CircleCloseFilled } from '@element-plus/icons-vue';
   import usePresetListStore from '../../store/usePresetListStore';
   import { storeToRefs } from 'pinia';
-  import { deletePresetApi } from '@/api/camera/camera-preview';
+  import { deletePresetApi, goToPresetApi } from '@/api/camera/camera-preview';
   import useCameraDetailStore from '../../store/useCameraDetailStore';
   import useFenceStore from '../../store/useFenceStore';
   import useCameraAlgoStore from '../../store/useCameraAlgoStore';
@@ -72,6 +72,7 @@
 
   const handleChangeValue = (val) => {
     currentPresetToken.value = val;
+    goToPresetApi({ presetToken: val, cameraId: cameraDetailStore.cameraId });
     fenceStore.getFence({
       presetToken: val,
       algoId: cameraAlgoStore.selectedAlgoId!,

+ 1 - 1
src/views/cameras/preview/components/ToolbarIcon/ToolbarIcon.vue

@@ -4,7 +4,7 @@
 <script lang="ts" setup>
   const props = defineProps<{ active: boolean; src: string }>();
 </script>
-<style scoped lang="scss">
+<style scoped>
   .toolbarIcon {
     width: 24px;
     height: 24px;

+ 18 - 27
src/views/cameras/preview/store/useCameraAlgoStore.ts

@@ -1,12 +1,16 @@
-import {
-  getAllAlgosApi,
-  getCameraAlgoListApi,
-  CameraAlgoItem,
-  AlgoStatus,
-} from '@/api/camera/camera-preview';
+import { getAllAlgosApi, getCameraAlgoListApi, CameraAlgoItem } from '@/api/camera/camera-preview';
 import { defineStore } from 'pinia';
-import { computed, ref } from 'vue';
+import { ref } from 'vue';
 import { useRequest } from 'vue-hooks-plus';
+import { TimeRangeItem } from '../components/AlgorithmsSetting/types';
+import { DetectionJSON } from '../components/AlgorithmsSetting/utils';
+
+interface CameraAlgoItemInCard extends CameraAlgoItem {
+  detectionJSON: DetectionJSON;
+  enableCardBool: boolean;
+  electronicFenceBool: boolean;
+  timeRangeArr: TimeRangeItem[];
+}
 
 const useCameraAlgoStore = defineStore('cameraAlgo', () => {
   const { data: cameraAlgoList, runAsync: getCameraAlgoList } = useRequest(
@@ -23,28 +27,15 @@ const useCameraAlgoStore = defineStore('cameraAlgo', () => {
   /** 所有算法列表中选定的算法id */
   const selectedAlgoId = ref<number>();
 
-  const selectedAlgoDetail = computed(() => {
-    if (!selectedAlgoId.value) return null;
-    return getAlgoDetail(selectedAlgoId.value);
-  });
+  const selectedAlgoDetail = ref<CameraAlgoItemInCard>({
+    // 复杂结构要加这个,否则v-model的时候会报错
+    detectionJSON: { detectionNum: 0, detectionUnit: 1 },
+  } as CameraAlgoItemInCard);
 
   const getAlgoDetail = (algoId: number): null | CameraAlgoItem => {
-    const detailWithCamera = cameraAlgoList.value?.find((x) => x.algoId === algoId);
-    if (detailWithCamera) return detailWithCamera;
-    const algoDetail = allAlgoList.value?.find((x) => x.id === algoId);
-    if (!algoDetail) return null;
-    return {
-      algoInfo: algoDetail,
-      id: undefined,
-      algoId: algoDetail?.id,
-      status: AlgoStatus.disabled,
-      electronicFence: 0,
-      cameraId: undefined,
-      code: '',
-      name: '',
-      detectionFrequency: 0,
-      detectionTime: '',
-    };
+    const detail = cameraAlgoList.value?.find((x) => x.algoId === algoId);
+    if (!detail) return null;
+    return detail;
   };
 
   /** 算法是否已经绑定到相机 */

+ 31 - 2
src/views/cameras/preview/store/useCameraDetailStore.ts

@@ -3,17 +3,46 @@
 import { CameraDetailServer } from '@/api/camera/camera-overview';
 import { useRouteQuery } from '@vueuse/router';
 import { defineStore } from 'pinia';
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
+
+interface CameraParams {
+  startTime: string;
+  endTime: string;
+  imageResolution: number;
+  recordPeriod: number;
+  /** 这个要调整一下 */
+  reservation: string;
+}
+
+/** 宽/长的比例 */
+export const WIDTH_HEIGHT_RATIO = 0.5625;
+/** 分辨率以1920为基础 */
+export const BASE_RESOLUTION = 1920;
 
 const useCameraDetailStore = defineStore('cameraDetail', () => {
   const cameraId = useRouteQuery('cameraId', '', { transform: (str) => Number(str) });
 
   const detail = ref<CameraDetailServer | null>(null);
 
+  /** 参数设置 */
+  const params = ref<CameraParams>({
+    startTime: '',
+    endTime: '',
+    imageResolution: 1,
+    recordPeriod: 0,
+    reservation: '',
+  });
+
+  const videoSize = computed(() => {
+    const width = params.value.imageResolution * BASE_RESOLUTION;
+    const height = width * WIDTH_HEIGHT_RATIO;
+    return { width, height };
+  });
+
   const setDetail = (newDetail: CameraDetailServer) => {
     detail.value = newDetail;
   };
-  return { detail, setDetail, cameraId };
+  return { detail, setDetail, cameraId, params, videoSize };
 });
 
 export default useCameraDetailStore;

+ 46 - 0
src/views/datamanager/systemdata/PlatformData.vue

@@ -0,0 +1,46 @@
+<template>
+    <div style="width: 100%">
+        <div>
+            <el-tabs v-model="activeName" type="card" @tab-click="handleClick">
+                <el-tab-pane label="访问次数统计" name="count">
+                    <Table />
+                </el-tab-pane>
+                <el-tab-pane label="积分统计" name="score">
+                    <Score />
+                </el-tab-pane>
+
+            </el-tabs>
+        </div>
+
+
+
+    </div>
+</template>
+
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import type { TabsPaneContext } from 'element-plus';
+import Table from './Table.vue'
+import Score from './Score.vue';
+
+
+
+const activeName = ref('count');
+export type LabelType = 'count' | 'score';
+const currentLabel = ref<LabelType>('count');
+
+const handleClick = (tab: TabsPaneContext) => {
+    console.log(tab.paneName);
+    if (tab.paneName === 'count') {
+        currentLabel.value = 'count';
+    } else {
+
+        currentLabel.value = 'score';
+
+    }
+};
+</script>
+
+
+<style scoped></style>

+ 129 - 0
src/views/datamanager/systemdata/Score.vue

@@ -0,0 +1,129 @@
+<template>
+    <div>
+        <TableCommon :type="type" :query-data="queryData" :current-day-data="getDayData" :current-month-data="getMonthData"
+            :current-all-data="getAllData" :get-personal-visits="getPersonalVisitsData" />
+    </div>
+</template>
+
+<script setup lang="ts">
+
+import { getList, getMonthVisits, getPersonalVisits, getTodayVisits, getTotalVisits } from '@/api/datamanagement/dataplatform';
+import TableCommon from './TableCommon.vue';
+import { ref } from 'vue';
+import { ElMessageBox, ElMessage } from 'element-plus'
+const type = ref('积分')
+
+
+
+// 查询数据列表
+function queryData(pageNumber: number, pageSize: number) {
+    return getList(pageNumber, pageSize).then((res) => {
+
+        console.log('table-querydata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject()
+    });
+
+}
+
+
+// 查询今日访问次数
+function getDayData(userId: number) {
+    return getTodayVisits(userId).then((res) => {
+
+        console.log('table-getdaydata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject();
+    });
+
+}
+
+// 查询本月访问次数
+function getMonthData(userId: number) {
+    console.log('table-getmonthdata:', userId)
+    return getMonthVisits(userId).then((res) => {
+        console.log('table-getmonthdata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        });
+        return Promise.reject();
+    });
+}
+
+// 查询累计访问次数
+function getAllData(userId: number) {
+    return getTotalVisits(userId).then((res) => {
+
+        console.log('table-getalldata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject();
+    });
+
+}
+
+
+
+// 查询个人访问次数-
+function getPersonalVisitsData(deptId: number, nickName: string, pageNumber: number, pageSize: number, staffNo: string) {
+    console.log('table-getPersonalVisitsData-deptid', deptId);
+    console.log('table-getPersonalVisitsData-nama', nickName);
+    console.log('table-getPersonalVisitsData', pageNumber, pageSize);
+    console.log('table-getPersonalVisitsData-staffno', staffNo);
+    return getPersonalVisits(pageNumber, pageSize, staffNo, deptId, nickName).then((res) => {
+        console.log('table-getPersonalVisitsData:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject();
+    });
+}
+
+</script>
+
+<style scoped></style>

+ 129 - 0
src/views/datamanager/systemdata/Table.vue

@@ -0,0 +1,129 @@
+<template>
+    <div>
+        <TableCommon :type="type" :query-data="queryData" :current-day-data="getDayData" :current-month-data="getMonthData"
+            :current-all-data="getAllData" :get-personal-visits="getPersonalVisitsData" />
+    </div>
+</template>
+
+<script setup lang="ts">
+
+import { getList, getMonthVisits, getPersonalVisits, getTodayVisits, getTotalVisits } from '@/api/datamanagement/dataplatform';
+import TableCommon from './TableCommon.vue';
+import { ref } from 'vue';
+import { ElMessageBox, ElMessage } from 'element-plus'
+const type = ref('访问数据')
+
+
+
+// 查询数据列表
+function queryData(pageNumber: number, pageSize: number) {
+    return getList(pageNumber, pageSize).then((res) => {
+
+        console.log('table-querydata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject()
+    });
+
+}
+
+
+// 查询今日访问次数
+function getDayData(userId: number) {
+    return getTodayVisits(userId).then((res) => {
+
+        console.log('table-getdaydata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject();
+    });
+
+}
+
+// 查询本月访问次数
+function getMonthData(userId: number) {
+    console.log('table-getmonthdata:', userId)
+    return getMonthVisits(userId).then((res) => {
+        console.log('table-getmonthdata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        });
+        return Promise.reject();
+    });
+}
+
+// 查询累计访问次数
+function getAllData(userId: number) {
+    return getTotalVisits(userId).then((res) => {
+
+        console.log('table-getalldata:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject();
+    });
+
+}
+
+
+
+// 查询个人访问次数-
+function getPersonalVisitsData(deptId: number, nickName: string, pageNumber: number, pageSize: number, staffNo: string) {
+    console.log('table-getPersonalVisitsData-deptid', deptId);
+    console.log('table-getPersonalVisitsData-nama', nickName);
+    console.log('table-getPersonalVisitsData', pageNumber, pageSize);
+    console.log('table-getPersonalVisitsData-staffno', staffNo);
+    return getPersonalVisits(pageNumber, pageSize, staffNo, deptId, nickName).then((res) => {
+        console.log('table-getPersonalVisitsData:', res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+        return Promise.reject();
+    });
+}
+
+</script>
+
+<style scoped></style>

+ 253 - 0
src/views/datamanager/systemdata/TableCommon.vue

@@ -0,0 +1,253 @@
+<template>
+    <div class="form">
+        <el-form ref="ruleFormRef" :model="ruleForm" inline label-width="60px">
+
+            <el-form-item label="姓名:" prop="nickName" style="margin-top: 15px;">
+                <el-input v-model="ruleForm.nickName" placeholder="请输入姓名!" style="width: 150px;"></el-input>
+            </el-form-item>
+
+            <el-form-item label="工号:" prop="staffNo" style="margin-top: 15px;">
+                <el-input v-model="ruleForm.staffNo" placeholder="请输入工号!" style="width: 150px;"></el-input>
+            </el-form-item>
+
+            <el-form-item label="部门:" prop="dept" style="margin-top: 15px;">
+                <el-select v-model="ruleForm.dept" placeholder="请选择部门" style="width: 100px ;">
+                    <el-option v-for="item in BoardDeptList" :key="item.value" :value="item.value"
+                        :label="item.label"></el-option>
+                </el-select>
+            </el-form-item>
+
+            <el-button type="primary" @click="submitForm(ruleFormRef)" style="width: 100px;">搜索</el-button>
+            <el-button @click="resetForm(ruleFormRef)" style="width: 100px;">重置</el-button>
+            <el-form-item>
+
+            </el-form-item>
+
+        </el-form>
+
+        <div>
+            <el-tag size="large">数据总表</el-tag>
+        </div>
+        <el-table style="width: 100%;margin-top: 20px;" :data="tableData" highlight-current-row
+            :default-sort="{ prop: 'todayVisits', order: 'descending' }">
+            <el-table-column label="序号" width="60" type="index"></el-table-column>
+            <el-table-column label="姓名" width="100" prop="nickName"></el-table-column>
+            <el-table-column label="工号" width="100" prop="staffNo"></el-table-column>
+            <el-table-column label="部门" width="120" prop="deptName"></el-table-column>
+            <el-table-column :label="`当日` + props.type" width="140" prop="todayVisits" sortable></el-table-column>
+            <el-table-column :label="`本月` + props.type" width="140" prop="monthVisits" sortable></el-table-column>
+            <el-table-column :label="`累计` + props.type" width="140" prop="totalVisits" sortable></el-table-column>
+            <el-table-column :label="props.type + '柱状图'" width="150">
+                <template #default="scoped">
+                    <el-button type="primary" :icon="TrendCharts" @click="openDialog(scoped.row)"></el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+
+
+        <el-pagination v-model="currentPage" v-model:currentPageSize="currentPageSize" :page-sizes="[10, 20, 50, 100, 200]"
+            layout="total,sizes,prev,pager,next,jumper" :total="total" @size-change="handeSizeChange"
+            @current-change="handleCurrentPageChange" style="margin-left: 500px;" />
+
+    </div>
+
+
+    <TableEcharts v-model="dialogVisible" :dialog-visible="dialogVisible" :userId="userId"
+        :current-day-chart="currentDayData" :current-month-chart="currentMonthData" :all-datas-chart="currentAllData"
+        :close-dialog="closeDialog">
+    </TableEcharts>
+</template>
+
+<script setup lang="ts">
+
+import { ref, onMounted } from 'vue';
+import { FormInstance } from 'element-plus';
+import { BoardDeptEnum, BoardDeptList } from './config'
+import TableEcharts from './TableEcharts.vue';
+import { TrendCharts } from '@element-plus/icons-vue'
+import { Records, Visits, VisitsModel } from '../../../api/datamanagement/dataplatform';
+
+export interface TableModel {
+    deptId: number,
+    deptName: string,
+    monthVisits: number,
+    nickName: string,
+    staffNo: string,
+    todayVisits: number,
+    totalVisits: number,
+    userId: number,
+}
+
+// const props = defineProps<{ type: string }>()
+const props = defineProps<{
+    type: string,
+    queryData: (d1: number, d2: number) => Promise<Visits<Records>>,
+    currentDayData: (d: number) => Promise<VisitsModel[]>,
+    currentMonthData: (d: number) => Promise<VisitsModel[]>,
+    currentAllData: (d: number) => Promise<VisitsModel[]>,
+    getPersonalVisits: (d1: number | undefined, d2: string, d3: number, d4: number, d5: string) => Promise<Visits<Records>>,
+
+}>();
+
+const dialogVisible = ref(false);//控制弹框显示
+const currentPage = ref(1);
+const currentPageSize = ref(10);
+const total = ref(30);
+const tableData = ref<TableModel[]>()
+const userId = ref(10014);
+
+export interface FormModelCommon {
+    dept: number,
+    nickName: string,
+    staffNo: string,
+}
+
+const ruleForm = ref<FormModelCommon>({
+    dept: BoardDeptEnum.all,
+    nickName: '',
+    staffNo: '',
+})
+const ruleFormRef = ref<FormInstance>();
+
+
+// 查询数据列表
+function queryData() {
+    return props.queryData(currentPage.value, currentPageSize.value).then(res => {
+        console.log('tablecommon-querydata:', res)
+        tableData.value = res.records;
+        total.value = res.totalRow;
+    })
+
+}
+
+// 根据指定参数查询数据-搜索数据
+// function queryDataByParams() {
+//     props.queryDataByParams().then(res => {
+//         console.log(res)
+//     })
+//     // emit('queryDataByParams', data);
+// }
+
+// interface FormTable {
+//     text: string,
+//     dept: string,
+// }
+
+// 搜索数据
+function submitForm(formE1: FormInstance | undefined) {
+
+    if (!formE1) return
+    formE1.validate((valid, fields) => {
+        if (valid) {
+
+
+            getPersonalVisits(ruleForm.value)
+        }
+        else {
+            console.log('error submit!', fields);
+        }
+    })
+}
+
+// 重置表单
+function resetForm(formE1: FormInstance | undefined) {
+    if (!formE1) return
+    formE1.resetFields()
+    console.log('resetForm')
+    queryData();
+}
+
+// 翻页
+function handleCurrentPageChange(val: number) {
+    currentPage.value = val
+    console.log('currentPage:' + currentPage.value)
+    queryData();
+}
+
+// 页数
+function handeSizeChange(val: number) {
+    currentPageSize.value = val
+    console.log('currentPageSize:' + currentPageSize.value)
+    queryData()
+}
+
+function closeDialog() {
+    dialogVisible.value = false;
+}
+
+// 查询今日访问次数
+function currentDayData(userId: number) {
+    console.log('table-common-currentDayData:', userId)
+    return props.currentDayData(userId).then(res => {
+        console.log('table-common-currentdaydata:', res)
+        return res
+    })
+
+}
+
+// 查询本月访问次数
+function currentMonthData(userId: number) {
+    console.log('table-common-currentMonthData:', userId)
+    return props.currentMonthData(userId).then(res => {
+        console.log('table-common-currentmonthdata:', res);
+        return res
+    })
+
+}
+
+// 查询累计访问次数
+function currentAllData(userId: number) {
+    return props.currentAllData(userId).then(res => {
+        console.log('table-common:', res);
+        return res;
+    })
+
+}
+
+// 查询个人访问次数-搜索数据
+function getPersonalVisits(data: FormModelCommon) {
+    const newParam = {
+        pageNumber: currentPage.value,
+        pageSize: currentPageSize.value,
+        nickName: data.nickName,
+        deptId: data.dept === BoardDeptEnum.all ? undefined : data.dept,
+        staffNo: data.staffNo,
+    }
+    props.getPersonalVisits(newParam.deptId, newParam.nickName, newParam.pageNumber, newParam.pageSize, newParam.staffNo).then(res => {
+        console.log('table-common-getPersonalVisits:', res)
+        tableData.value = res.records;
+
+    })
+}
+
+// 打开数据表
+function openDialog(row: TableModel) {
+    console.log('tablecommon-opendialog');
+    console.log(row.userId);
+    // tbData.value=row;
+    userId.value = row.userId;
+    dialogVisible.value = true;
+
+    // currentDayData();
+}
+
+
+
+onMounted(() => {
+    queryData()
+})
+
+</script>
+
+<style scoped>
+.form {
+    /* width: 1100px; */
+    margin-top: 30px;
+    margin-bottom: 30px;
+}
+
+
+.el-pagination {
+    margin-top: 30px;
+}
+</style>

+ 400 - 0
src/views/datamanager/systemdata/TableEcharts.vue

@@ -0,0 +1,400 @@
+<template>
+    <div>
+        <el-dialog v-model="visible" :before-close="handleClose" center style="width: 700px;">
+            <el-card>
+                <template #header>
+                    <div v-if="isShowAll === false" style="text-align: center;">
+                        <el-button type="text" @click="currentDayChart">今日</el-button>
+                        <el-button type="text" @click="currentMonthChart">本月</el-button>
+                        <el-button type="text" @click="allDatasChart">累计</el-button>
+
+                    </div>
+                    <div v-else>
+                        <el-form inline ref="ruleFormRef" :model="form" style="width:600px">
+                            <el-form-item style="margin-top: 20px;">
+                                <el-select v-model="form.workspace" placeholder="请选择车间"
+                                    style="width: 140px ;text-align: left;">
+                                    <el-option v-for="item in BoardDeptList" :key="item.value" :value="item.value"
+                                        :label="item.label"></el-option>
+                                </el-select>
+                            </el-form-item>
+                            <el-button style="text-align: left;width: 100px;" type="primary"
+                                @click="getDepartmentData">生成柱状图</el-button>
+                            <el-button style="text-align: left; margin-left: 180px;" type="text"
+                                @click="currentDayChart">今日</el-button>
+                            <el-button style="text-align: left;" type="text" @click="currentMonthChart">本月</el-button>
+                            <el-button style="text-align: left;" type="text" @click="allDatasChart">累计</el-button>
+
+                        </el-form>
+
+                    </div>
+                </template>
+                <div id="container" style="width: 600px; height: 470px;margin-left: 20px;"></div>
+            </el-card>
+        </el-dialog>
+    </div>
+</template>
+
+<script setup lang="ts">
+
+import * as echarts from 'echarts';
+import { ref, markRaw } from 'vue';
+import { ElMessageBox } from 'element-plus';
+import { BoardDeptList } from './config'
+import { watch } from 'vue';
+import { onMounted } from 'vue';
+import { VisitsModel } from '@/api/datamanagement/dataplatform';
+
+interface DataOption {
+    title: {},//标题
+    tooltip: {},//虚线
+    calculate: Boolean,
+    xAxis: {},
+    yAxis: {},
+    series: SerialModel[]
+}
+
+
+
+
+const visible = ref(false);
+const userId = ref(1);
+const isShowAll = ref(false);//控制标题是否居中
+const chart = ref<any>("");
+
+
+
+
+watch(
+    () => props.dialogVisible,
+    (newvisible) => {
+        visible.value = newvisible
+        console.log('tablechart', visible.value)
+    },
+    { immediate: true },
+)
+watch(
+    () => props.userId,
+    (newuserId) => {
+        userId.value = newuserId
+        console.log('tablechart-data:', userId.value)
+        currentDayChart()
+    },
+    { immediate: true },
+)
+
+const form = ref({
+    workspace: '',
+});
+
+
+const props = defineProps<{
+    dialogVisible: boolean,
+    userId: number,
+    currentDayChart: (d: number) => Promise<VisitsModel[]>,
+    currentMonthChart: (d: number) => Promise<VisitsModel[]>,
+    allDatasChart: (d: number) => Promise<VisitsModel[]>,
+    closeDialog(),
+    // getDepartmentData: () => Promise<unknown>,
+
+}>();
+
+
+
+// 关闭对话框
+function closeDialog() {
+    visible.value = false;
+    props.closeDialog();
+    // emit('closeDialog');
+}
+
+// 关闭对话框
+function handleClose(done: () => void) {
+    ElMessageBox.confirm('确认关闭?')
+        .then(() => {
+
+            done()
+            closeDialog();//关闭对话框
+        })
+        .catch(() => {
+
+            // catch error
+        })
+}
+
+// 查看今日数据
+function currentDayChart() {
+    isShowAll.value = false;
+    console.log('table-chart-currentday');
+    const daytitle = ref();
+    const seriesdata = ref()
+    props.currentDayChart(userId.value).then(res => {
+        daytitle.value = getHorizontalTitle(res);
+        seriesdata.value = getVorizontalData(res);
+        console.log('查看今日数据', res);
+        console.log('查看今日数据', daytitle);
+        console.log('查看今日数据', seriesdata);
+        initChart(optionday.value, daytitle.value, seriesdata.value)
+    })
+
+};
+
+// 查看本月数据
+function currentMonthChart() {
+    isShowAll.value = false;
+    console.log('currentmonth:', userId.value);
+    const monthtitle = ref();
+    const monthseriesdata = ref()
+    props.currentMonthChart(userId.value).then(res => {
+        console.log('table-chart-currentmonth:', res);
+        monthtitle.value = getHorizontalTitle(res);
+        monthseriesdata.value = getVorizontalData(res);
+        console.log('查看今日数据', res);
+        console.log('查看今日数据', monthtitle);
+        console.log('查看今日数据', monthseriesdata);
+        initChart(optionmonth.value, monthtitle.value, monthseriesdata.value)
+    })
+};
+
+
+// 查看累计数据
+function allDatasChart() {
+    isShowAll.value = true;
+    console.log('alldata');
+    const alltitle = ref();
+    const allseriesdata = ref()
+    props.allDatasChart(userId.value).then(res => {
+        console.log(res);
+        alltitle.value = getHorizontalTitle(res);
+        allseriesdata.value = getVorizontalData(res);
+        console.log('查看今日数据', res);
+        console.log('查看今日数据', alltitle);
+        console.log('查看今日数据', allseriesdata);
+        initChart(optionall.value, alltitle.value, allseriesdata.value)
+    })
+}
+
+interface SerialModel {
+    data: [],
+    type: string,
+}
+
+
+function initChart(data: DataOption, title: [], series: []) {
+    data.xAxis = {};
+    const dataseries = ref<SerialModel[]>([]);
+
+    const type = ref('category');
+    const horidata = ref([]) //todo,获取横坐标
+    const nameLocation = ref('center');
+    const axisLabel = { interval: 0, rotate: 30 };
+    const nameTextStyle = {
+        color: 'red',
+        fontSize: 6,
+    };
+    horidata.value = title;
+
+    data.xAxis = { type: type.value, data: horidata.value, nameLocation: nameLocation.value, axisLabel: axisLabel, nameTextStyle: nameTextStyle }
+
+
+    const seriesdata = ref<SerialModel>({
+        data: [],//todo,获取数值,
+        type: 'bar'
+    })
+    seriesdata.value.data = series;
+    dataseries.value.push(seriesdata.value)
+    data.series = dataseries.value;
+
+    console.log('data.xAxis.data', data.xAxis);
+    console.log('data.yAxis.data', data.series)
+    console.log(data)
+    createChart(data);
+
+}
+
+// 创建图表
+function createChart(option: DataOption) {
+    chart.value = markRaw(echarts.init(document.getElementById('container') as HTMLDivElement))
+
+    chart.value.setOption(option);
+
+    // 大小自适应
+    window.addEventListener('resize', () => {
+        chart.value.resize();
+    })
+}
+
+// 创建柱状图
+function getDepartmentData() {
+    // todo,原型未定义
+}
+
+
+
+function getHorizontalTitle(data: VisitsModel[]) {
+    console.log('getHorizontalTitle:', data)
+    const title = ref<string[]>([]);
+    for (var i = 0; i < data.length; i++) {
+        title.value.push(data[i].workshopName)
+        console.log('getHorizontalTitle-data-i', data[i].workshopName)
+    }
+    console.log('title:', title);
+    return title;
+}
+
+function getVorizontalData(data: VisitsModel[]) {
+    console.log('getvorizontaldata:', data)
+    const seriesData = ref<number[]>([]);
+    for (var i = 0; i < data.length; i++) {
+        seriesData.value.push(data[i].visits)
+        console.log('getHorizontalTitle-data-i', data[i].visits)
+    }
+    console.log('seriesData:', seriesData);
+    return seriesData;
+}
+
+const optionday = ref();
+optionday.value = {
+    title: {
+        text: '各车间地点访问次数柱状图(天)',
+        x: "center", //设置标题位置居中
+        textStyle: {//设置主标题的文字风格
+            fontSize: 10 //文字大小
+        },
+
+
+
+    },//标题
+    tooltip: {
+        trigger: 'axis'
+    },//虚线
+    calculate: true,//显示数据
+    xAxis: {
+        type: 'category',
+        data: [],//todo,获取横坐标
+        nameLocation: 'center',
+        axisLabel: { interval: 0, rotate: 30 },
+        nameTextStyle: {
+            color: 'red',
+            fontSize: 6,
+
+        }
+    },
+    yAxis: {
+        type: 'value',
+        name: '访问次数',
+        nameTextStyle: {
+            color: 'black',
+            fontSize: 8,
+            padding: 0,
+        }
+
+    },
+    series: [
+        {
+            data: [],//todo,获取数值,
+            type: 'bar'
+        }
+    ]
+};
+
+
+const optionmonth = ref();
+optionmonth.value = {
+    title: {
+        text: '各车间地点访问次数柱状图(月)',
+        x: "center", //设置标题位置居中
+        textStyle: {//设置主标题的文字风格
+            fontSize: 10 //文字大小
+        },
+
+
+
+    },//标题
+    tooltip: {
+        trigger: 'axis'
+    },//虚线
+    calculate: true,//显示数据
+    xAxis: {
+        type: 'category',
+        data: [],//todo,获取横坐标
+        nameLocation: 'center',
+        axisLabel: { interval: 0, rotate: 30 },
+        nameTextStyle: {
+            color: 'red',
+            fontSize: 6,
+
+        }
+    },
+    yAxis: {
+        type: 'value',
+        name: '访问次数',
+        nameTextStyle: {
+            color: 'black',
+            fontSize: 8,
+            padding: 0,
+        }
+
+    },
+    series: [
+        {
+            data: [],//todo,获取数值,
+            type: 'bar'
+        }
+    ]
+};
+
+
+const optionall = ref();
+optionall.value = {
+    title: {
+        text: '各车间地点访问次数柱状图(汇总)',
+        x: "center", //设置标题位置居中
+        textStyle: {//设置主标题的文字风格
+            fontSize: 10 //文字大小
+        },
+
+
+
+    },//标题
+    tooltip: {
+        trigger: 'axis'
+    },//虚线
+    calculate: true,//显示数据
+    xAxis: {
+        type: 'category',
+        data: [],//todo,获取横坐标
+        nameLocation: 'center',
+        axisLabel: { interval: 0, rotate: 30 },
+        nameTextStyle: {
+            color: 'red',
+            fontSize: 6,
+
+        }
+    },
+    yAxis: {
+        type: 'value',
+        name: '访问次数',
+        nameTextStyle: {
+            color: 'black',
+            fontSize: 8,
+            padding: 0,
+        }
+
+    },
+    series: [
+        {
+            data: [],//todo,获取数值,
+            type: 'bar'
+        }
+    ]
+};
+
+
+onMounted(() => {
+    isShowAll.value = false;
+})
+
+</script>
+
+<style scoped></style>

+ 33 - 0
src/views/datamanager/systemdata/config.ts

@@ -0,0 +1,33 @@
+
+
+export const BoardSelectList = [
+    {
+        label: '姓名',
+        value: 0,
+    },
+    {
+        label: '工号',
+        value: 1,
+    },
+
+]
+
+
+export enum BoardDeptEnum {
+    all = 100,
+    '5g' = 0,
+    'c02' = 1,
+    'c12' = 2,
+    'top' = 12,
+}
+
+export const BoardDeptList = [
+    {
+        label: '全部',
+        value: BoardDeptEnum.all,
+    },
+    {
+        label: '总部公司',
+        value: BoardDeptEnum.top,
+    },
+]

+ 207 - 0
src/views/system-config/template/CardCommon.vue

@@ -0,0 +1,207 @@
+<template>
+  <div>
+
+
+    <TitleCommon v-model="cardaddDrawer" :type="props.type" :pagesize="currentPageSize" :pagenumber="currentPage"
+      :drawer="cardaddDrawer" @find-data-by-name="searchDataByName" @submit-drawer="addData" @add-form="clickAddData"
+      @handle-close="closeDrawer" @clear-data="clearData">
+    </TitleCommon>
+
+    <div class="table" style="margin-top: 20px;">
+      <el-table style="width: 100%" :data="tableData" highlight-current-row>
+        <el-table-column :label="props.type + `名称`" width="250" prop="name" />
+        <el-table-column :label="props.type + `代码`" width="200" prop="code" />
+
+        <el-table-column prop="status" label="状态" width="150">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status === 0" type="success">正常</el-tag>
+            <el-tag v-else type="danger">异常</el-tag>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="备注" width="200" prop="remark" />
+        <el-table-column label="创建时间" width="250" prop="createdAt" />
+        <el-table-column label="操作" width="200">
+          <template #default="scoped">
+            <el-button size="small" @click="deleteTableData(scoped.row)">删除</el-button>
+            <el-button size="small" @click="showEditDrawer(scoped.row)">编辑</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination v-model:currentPage="currentPage" v-model:currentPageSize="currentPageSize"
+        :page-sizes="[10, 20, 50, 100, 200]" layout="total,sizes,prev,pager,next,jumper" :total="total"
+        @size-change="handleSizeChangeFun" @current-change="handlePageChangeFun"
+        style="margin-left: 600px; margin-top: 20px" />
+    </div>
+
+    <el-drawer v-model="cardeditDrawer" direction="rtl" :title="currentType" :before-close="handleClose">
+      <DrawerCommonVue :detail="currentdrawerFormData" v-if="currentdrawerFormData" @submit-drawer="submitDrawer" />
+    </el-drawer>
+
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from 'vue';
+import { ElDrawer, ElMessageBox, } from 'element-plus';
+import DrawerCommonVue, { FormModelCommon } from './DrawerCommon.vue';
+import TitleCommon, { DataCommon } from './TitleCommon.vue';
+import { Records, SceneModule } from '@/api/template/template';
+
+// const currentFormType = ref('场景');
+
+export type CreateType = '添加模板' | '修改模板';
+export interface TableModel {
+  id: number;
+  name: string;
+  code: string;
+  status: number;
+  remark: string;
+  createAt: string;
+}
+
+export interface ModelCommon {
+  drawerFormData: FormModelCommon | null
+}
+
+export interface Page {
+  page: number,
+  size: number,
+}
+
+const currentType = ref<CreateType>('添加模板');
+const currentdrawerFormData = ref<FormModelCommon>({} as FormModelCommon);
+const cardeditDrawer = ref(false);
+const cardaddDrawer = ref(false);
+const currentPage = ref(1);
+const currentPageSize = ref(10);
+const total = ref(100);
+const tableData = ref<Records[]>([]);
+
+
+
+
+const props = defineProps<{
+  type: string,
+  addData: (d: SceneModule) => Promise<unknown>,
+  searchDataByName: (d: DataCommon) => Promise<unknown>
+  deleteTableData: (d: TableModel) => Promise<unknown>,
+  submitDrawer: (d: SceneModule) => Promise<unknown>,
+  getData: (d: Page) => Promise<unknown>,
+}>();
+
+
+// 删除数据-finish
+function deleteTableData(row: TableModel) {
+  // emit('deleteTableData', row);
+  props.deleteTableData(row).then(res => {
+    console.log('deleteTableData', res);
+    getData({ page: currentPage.value, size: currentPageSize.value });
+  })
+}
+
+// 编辑数据弹框-finish
+function showEditDrawer(row: TableModel) {
+  cardeditDrawer.value = true;
+  console.log('row', row)
+  currentdrawerFormData.value = row;
+}
+
+// 弹框关闭事件-finish
+const handleClose = (done: () => void) => {
+  ElMessageBox.confirm('是否确认关闭?')
+    .then(() => {
+      done();
+      closeDrawer();
+
+    })
+    .catch(() => {
+      // catch error
+    });
+
+};
+
+
+// 编辑提交数据
+function submitDrawer(data: SceneModule) {
+  console.log('编辑提交数据:', data)
+  props.submitDrawer(data).then(res => {
+    console.log('submitDrawer', res)
+    closeDrawer();
+    getData({ page: currentPage.value, size: currentPageSize.value });
+  })
+}
+
+
+
+// 查询数据列表-finish
+function getData(data: Page) {
+  props.getData(data).then(res => {
+    console.log('getData', res)
+    tableData.value = res.records;
+    total.value = res.totalRow;
+  });
+
+}
+
+// 根据名称找到数据-finish
+function searchDataByName(data: DataCommon) {
+  props.searchDataByName(data).then(res => {
+    console.log('searchDataByName', res);
+    tableData.value = res.records;
+  })
+}
+
+// 单机添加按钮,控制弹框显示-finish
+function clickAddData() {
+  cardaddDrawer.value = true;
+
+}
+
+// 添加数据-finish
+function addData(data: SceneModule) {
+
+  props.addData(data).then(res => {
+    console.log('addData', res);
+    closeDrawer();
+
+    getData({ page: currentPage.value, size: currentPageSize.value });
+  })
+
+}
+
+function closeDrawer() {
+  cardaddDrawer.value = false;
+  cardeditDrawer.value = false;
+  getData({ page: currentPage.value, size: currentPageSize.value });
+}
+
+// 清空输入框数据-finish
+function clearData() {
+  // getSceneLabelList(currentPage.value, currentPageSize.value);
+  getData({ page: currentPage.value, size: currentPageSize.value });
+}
+
+// 页数-finish
+function handleSizeChangeFun(val: number) {
+  currentPageSize.value = val;
+  getData({ page: currentPage.value, size: currentPageSize.value });
+}
+
+// 翻页-finish
+function handlePageChangeFun(val: number) {
+  currentPage.value = val;
+  console.log('currentPage:' + currentPage.value);
+  getData({ page: currentPage.value, size: currentPageSize.value });
+}
+
+
+onMounted(() => {
+  // getData(props.currentPage, props.currentPageSize);
+  getData({ page: currentPage.value, size: currentPageSize.value });
+
+});
+</script>
+
+<style scoped></style>

+ 113 - 0
src/views/system-config/template/DrawerCommon.vue

@@ -0,0 +1,113 @@
+<template>
+  <div>
+    <el-form ref="ruleDrawerRef" :model="form" label-width="100px">
+      <el-form-item label="模板名称" prop="name" :rules="[
+        {
+          required: true,
+          message: '输入模板名称',
+          trigger: 'blur',
+        },
+      ]">
+        <el-input placeholder="请输入模板名称" v-model="form.name" />
+      </el-form-item>
+
+      <el-form-item label="模板代码" prop="code" :rules="[
+        {
+          required: true,
+          message: '输入模板代码',
+          trigger: 'blur',
+        },
+      ]">
+        <el-input placeholder="请输入模板代码" v-model="form.code" />
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input placeholder="请输入备注" v-model="form.remark" />
+      </el-form-item>
+
+      <el-form-item label="模板上传">
+        <el-upload>
+          <el-button disabled>上传</el-button>
+        </el-upload>
+      </el-form-item>
+
+      <el-form-item label="状态" prop="status">
+        <el-switch v-model="form.status" active-value=1 inactive-value=0 />
+      </el-form-item>
+
+      <div style="margin-top: 100px; margin-left: 250px">
+        <el-button style="margin-left: 50px" @click="resetForm(ruleDrawerRef)">重置</el-button>
+        <el-button style="margin-left: 50px" @click="submitDrawer(ruleDrawerRef)">提交</el-button>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+import { FormInstance } from 'element-plus';
+import { onMounted } from 'vue';
+
+
+export interface FormModelCommon {
+  id: number;
+  code: string;
+  name: string;
+  status: number;
+  remark: string;
+}
+export type CreateType = 'create' | 'edit';
+const props = defineProps<{ detail: FormModelCommon }>();
+const emit = defineEmits(['submitDrawer']);
+const form = ref<FormModelCommon>({
+  id: props.detail.id,
+  name: props.detail.name,
+  code: props.detail.code,
+  remark: props.detail.remark,
+  status: props.detail.status,
+});
+
+watch(
+  () => props.detail,
+  (newdetail) => {
+    form.value = newdetail;
+  },
+  { immediate: true },
+);
+
+const ruleDrawerRef = ref<FormInstance>();
+
+function resetForm(formE1: FormInstance | undefined) {
+  if (!formE1) return;
+  formE1.resetFields();
+  console.log('resetForm');
+
+}
+
+function submitDrawer(formEl: FormInstance | undefined) {
+  console.log('common-', form, props.detail.code)
+  if (!formEl) return;
+  formEl.validate((valid, fields) => {
+    if (valid) {
+      const drawerData = {
+        id: form.value.id,
+        name: form.value.name,
+        code: form.value.code,
+        remark: form.value.remark,
+        status: form.value.status,
+      };
+      emit('submitDrawer', drawerData);
+
+    } else {
+      console.log('error submit!', fields);
+    }
+  });
+}
+
+onMounted(() => {
+  console.log('drawercommon-form', form);
+  console.log('drawercommon-props', props.detail);
+});
+</script>
+
+<style scoped></style>

+ 121 - 0
src/views/system-config/template/LabelManager.vue

@@ -0,0 +1,121 @@
+<template>
+    <div>
+
+        <CardCommon :type='type' :search-data-by-name="findSceneLabelFun" :get-data="getSceneLabelListFun"
+            :delete-table-data="deleteSceneLabelFun" :submit-drawer="updateSceneLabelFun" :add-data="saveSceneLabelFun">
+        </CardCommon>
+
+    </div>
+</template>
+
+<script setup lang="ts">
+import { updateSceneLabel, getSceneLabelList, deleteSceneLabel, findSceneLabelByName, saveSceneLabel, SceneLabel } from '@/api/template/template';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import CardCommon, { TableModel } from './CardCommon.vue'
+import { DataCommon } from './TitleCommon.vue';
+import { ref } from 'vue';
+const type = ref('标签');
+
+
+// 添加数据
+function saveSceneLabelFun(data: SceneLabel) {
+    return saveSceneLabel(data).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `添加数据失败`,
+                })
+            },
+        })
+    });
+};
+
+// 根据名称查找
+function findSceneLabelFun(data: DataCommon) {
+    console.log('findSceneLabelByName', data.name, data.page, data.size);
+    return findSceneLabelByName(data.name, data.page, data.size).then((res) => {
+        console.log(res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 查找数据列表-finish
+function getSceneLabelListFun(data) {
+    return getSceneLabelList(data.page, data.size).then((res) => {
+        return res
+    }).catch(error => {
+        ElMessageBox.alert(error.message, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 删除数据
+function deleteSceneLabelFun(data: TableModel) {
+    console.log('detele', data);
+    return deleteSceneLabel(data.id).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `删除数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 编辑数据
+function updateSceneLabelFun(data: SceneLabel) {
+    console.log('edit', data);
+    return updateSceneLabel(data).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `编辑数据失败`,
+                })
+            },
+        })
+    });
+}
+
+
+
+
+
+</script>
+
+<style scoped></style>

+ 121 - 0
src/views/system-config/template/SceneManager.vue

@@ -0,0 +1,121 @@
+<template>
+    <div>
+
+        <CardCommon :type='type' :search-data-by-name="findSceneModuleFun" :get-data="getSceneLabelListFun"
+            :delete-table-data="deleteSceneModuleFun" :submit-drawer="updateSceneModuleFun" :add-data="saveSceneModuleFun">
+        </CardCommon>
+
+    </div>
+</template>
+
+<script setup lang="ts">
+import { updateSceneModule, getSceneModuleList, deleteSceneModule, findSceneModuleByName, saveSceneModule, SceneModule } from '@/api/template/template';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import CardCommon, { TableModel } from './CardCommon.vue'
+import { DataCommon } from './TitleCommon.vue';
+import { ref } from 'vue';
+const type = ref('模板');
+
+
+// 添加数据
+function saveSceneModuleFun(data: SceneModule) {
+    return saveSceneModule(data).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `添加数据失败`,
+                })
+            },
+        })
+    });
+};
+
+// 根据名称查找
+function findSceneModuleFun(data: DataCommon) {
+    console.log('findSceneModuleByName', data.name, data.page, data.size);
+    return findSceneModuleByName(data.name, data.page, data.size).then((res) => {
+        console.log(res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 查找数据列表-finish
+function getSceneLabelListFun(data) {
+    return getSceneModuleList(data.page, data.size).then((res) => {
+        return res
+    }).catch(error => {
+        ElMessageBox.alert(error.message, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 删除数据
+function deleteSceneModuleFun(data: TableModel) {
+    console.log('detele', data);
+    return deleteSceneModule(data.id).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `删除数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 编辑数据
+function updateSceneModuleFun(data: SceneModule) {
+    console.log('edit', data);
+    return updateSceneModule(data).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `编辑数据失败`,
+                })
+            },
+        })
+    });
+}
+
+
+
+
+
+</script>
+
+<style scoped></style>

+ 50 - 0
src/views/system-config/template/TemplateManager.vue

@@ -0,0 +1,50 @@
+<template>
+  <div style="width: 100%">
+    <div>
+      <el-tabs v-model="activeName" type="card" @tab-click="handleClick">
+        <el-tab-pane label="场景管理" name="scene">
+          <SceneManagerVue />
+        </el-tab-pane>
+        <el-tab-pane label="场景标签管理" name="label">
+          <LabelManagerVue />
+        </el-tab-pane>
+        <el-tab-pane label="车间模板" name="workspace">
+          <WorkspaceManagerVue />
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+
+
+
+  </div>
+</template>
+  
+<script setup lang="ts">
+import { ref } from 'vue';
+import type { TabsPaneContext } from 'element-plus';
+import LabelManagerVue from './LabelManager.vue';
+import WorkspaceManagerVue from './WorkspaceManager.vue';
+import SceneManagerVue from './SceneManager.vue';
+
+
+const activeName = ref('scene');
+export type LabelType = 'scene' | 'label' | 'workspace';
+const currentLabel = ref<LabelType>('scene');
+
+const handleClick = (tab: TabsPaneContext) => {
+  console.log(tab.paneName);
+  if (tab.paneName === 'scene') {
+    currentLabel.value = 'scene';
+  } else {
+    if (tab.paneName === 'label') {
+      currentLabel.value = 'label';
+    } else {
+      currentLabel.value = 'workspace';
+
+    }
+  }
+};
+</script>
+  
+<style scoped></style>
+  

+ 102 - 0
src/views/system-config/template/TitleCommon.vue

@@ -0,0 +1,102 @@
+<template>
+    <div>
+        <el-form inline ref="ruleFormRef" :model="form">
+            <el-form-item style="margin-top: 20px;">
+                <el-input style="width: 200px" :placeholder="`请输入` + props.type + `名称`" v-model="form.name" clearable
+                    @clear="clearData" />
+            </el-form-item>
+            <el-button type="primary" :icon="Search" @click="findDataByName(ruleFormRef)">查询</el-button>
+            <el-button type="primary" :icon="Files" @click="addForm"> 添加</el-button>
+        </el-form>
+    </div>
+
+
+    <el-drawer v-model="titleDrawer" direction="rtl" :title="currentType" :before-close="handleClose">
+        <DrawerCommonVue :detail="drawerFormData" @submit-drawer="submitDrawer" />
+    </el-drawer>
+</template>
+
+<script setup lang="ts">
+import { Search, Files } from '@element-plus/icons-vue';
+import { onMounted, ref, watch } from 'vue';
+import { ElMessageBox, FormInstance } from 'element-plus';
+import DrawerCommonVue, { FormModelCommon } from './DrawerCommon.vue';
+
+export type CreateType = '添加模板' | '修改模板';
+const props = defineProps<{ type: string, pagesize: number, pagenumber: number, drawer: boolean }>();
+const emit = defineEmits(['findDataByName', 'submitDrawer', 'handleClose', 'addForm', 'clearData'])
+const form = ref({
+    name: '',
+});
+const ruleFormRef = ref<FormInstance>();
+const currentType = ref<CreateType>('添加模板');
+const drawerFormData = ref<FormModelCommon>({} as FormModelCommon);
+
+
+export interface DataCommon {
+    name: string,
+    size: number,
+    page: number,
+}
+
+const titleDrawer = ref(false);
+watch(
+    () => props.drawer,
+    (newdrawer) => {
+        titleDrawer.value = newdrawer;
+    },
+    { immediate: true },
+);
+
+
+// 根据名称找到数据-finish
+function findDataByName(formEl: FormInstance | undefined) {
+    if (!formEl) return;
+    formEl.validate((valid, fields) => {
+        if (valid) {
+            const data = ref<DataCommon>({
+                name: form.value.name,
+                size: props.pagesize,
+                page: props.pagenumber,
+            })
+            emit('findDataByName', data.value);
+        } else {
+            console.log('error submit!', fields);
+        }
+    });
+}
+
+function addForm() {
+    currentType.value = '添加模板';
+    emit('addForm')
+}
+
+// 添加
+function submitDrawer(data) {
+    emit('submitDrawer', data);
+    drawerFormData.value = { id: 0, code: '', name: '', remark: '', status: 0 };
+}
+
+
+const handleClose = (done: () => void) => {
+    ElMessageBox.confirm('是否确认关闭?')
+        .then(() => {
+            done();
+        })
+        .catch(() => {
+            // catch error
+        });
+    emit('handleClose');
+};
+
+function clearData() {
+    emit('clearData');
+}
+
+
+onMounted(() => {
+
+});
+</script>
+
+<style scoped></style>

+ 122 - 0
src/views/system-config/template/WorkspaceManager.vue

@@ -0,0 +1,122 @@
+<template>
+    <div>
+
+        <CardCommon :type='type' :search-data-by-name="findWorkshopModuleFun" :get-data="getWorkshopModuleListFun"
+            :delete-table-data="deleteWorkshopModuleFun" :submit-drawer="updateWorkshopModuleFun"
+            :add-data="saveWorkshopModuleFun">
+        </CardCommon>
+
+    </div>
+</template>
+
+<script setup lang="ts">
+import { updateWorkshopModule, getWorkshopModuleList, deleteWorkshopModule, findWorkshopModuleByName, saveWorkshopModule, WorkshopModule } from '@/api/template/template';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import CardCommon, { TableModel } from './CardCommon.vue'
+import { DataCommon } from './TitleCommon.vue';
+import { ref } from 'vue';
+const type = ref('模板');
+
+
+// 添加数据
+function saveWorkshopModuleFun(data: WorkshopModule) {
+    return saveWorkshopModule(data).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `添加数据失败`,
+                })
+            },
+        })
+    });
+};
+
+// 根据名称查找
+function findWorkshopModuleFun(data: DataCommon) {
+    console.log('findWorkshopModuleByName', data.name, data.page, data.size);
+    return findWorkshopModuleByName(data.name, data.page, data.size).then((res) => {
+        console.log(res)
+        return res;
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 查找数据列表-finish
+function getWorkshopModuleListFun(data) {
+    return getWorkshopModuleList(data.page, data.size).then((res) => {
+        return res
+    }).catch(error => {
+        ElMessageBox.alert(error.message, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `查询数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 删除数据
+function deleteWorkshopModuleFun(data: TableModel) {
+    console.log('detele', data);
+    return deleteWorkshopModule(data.id).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `删除数据失败`,
+                })
+            },
+        })
+    });
+}
+
+// 编辑数据
+function updateWorkshopModuleFun(data: WorkshopModule) {
+    console.log('edit', data);
+    return updateWorkshopModule(data).then(res => {
+        console.log(res);
+        return res;
+
+    }).catch(error => {
+        ElMessageBox.alert(error, '异常', {
+            confirmButtonText: 'OK',
+            callback: () => {
+                ElMessage({
+                    type: 'info',
+                    message: `编辑数据失败`,
+                })
+            },
+        })
+    });
+}
+
+
+
+
+
+</script>
+
+<style scoped></style>