From 328968f75a4dd4292ebc71f01d759a824765ac72 Mon Sep 17 00:00:00 2001
From: Jay <csj123456>
Date: 星期五, 22 十一月 2024 10:00:42 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 src/views/data/video/nvr/index.vue              |  187 +-
 src/api/data/plan/item/index.ts                 |    2 
 types/env.d.ts                                  |    2 
 src/views/bpm/model/index.vue                   |   17 
 src/views/model/pre/item/MmPredictItemChart.vue |  253 ++++
 src/views/model/wiki/index.vue                  |    4 
 .env.local                                      |    5 
 .env.test                                       |    6 
 src/api/data/video/image/index.ts               |   50 
 src/api/model/pre/dm/index.ts                   |    2 
 src/views/data/wiki/index.vue                   |    4 
 src/views/model/mpk/file/MpkRun.vue             |    3 
 src/api/model/mcs/index.ts                      |   30 
 src/views/data/video/camera/index.vue           |  197 ++-
 src/views/data/channel/modbus/tag/index.vue     |   19 
 src/views/data/channel/http/api/tag/index.vue   |   20 
 src/router/modules/remaining.ts                 |   21 
 src/views/data/video/nvr/NvrCamera.vue          |  174 +-
 src/views/model/pre/item/index.vue              |   34 
 src/views/data/channel/opcua/tag/index.vue      |   25 
 src/api/model/pre/item/index.ts                 |  145 ++
 src/views/Home/Index.vue                        |   29 
 src/views/model/pre/type/ItemTypeForm.vue       |    2 
 src/utils/is.ts                                 |    3 
 src/views/model/pre/analysis/index.vue          | 1100 ++++++++++---------
 src/views/model/pre/item/MmPredictItemForm.vue  |  243 ++-
 /dev/null                                       |  149 --
 src/api/model/pre/type/index.ts                 |   43 
 src/views/model/pre/type/index.vue              |    2 
 src/views/Login/components/LoginForm.vue        |   32 
 .env.dev                                        |   10 
 src/utils/hostMap.ts                            |    2 
 src/utils/dict.ts                               |    3 
 src/views/model/mpk/file/MpkForm.vue            |    4 
 src/api/model/sche/model/index.ts               |   23 
 src/views/data/channel/opcda/tag/index.vue      |   19 
 src/views/data/channel/kio/tag/index.vue        |   19 
 src/views/data/video/camera/CameraImage.vue     |  114 ++
 src/views/data/point/DaPointChart.vue           |  213 ++-
 src/views/model/mpk/icon/index.vue              |    4 
 40 files changed, 1,944 insertions(+), 1,270 deletions(-)

diff --git a/.env.dev b/.env.dev
index 304200e..82ec13b 100644
--- a/.env.dev
+++ b/.env.dev
@@ -24,13 +24,19 @@
 VITE_SOURCEMAP=true
 
 # 打包路径
-VITE_BASE_PATH=/
+VITE_BASE_PATH=/plat
 
 # 输出路径
 VITE_OUT_DIR=dist
+
+# 公共静态文件路径
+VITE_STATIC_DIR=/
 
 # 商城H5会员端域名iai
 VITE_MALL_H5_DOMAIN='http://'
 
 # 验证码的开关
-VITE_APP_CAPTCHA_ENABLE=true
+VITE_APP_CAPTCHA_ENABLE=false
+
+# MDK模型上传路径
+MDK_UPLOAD_URL='http://localhost:48080/admin-api/model//pre/item/upload-model'
diff --git a/.env.local b/.env.local
index 005d2f0..b28e8e2 100644
--- a/.env.local
+++ b/.env.local
@@ -24,7 +24,10 @@
 VITE_SOURCEMAP=false
 
 # 打包路径
-VITE_BASE_PATH=/
+VITE_BASE_PATH=/plat
+
+# 公共静态文件路径
+VITE_STATIC_DIR=/
 
 # 商城H5会员端域名
 VITE_MALL_H5_DOMAIN='http://localhost:3000'
diff --git a/.env.test b/.env.test
index a087496..65af8e8 100644
--- a/.env.test
+++ b/.env.test
@@ -26,9 +26,15 @@
 # 打包路径
 VITE_BASE_PATH=/plat
 
+# 数据采集服务所在服务器,映射截图图片用
+VITE_VIDEO_CAMERA_DOMAIN='172.16.59.105'
+
 # 输出路径
 VITE_OUT_DIR=dist
 
+# 公共静态文件路径
+VITE_STATIC_DIR=/plat/
+
 # 商城H5会员端域名
 VITE_MALL_H5_DOMAIN='http://'
 
diff --git a/src/api/data/plan/item/index.ts b/src/api/data/plan/item/index.ts
index aeefbc6..0273cbd 100644
--- a/src/api/data/plan/item/index.ts
+++ b/src/api/data/plan/item/index.ts
@@ -50,7 +50,7 @@
 
 //获取下拉集合
 export const getItemList = (params: PageParam) => {
-  return request.get({ url: '/data/plan-item/getList', params})
+  return request.get({ url: '/data/plan-item/list', params})
 }
 
 // 查询Plan图表
diff --git a/src/api/data/video/image/index.ts b/src/api/data/video/image/index.ts
new file mode 100644
index 0000000..d2d5f7a
--- /dev/null
+++ b/src/api/data/video/image/index.ts
@@ -0,0 +1,50 @@
+import request from '@/config/axios'
+
+export interface ImageVO {
+  id: undefined,
+  cameraId: string,
+  imagePath: string,
+  imageUrl: string,
+  createDate: undefined,
+}
+
+// 查询列表
+export const getImagePage = (params: PageParam) => {
+  return request.get({url: '/data/video/image/page', params})
+}
+
+// 获得
+export const getImage = (id: number) => {
+  return request.get({url: '/data/video/image/get?id=' + id})
+}
+
+// 查询应用列表
+export const getImageList = () => {
+  return request.get({url: '/data/video/image/list'})
+}
+
+// 新增
+export const createImage = (data: ImageVO) => {
+  return request.post({url: '/data/video/image/create', data})
+}
+
+// 修改
+export const updateImage = (data: ImageVO) => {
+  return request.put({url: '/data/video/image/update', data})
+}
+
+// 删除
+export const deleteImage = (id: number) => {
+  return request.delete({url: '/data/video/image/delete?id=' + id})
+}
+
+// 导出
+export const exportImage = (params: ImageVO) => {
+  return request.download({url: '/data/video/image/export-excel', params})
+}
+
+//预览摄像头截图
+export const getPreviewUrl = (url: string) => {
+  const host = import.meta.env.VITE_VIDEO_CAMERA_DOMAIN ? import.meta.env.VITE_VIDEO_CAMERA_DOMAIN : window.location.host
+  return `http://${host}:8899` + url
+}
diff --git a/src/api/model/mcs/index.ts b/src/api/model/mcs/index.ts
new file mode 100644
index 0000000..4f55207
--- /dev/null
+++ b/src/api/model/mcs/index.ts
@@ -0,0 +1,30 @@
+import request from '@/config/axios'
+
+export interface PreDataBarLineReqVO {
+  outIds: string[],
+  predictTime: string,
+  startTime: string,
+  endTime: string
+}
+
+export interface PreDataItemChartReqVO {
+  itemId: string,
+  startTime: string,
+  endTime: string
+}
+
+export const getPredictItemTree = () => {
+  return request.get({ url: '/model/api/mcs/predict-item/tree'})
+}
+
+export const getPreDataCharts = (data: PreDataBarLineReqVO) => {
+  return request.post({ url: '/model/api/mcs/predict-data/charts', data })
+}
+
+export const getPreDataItemChart = (data: PreDataItemChartReqVO) => {
+  return request.post({ url: '/model/api/mcs/predict-data/item-chart', data })
+}
+
+export const exportPredictValue = (params) => {
+  return request.download({ url: '/model/api/mcs/predict-data/exportValue', params })
+}
diff --git a/src/api/model/pre/dm/index.ts b/src/api/model/pre/dm/index.ts
index aa652b6..69e13c3 100644
--- a/src/api/model/pre/dm/index.ts
+++ b/src/api/model/pre/dm/index.ts
@@ -1,7 +1,7 @@
 import request from '@/config/axios'
 
 export interface DmModuleVO {
-  id: string
+  id: string,
   modulename: string,
   moduletype: string,
   cycle: string
diff --git a/src/api/model/pre/item/index.ts b/src/api/model/pre/item/index.ts
index 795ca65..e27d8c4 100644
--- a/src/api/model/pre/item/index.ts
+++ b/src/api/model/pre/item/index.ts
@@ -1,43 +1,140 @@
 import request from '@/config/axios'
+import {UploadRequestOptions} from "element-plus/es/components/upload/src/upload";
 
-export interface MmItemTypeVO {
-  id: string
+export interface MmPredictItemVO {
+  id: string,
   itemtypename: string,
-  itemclasstype: string,
-  assemblyname: string
+  mmPredictItem: {
+    id: string,
+    itemno: string,
+    itemname: string,
+    caltypeid: string,
+    itemtypeid: string,
+    predictlength: string,
+    granularity: number,
+    status: string,
+    isfuse: number,
+    predictphase: string,
+    workchecked: number,
+    unittransfactor: string,
+    saveindex: string
+  },
+  dmModuleItem: {
+    id: string,
+    moduleid: string,
+    itemid: string,
+    itemorder: number,
+    status: number,
+    categoryid: string
+  },
+  mmItemOutput: {
+    id: string,
+    itemid: string,
+    pointid: string,
+    resulttableid: string,
+    tagname: string,
+    outputorder: number
+  },
+  mmPredictModel: {
+    id: string,
+    modelno: string,
+    modelname: string,
+    itemid: string,
+    arithid: string,
+    trainsamplength: number,
+    predictsamplength: string,
+    isonlinetrain: string,
+    modelpath: string,
+    isnormal: string,
+    normalmax: string,
+    normalmin: string,
+    status: number,
+    classname: string,
+    methodname: string,
+    modelparamstructure: string,
+    resultstrid: string,
+    settingmap: string
+  },
+  mmPredictMergeItem: {
+    id: string,
+    itemid: string,
+    expression: string,
+    num: string
+  },
+  modelparamtypeList: [],
+  mmModelArithSettingsList: [],
+  mmModelParamList: []
 }
 
-export interface MmItemTypePageReqVO extends PageParam {
-  itemtypename?: string
+export interface MmPredictItemPageReqVO extends PageParam {
+  itemno?: string,
+  itemname?: string,
 }
 
-// 查询MmItemType列表
-export const getMmItemTypePage = (params: MmItemTypePageReqVO) => {
-  return request.get({ url: '/model/pre/item-type/page', params })
+// 查询MmPredictItem列表
+export const getMmPredictItemPage = (params: MmPredictItemPageReqVO) => {
+  return request.get({ url: '/model/pre/item/page', params })
 }
 
-// 查询MmItemType详情
-export const getMmItemType = (id: number) => {
-  return request.get({ url: `/model/pre/item-type/get/${id}`})
+// 查询MmPredictItem详情
+export const getMmPredictItem = (id: number) => {
+  return request.get({ url: `/model/pre/item/get/${id}`})
 }
 
-// 新增MmItemType
-export const createMmItemType = (data: MmItemTypeVO) => {
-  return request.post({ url: '/model/pre/item-type/create', data })
+// 新增MmPredictItem
+export const createMmPredictItem = (data: MmPredictItemVO) => {
+  return request.post({ url: '/model/pre/item/create', data })
 }
 
-// 修改MmItemType
-export const updateMmItemType = (data: MmItemTypeVO) => {
-  return request.put({ url: '/model/pre/item-type/update', data })
+// 修改MmPredictItem
+export const updateMmPredictItem = (data: MmPredictItemVO) => {
+  return request.put({ url: '/model/pre/item/update', data })
 }
 
-// 删除MmItemType
-export const deleteMmItemType = (id: number) => {
-  return request.delete({ url: '/model/pre/item-type/delete?id=' + id })
+// 删除MmPredictItem
+export const deleteMmPredictItem = (id: number) => {
+  return request.delete({ url: '/model/pre/item/delete?id=' + id })
 }
 
+// 查询MmPredictItem列表
+export const getMmPredictItemList = (params) => {
+  return request.get({ url: `/model/pre/item/list`, params})
+}
 
-// 查询getItemTypeList详情
-export const getItemTypeList = () => {
-  return request.get({ url: `/model/pre/item-type/list`})
+export const updateModel = (data: any) => {
+  return request.upload({ url: '/model/pre/item/upload-model', data })
+}
+
+export const useUpload = () => {
+  const uploadUrl = import.meta.env.VITE_BASE_URL + '/admin-api/model/pre/item/upload-model'
+
+  const httpRequest = async (options: UploadRequestOptions) => {
+    return new Promise((resolve, reject) => {
+      updateModel({ file: options.file })
+        .then((res) => {
+          if (res.code === 0) {
+            resolve(res)
+          } else {
+            reject(res)
+          }
+        })
+        .catch((res) => {
+          reject(res)
+        })
+    })
+
+  }
+
+  return {
+    uploadUrl,
+    httpRequest
+  }
+}
+
+export const getMmPredictItemTree = () => {
+  return request.get({ url: `/model/pre/item/tree`})
+}
+
+export const getViewCharts = (params) => {
+  return request.get({ url: `/model/pre/item/view-charts`,params})
 }
diff --git a/src/api/model/pre/predict/index.ts b/src/api/model/pre/predict/index.ts
deleted file mode 100644
index e27d8c4..0000000
--- a/src/api/model/pre/predict/index.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import request from '@/config/axios'
-import {UploadRequestOptions} from "element-plus/es/components/upload/src/upload";
-
-export interface MmPredictItemVO {
-  id: string,
-  itemtypename: string,
-  mmPredictItem: {
-    id: string,
-    itemno: string,
-    itemname: string,
-    caltypeid: string,
-    itemtypeid: string,
-    predictlength: string,
-    granularity: number,
-    status: string,
-    isfuse: number,
-    predictphase: string,
-    workchecked: number,
-    unittransfactor: string,
-    saveindex: string
-  },
-  dmModuleItem: {
-    id: string,
-    moduleid: string,
-    itemid: string,
-    itemorder: number,
-    status: number,
-    categoryid: string
-  },
-  mmItemOutput: {
-    id: string,
-    itemid: string,
-    pointid: string,
-    resulttableid: string,
-    tagname: string,
-    outputorder: number
-  },
-  mmPredictModel: {
-    id: string,
-    modelno: string,
-    modelname: string,
-    itemid: string,
-    arithid: string,
-    trainsamplength: number,
-    predictsamplength: string,
-    isonlinetrain: string,
-    modelpath: string,
-    isnormal: string,
-    normalmax: string,
-    normalmin: string,
-    status: number,
-    classname: string,
-    methodname: string,
-    modelparamstructure: string,
-    resultstrid: string,
-    settingmap: string
-  },
-  mmPredictMergeItem: {
-    id: string,
-    itemid: string,
-    expression: string,
-    num: string
-  },
-  modelparamtypeList: [],
-  mmModelArithSettingsList: [],
-  mmModelParamList: []
-}
-
-export interface MmPredictItemPageReqVO extends PageParam {
-  itemno?: string,
-  itemname?: string,
-}
-
-// 查询MmPredictItem列表
-export const getMmPredictItemPage = (params: MmPredictItemPageReqVO) => {
-  return request.get({ url: '/model/pre/item/page', params })
-}
-
-// 查询MmPredictItem详情
-export const getMmPredictItem = (id: number) => {
-  return request.get({ url: `/model/pre/item/get/${id}`})
-}
-
-// 新增MmPredictItem
-export const createMmPredictItem = (data: MmPredictItemVO) => {
-  return request.post({ url: '/model/pre/item/create', data })
-}
-
-// 修改MmPredictItem
-export const updateMmPredictItem = (data: MmPredictItemVO) => {
-  return request.put({ url: '/model/pre/item/update', data })
-}
-
-// 删除MmPredictItem
-export const deleteMmPredictItem = (id: number) => {
-  return request.delete({ url: '/model/pre/item/delete?id=' + id })
-}
-
-// 查询MmPredictItem列表
-export const getMmPredictItemList = (params) => {
-  return request.get({ url: `/model/pre/item/list`, params})
-}
-
-export const updateModel = (data: any) => {
-  return request.upload({ url: '/model/pre/item/upload-model', data })
-}
-
-export const useUpload = () => {
-  const uploadUrl = import.meta.env.VITE_BASE_URL + '/admin-api/model/pre/item/upload-model'
-
-  const httpRequest = async (options: UploadRequestOptions) => {
-    return new Promise((resolve, reject) => {
-      updateModel({ file: options.file })
-        .then((res) => {
-          if (res.code === 0) {
-            resolve(res)
-          } else {
-            reject(res)
-          }
-        })
-        .catch((res) => {
-          reject(res)
-        })
-    })
-
-  }
-
-  return {
-    uploadUrl,
-    httpRequest
-  }
-}
-
-export const getMmPredictItemTree = () => {
-  return request.get({ url: `/model/pre/item/tree`})
-}
-
-export const getViewCharts = (params) => {
-  return request.get({ url: `/model/pre/item/view-charts`,params})
-}
diff --git a/src/api/model/pre/result/index.ts b/src/api/model/pre/result/index.ts
deleted file mode 100644
index 5561e94..0000000
--- a/src/api/model/pre/result/index.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import request from '@/config/axios'
-
-export interface MmResultTableVO {
-  id: string
-  tablename: string,
-}
-
-export interface MmResultTablePageReqVO extends PageParam {
-  tablename?: string
-}
-
-// 查询MmResultTable列表
-export const getMmResultTablePage = (params: MmResultTablePageReqVO) => {
-  return request.get({ url: '/model/pre/result-table/page', params })
-}
-
-// 查询MmResultTable详情
-export const getMmResultTable = (id: number) => {
-  return request.get({ url: `/model/pre/result-table/get/${id}`})
-}
-
-// 新增MmResultTable
-export const createMmResultTable = (data: MmResultTableVO) => {
-  return request.post({ url: '/model/pre/result-table/create', data })
-}
-
-// 修改MmResultTable
-export const updateMmResultTable = (data: MmResultTableVO) => {
-  return request.put({ url: '/model/pre/result-table/update', data })
-}
-
-// 删除MmResultTable
-export const deleteMmResultTable = (id: number) => {
-  return request.delete({ url: '/model/pre/result-table/delete?id=' + id })
-}
-
-
-// 查询getResulttableList详情
-export const getResulttableList = () => {
-  return request.get({ url: `/model/pre/result-table/list`})
-}
-
-// 查询getResulttableList详情
-export const getResultstridList = () => {
-  return request.get({ url: `/model/pre/model-resultstr/list`})
-}
diff --git a/src/api/model/pre/type/index.ts b/src/api/model/pre/type/index.ts
new file mode 100644
index 0000000..795ca65
--- /dev/null
+++ b/src/api/model/pre/type/index.ts
@@ -0,0 +1,43 @@
+import request from '@/config/axios'
+
+export interface MmItemTypeVO {
+  id: string
+  itemtypename: string,
+  itemclasstype: string,
+  assemblyname: string
+}
+
+export interface MmItemTypePageReqVO extends PageParam {
+  itemtypename?: string
+}
+
+// 查询MmItemType列表
+export const getMmItemTypePage = (params: MmItemTypePageReqVO) => {
+  return request.get({ url: '/model/pre/item-type/page', params })
+}
+
+// 查询MmItemType详情
+export const getMmItemType = (id: number) => {
+  return request.get({ url: `/model/pre/item-type/get/${id}`})
+}
+
+// 新增MmItemType
+export const createMmItemType = (data: MmItemTypeVO) => {
+  return request.post({ url: '/model/pre/item-type/create', data })
+}
+
+// 修改MmItemType
+export const updateMmItemType = (data: MmItemTypeVO) => {
+  return request.put({ url: '/model/pre/item-type/update', data })
+}
+
+// 删除MmItemType
+export const deleteMmItemType = (id: number) => {
+  return request.delete({ url: '/model/pre/item-type/delete?id=' + id })
+}
+
+
+// 查询getItemTypeList详情
+export const getItemTypeList = () => {
+  return request.get({ url: `/model/pre/item-type/list`})
+}
diff --git a/src/api/model/sche/model/index.ts b/src/api/model/sche/model/index.ts
index b0bf106..8863d31 100644
--- a/src/api/model/sche/model/index.ts
+++ b/src/api/model/sche/model/index.ts
@@ -1,7 +1,9 @@
 import request from '@/config/axios'
 import * as DataPointApi from '@/api/data/da/point'
-import * as PredictItemApi from '@/api/model/pre/predict'
+import * as PredictItemApi from '@/api/model/pre/item'
+import * as PlanItemApi from '@/api/data/plan/item'
 import {CommonEnabled} from "@/utils/constants";
+import {getItemList, ItemVO} from "@/api/data/plan/item";
 
 export interface ScheduleModelVO {
   id: string
@@ -100,8 +102,25 @@
       )
     })
   }
+
+  const planItemList = ref([] as PlanItemApi.ItemVO)
+  planItemList.value = await PlanItemApi.getItemList({
+  })
+  const planList = []
+  if (planItemList.value) {
+    planItemList.value.forEach(item => {
+      planList.push(
+        {
+          id: item.id,
+          name:  item.itemName
+        }
+      )
+    })
+  }
+
   return {
     'DATAPOINT':pointList,
-    'PREDICTITEM': itemList
+    'PREDICTITEM': itemList,
+    'PLAN': planList,
   }
 }
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index dcf55c7..5de3d74 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -50,6 +50,15 @@
       noTagsView: true
     }
   },
+  // {
+  //   path: '/shasteel',
+  //   component: () => import('@/views/micro/index.vue'),
+  //   name: 'shasteel',
+  //   meta: {
+  //     hidden: true,
+  //     noTagsView: true
+  //   },
+  // },
   {
     path: '/home2',
     component: () => import('@/views/Home/Index2.vue'),
@@ -274,18 +283,6 @@
           hidden: true,
           canTo: true,
           title: '设计流程',
-          activeMenu: '/bpm/manager/model'
-        }
-      },
-      {
-        path: 'manager/simple/workflow/model/edit',
-        component: () => import('@/views/bpm/simpleWorkflow/index.vue'),
-        name: 'SimpleWorkflowDesignEditor',
-        meta: {
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          title: '仿钉钉设计流程',
           activeMenu: '/bpm/manager/model'
         }
       },
diff --git a/src/utils/dict.ts b/src/utils/dict.ts
index 86bd05a..9347dfc 100644
--- a/src/utils/dict.ts
+++ b/src/utils/dict.ts
@@ -162,6 +162,8 @@
   MODEL_TYPE = 'model_type',
   MODEL_METHOD_SETTING_TYPE = 'model_method_setting_type',
   MODEL_METHOD_SETTING_VALUE_TYPE = 'model_method_setting_value_type',
+  PRED_GRANULARITY = 'pred_granularity',
+  ITEM_RUN_STATUS = 'item_run_status',
 
   // ========== DATA - 数据平台模块  ==========
   DATA_FIELD_TYPE = 'data_field_type',
@@ -183,4 +185,5 @@
   NVR_ONLINE_STATUS = 'nvr_online_status',
   CAMERA_BRAND = 'camera_brand',
   CAPTURE_TYPE = 'capture_type',
+  MODEL_RESULT_TYPE = 'model_result_type',
 }
diff --git a/src/utils/hostMap.ts b/src/utils/hostMap.ts
index 81df37d..c2904af 100644
--- a/src/utils/hostMap.ts
+++ b/src/utils/hostMap.ts
@@ -1,6 +1,6 @@
 const map = {
   "//localhost:7200/": "//wujie-micro.github.io/demo-vue2/",
-  "//localhost:90/": "//localhost:90/",
+  "//localhost:9000/": "//localhost:9000/",
   "//localhost:8000/": "//wujie-micro.github.io/demo-main-vue/",
 };
 
diff --git a/src/utils/is.ts b/src/utils/is.ts
index eec86a9..39812b6 100644
--- a/src/utils/is.ts
+++ b/src/utils/is.ts
@@ -98,8 +98,9 @@
 export const isClient = !isServer
 
 export const isUrl = (path: string): boolean => {
+  // fix:修复hash路由无法跳转的问题
   const reg =
-    /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
+    /(((^https?:(?:\/\/)?)(?:[-:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%#\/.\w-_]*)?\??(?:[-\+=&%@.\w_]*)#?(?:[\w]*))?)$/
   return reg.test(path)
 }
 
diff --git a/src/views/Home/Index.vue b/src/views/Home/Index.vue
index aa64a85..822f800 100644
--- a/src/views/Home/Index.vue
+++ b/src/views/Home/Index.vue
@@ -5,9 +5,9 @@
   <el-skeleton :loading="loading" animated>
     <div id="app" v-for="(item, index) in appList" :key="`dynamics-${index}`">
       <div class="card" @click="gotoApp(item)">
-        <img :src="item.icon" style="width: 100px; height: 100px" />
+        <img :src="item.icon" style="width: 100px; height: 100px"/>
         <div>
-          {{item.appName}}
+          {{ item.appName }}
         </div>
       </div>
     </div>
@@ -19,12 +19,11 @@
 import * as AppApi from '@/api/system/app'
 import {Apps} from "@/views/Home/types";
 import {CACHE_KEY, useCache} from "@/hooks/web/useCache";
-import * as authUtil from "@/utils/auth";
 
 
-defineOptions({ name: 'Home' })
+defineOptions({name: 'Home'})
 
-const { wsCache } = useCache()
+const {wsCache} = useCache()
 
 const loading = ref(true)
 
@@ -35,7 +34,7 @@
   appList = Object.assign(appList, data)
 }
 
-const getAppMenuList = async (id) => {
+const getAppMenuList = async (id, appCode) => {
   const data = await AppApi.getAppMenuList(id)
   let userInfo = wsCache.get(CACHE_KEY.USER)
   userInfo.menus = data
@@ -55,32 +54,38 @@
 
 // 进入应用
 const gotoApp = async (item) => {
+  let path = window.location.pathname
+  let appName = path.split("/")[0]
+  console.log(appName)
   let id = item.id
   let type = item.type
-  if(type === 0) {
-    getAppMenuList(id)
+  let appCode = item.appCode
+  if (type === 0) {
+    await getAppMenuList(id, appCode)
   } else {
     const data = await AppApi.getAppMenuList(id)
     let userInfo = wsCache.get(CACHE_KEY.USER)
     userInfo.menus = data
     wsCache.set(CACHE_KEY.USER, userInfo)
     wsCache.set(CACHE_KEY.ROLE_ROUTERS, data)
-    // await OAuth2Login(formData.value)
-    // window.open(item.appDomain + '/login?appid=' + item.id + "&username=" + authUtil.getLoginForm().username, '_blank')
     window.open(item.appDomain + '/index', '_blank')
+    // window.open('/plat/shasteel', '_blank')
+    // window.location.href = '/plat/shasteel'
+    // window.location.href = `/plat/shasteel?key=energy&url=http://localhost:9000&energy=/energy/demo`
   }
 }
 
 </script>
 
 <style lang="scss" scoped>
-#app{
+#app {
   width: 300px;
   height: 200px;
   display: inline-block;
   background: transparent;
 }
-.card{
+
+.card {
   border: thin dashed gainsboro;
   width: 150px;
   height: 120px;
diff --git a/src/views/Login/components/LoginForm.vue b/src/views/Login/components/LoginForm.vue
index 22a80b2..bc3a32a 100644
--- a/src/views/Login/components/LoginForm.vue
+++ b/src/views/Login/components/LoginForm.vue
@@ -12,7 +12,7 @@
     <el-row style="margin-right: -10px; margin-left: -10px">
       <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
         <el-form-item>
-          <LoginFormTitle style="width: 100%" />
+          <LoginFormTitle style="width: 100%"/>
         </el-form-item>
       </el-col>
       <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
@@ -86,27 +86,27 @@
   </el-form>
 </template>
 <script lang="ts" setup>
-import { ElLoading } from 'element-plus'
+import {ElLoading} from 'element-plus'
 import LoginFormTitle from './LoginFormTitle.vue'
-import type { RouteLocationNormalizedLoaded } from 'vue-router'
+import type {RouteLocationNormalizedLoaded} from 'vue-router'
 
-import { useIcon } from '@/hooks/web/useIcon'
+import {useIcon} from '@/hooks/web/useIcon'
 
 import * as authUtil from '@/utils/auth'
-import { usePermissionStore } from '@/store/modules/permission'
+import {usePermissionStore} from '@/store/modules/permission'
 import * as LoginApi from '@/api/login'
-import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
+import {LoginStateEnum, useFormValid, useLoginState} from './useLogin'
 
-defineOptions({ name: 'LoginForm' })
+defineOptions({name: 'LoginForm'})
 
-const { t } = useI18n()
-const iconHouse = useIcon({ icon: 'ep:house' })
-const iconAvatar = useIcon({ icon: 'ep:avatar' })
-const iconLock = useIcon({ icon: 'ep:lock' })
+const {t} = useI18n()
+const iconHouse = useIcon({icon: 'ep:house'})
+const iconAvatar = useIcon({icon: 'ep:avatar'})
+const iconLock = useIcon({icon: 'ep:lock'})
 const formLogin = ref()
-const { validForm } = useFormValid(formLogin)
-const { getLoginState } = useLoginState()
-const { currentRoute, push } = useRouter()
+const {validForm} = useFormValid(formLogin)
+const {getLoginState} = useLoginState()
+const {currentRoute, push} = useRouter()
 const permissionStore = usePermissionStore()
 const redirect = ref<string>('')
 const loginLoading = ref(false)
@@ -199,14 +199,14 @@
       authUtil.removeLoginForm()
     }
     authUtil.setToken(res)
-    if (!redirect.value) {
+    if (!redirect.value || redirect.value == "/") {
       redirect.value = '/index'
     }
     // 判断是否为SSO登录
     if (redirect.value.indexOf('sso') !== -1) {
       window.location.href = window.location.href.replace('/login?redirect=', '')
     } else {
-      push({ path: redirect.value || permissionStore.addRouters[0].path })
+      push({path: redirect.value || permissionStore.addRouters[0].path})
     }
   } finally {
     loginLoading.value = false
diff --git a/src/views/bpm/model/index.vue b/src/views/bpm/model/index.vue
index f8b0b75..a20ea4e 100644
--- a/src/views/bpm/model/index.vue
+++ b/src/views/bpm/model/index.vue
@@ -163,14 +163,6 @@
           <el-button
             link
             type="primary"
-            @click="handleSimpleDesign(scope.row.id)"
-            v-hasPermi="['bpm:model:update']"
-          >
-            仿钉钉设计流程
-          </el-button>
-          <el-button
-            link
-            type="primary"
             @click="handleDeploy(scope.row)"
             v-hasPermi="['bpm:model:deploy']"
           >
@@ -328,15 +320,6 @@
 const handleDesign = (row) => {
   push({
     name: 'BpmModelEditor',
-    query: {
-      modelId: row.id
-    }
-  })
-}
-
-const handleSimpleDesign = (row) => {
-  push({
-    name: 'SimpleWorkflowDesignEditor',
     query: {
       modelId: row.id
     }
diff --git a/src/views/bpm/simpleWorkflow/index.vue b/src/views/bpm/simpleWorkflow/index.vue
deleted file mode 100644
index 144615e..0000000
--- a/src/views/bpm/simpleWorkflow/index.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-<template>
-  <div>
-    <section class="dingflow-design">
-      <div class="box-scale">
-        <nodeWrap v-model:nodeConfig="nodeConfig" />
-        <div class="end-node">
-          <div class="end-node-circle"></div>
-          <div class="end-node-text">流程结束</div>
-        </div>
-      </div>
-    </section>
-  </div>
-</template>
-<script lang="ts" setup>
-import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
-defineOptions({ name: 'SimpleWorkflowDesignEditor' })
-let nodeConfig = ref({
-  nodeName: '发起人',
-  type: 0,
-  id: 'root',
-  formPerms: {},
-  nodeUserList: [],
-  childNode: {}
-})
-</script>
-<style>
-@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
-</style>
\ No newline at end of file
diff --git a/src/views/data/channel/http/api/tag/index.vue b/src/views/data/channel/http/api/tag/index.vue
index 5f16d44..9a38a69 100644
--- a/src/views/data/channel/http/api/tag/index.vue
+++ b/src/views/data/channel/http/api/tag/index.vue
@@ -1,7 +1,7 @@
 <template>
   <el-drawer
     v-model="drawer"
-    size="50%"
+    size="60%"
     title="Http Tag"
     :direction="direction"
     :before-close="handleClose"
@@ -107,20 +107,22 @@
           label="数据值"
           header-align="center"
           align="center"
+          min-width="100"
           :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
         />
         <el-table-column
-          prop="quality"
+          prop="dataTime"
+          label="数据时间"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column
+          prop="dataQuality"
           label="数据质量"
           header-align="center"
           align="center"
-        >
-          <template #default="scope">
-            <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad
-            </el-tag>
-            <el-tag v-else size="small">good</el-tag>
-          </template>
-        </el-table-column>
+        />
         <el-table-column label="操作" align="center" min-width="110" fixed="right">
           <template #default="scope">
             <el-button
diff --git a/src/views/data/channel/kio/tag/index.vue b/src/views/data/channel/kio/tag/index.vue
index d334fb4..735b1d8 100644
--- a/src/views/data/channel/kio/tag/index.vue
+++ b/src/views/data/channel/kio/tag/index.vue
@@ -1,7 +1,7 @@
 <template>
   <el-drawer
     v-model="drawer"
-    size="50%"
+    size="60%"
     title="Kio Tag"
     :direction="direction"
     :before-close="handleClose"
@@ -105,19 +105,22 @@
           label="数据值"
           header-align="center"
           align="center"
+          min-width="100"
           :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
         />
         <el-table-column
-          prop="quality"
+          prop="dataTime"
+          label="数据时间"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column
+          prop="dataQuality"
           label="数据质量"
           header-align="center"
           align="center"
-        >
-          <template #default="scope">
-            <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag>
-            <el-tag v-else size="small">good</el-tag>
-          </template>
-        </el-table-column>
+        />
         <el-table-column label="操作" align="center" min-width="110" fixed="right">
           <template #default="scope">
             <el-button
diff --git a/src/views/data/channel/modbus/tag/index.vue b/src/views/data/channel/modbus/tag/index.vue
index 7739240..528d368 100644
--- a/src/views/data/channel/modbus/tag/index.vue
+++ b/src/views/data/channel/modbus/tag/index.vue
@@ -1,7 +1,7 @@
 <template>
   <el-drawer
     v-model="drawer"
-    size="50%"
+    size="60%"
     title="ModBus Tag"
     :direction="direction"
     :before-close="handleClose"
@@ -133,19 +133,22 @@
           label="数据值"
           header-align="center"
           align="center"
+          min-width="100"
           :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
         />
         <el-table-column
-          prop="quality"
+          prop="dataTime"
+          label="数据时间"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column
+          prop="dataQuality"
           label="数据质量"
           header-align="center"
           align="center"
-        >
-          <template #default="scope">
-            <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag>
-            <el-tag v-else size="small">good</el-tag>
-          </template>
-        </el-table-column>
+        />
         <el-table-column label="操作" align="center" min-width="110" fixed="right">
           <template #default="scope">
             <el-button
diff --git a/src/views/data/channel/opcda/tag/index.vue b/src/views/data/channel/opcda/tag/index.vue
index 53b3a4a..fc11b57 100644
--- a/src/views/data/channel/opcda/tag/index.vue
+++ b/src/views/data/channel/opcda/tag/index.vue
@@ -1,7 +1,7 @@
 <template>
   <el-drawer
     v-model="drawer"
-    size="50%"
+    size="60%"
     title="OpcDA Tag"
     :direction="direction"
     :before-close="handleClose"
@@ -98,19 +98,22 @@
           label="数据值"
           header-align="center"
           align="center"
+          min-width="100"
           :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
         />
         <el-table-column
-          prop="quality"
+          prop="dataTime"
+          label="数据时间"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column
+          prop="dataQuality"
           label="数据质量"
           header-align="center"
           align="center"
-        >
-          <template #default="scope">
-            <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag>
-            <el-tag v-else size="small">good</el-tag>
-          </template>
-        </el-table-column>
+        />
         <el-table-column label="操作" align="center" min-width="110" fixed="right">
           <template #default="scope">
             <el-button
diff --git a/src/views/data/channel/opcua/tag/index.vue b/src/views/data/channel/opcua/tag/index.vue
index 524f4b5..2c8d799 100644
--- a/src/views/data/channel/opcua/tag/index.vue
+++ b/src/views/data/channel/opcua/tag/index.vue
@@ -1,7 +1,7 @@
 <template>
   <el-drawer
     v-model="drawer"
-    size="50%"
+    size="60%"
     title="Opcua Tag"
     :direction="direction"
     :before-close="handleClose"
@@ -67,6 +67,12 @@
             <Icon icon="ep:download" />导出
           </el-button>
         </el-form-item>
+        <el-form-item label="更新当前值" label-width="100px">
+          <el-switch
+            v-model="queryParams.currentValue"
+            active-color="#13ce66"
+            inactive-color="#ff4949"/>
+        </el-form-item>
       </el-form>
     </ContentWrap>
     <!-- 列表 -->
@@ -113,19 +119,22 @@
           label="数据值"
           header-align="center"
           align="center"
+          min-width="100"
           :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
         />
         <el-table-column
-          prop="quality"
+          prop="dataTime"
+          label="数据时间"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column
+          prop="dataQuality"
           label="数据质量"
           header-align="center"
           align="center"
-        >
-          <template #default="scope">
-            <el-tag v-if="scope.row.dataValue === Number(-2.0)" type="danger" size="small">bad</el-tag>
-            <el-tag v-else size="small">good</el-tag>
-          </template>
-        </el-table-column>
+        />
         <el-table-column label="操作" align="center" min-width="110" fixed="right">
           <template #default="scope">
             <el-button
diff --git a/src/views/data/point/DaPointChart.vue b/src/views/data/point/DaPointChart.vue
index 19a6b23..8713861 100644
--- a/src/views/data/point/DaPointChart.vue
+++ b/src/views/data/point/DaPointChart.vue
@@ -41,7 +41,8 @@
           :loading="exportLoading"
           v-hasPermi="['data:point:export']"
         >
-          <Icon icon="ep:download" />导出
+          <Icon icon="ep:download"/>
+          导出
         </el-button>
       </el-form-item>
     </el-form>
@@ -50,39 +51,41 @@
 </template>
 
 <script lang="ts" setup>
-  import {ref} from 'vue';
-  import * as echarts from 'echarts';
-  import * as DaPoint from '@/api/data/da/point/daPointChart'
-  import {getYMDHM0} from "@/utils/dateUtil"
-  import download from "@/utils/download";
-  const message = useMessage() // 消息弹窗
-  const visible = ref(false);
-  const chartDom = ref(null);
-  let myChart = null;
-  const chartParams = reactive({
-    codes:[],
-    startDate : undefined,
-    endDate: undefined,
-  })
-  const dataForm = ref({
-    id: "",
-    pointNo: "",
-    pointName: "",
-    pointTypeName: "",
-    startTime: getYMDHM0(new Date() - 60 * 60 * 1000),
-    endTime: "",
-  });
-  const loading = ref(true) // 列表的加载中
-  /** 打开弹窗 */
-  const open = async (row: object) => {
-    visible.value = true
-    dataForm.value.id = row.id;
-    dataForm.value.pointNo = row.pointNo;
-    dataForm.value.pointName = row.pointName;
-    getDataList();
-  }
+import {ref} from 'vue';
+import * as echarts from 'echarts';
+import * as DaPoint from '@/api/data/da/point/daPointChart'
+import {getYMDHM0} from "@/utils/dateUtil"
+import download from "@/utils/download";
 
-  defineExpose({open}) // 提供 open 方法,用于打开弹窗
+const message = useMessage() // 消息弹窗
+const visible = ref(false);
+const chartDom = ref(null);
+let myChart = null;
+const chartParams = reactive({
+  codes: [],
+  pointNo: undefined,
+  startDate: undefined,
+  endDate: undefined,
+})
+const dataForm = ref({
+  id: "",
+  pointNo: "",
+  pointName: "",
+  pointTypeName: "",
+  startTime: getYMDHM0(new Date() - 60 * 60 * 1000),
+  endTime: "",
+});
+
+/** 打开弹窗 */
+const open = async (row: object) => {
+  visible.value = true
+  dataForm.value.id = row.id;
+  dataForm.value.pointNo = row.pointNo;
+  dataForm.value.pointName = row.pointName;
+  getDataList();
+}
+
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
 
   async function getDataList() {
     visible.value = true;
@@ -111,84 +114,86 @@
           });
         })
 
-        myChart = echarts.init(chartDom.value);
-        const option = {
-          title: {
-            text: dataForm.value.pointName,
-            top: 0,
-            left: "1%",
-            textStyle: {
-              fontSize: 14,
+      myChart = echarts.init(chartDom.value);
+      const option = {
+        title: {
+          text: dataForm.value.pointName,
+          top: 0,
+          left: "1%",
+          textStyle: {
+            fontSize: 14,
+          },
+        },
+        tooltip: {
+          trigger: "axis",
+          axisPointer: {
+            type: "line",
+            lineStyle: {
+              color: "#cccccc",
+              width: "1",
+              type: "dashed",
             },
           },
-          tooltip: {
-            trigger: "axis",
-            axisPointer: {
-              type: "line",
-              lineStyle: {
-                color: "#cccccc",
-                width: "1",
-                type: "dashed",
-              },
-            },
+        },
+        legend: {
+          show: false,
+          top: 10,
+        },
+        grid: {
+          top: 30,
+          left: "3%",
+          right: "5%",
+          bottom: 10,
+          containLabel: true,
+        },
+        xAxis: {
+          type: "category",
+          boundaryGap: false,
+          data: data.categories,
+        },
+        yAxis: {
+          type: "value",
+        },
+        dataZoom: [
+          {
+            type: "inside",
           },
-          legend: {
-            show: false,
-            top: 10,
-          },
-          grid: {
-            top: 30,
-            left: "3%",
-            right: "5%",
-            bottom: 10,
-            containLabel: true,
-          },
-          xAxis: {
-            type: "category",
-            boundaryGap: false,
-            data: data.categories,
-          },
-          yAxis: {
-            type: "value",
-          },
-          dataZoom: [
-            {
-              type: "inside",
-            },
-          ],
-          series: seriesData,
-        };
-        myChart.setOption(option);
-      } catch (error) {
-        console.error(error)
-      }
+        ],
+        series: seriesData,
+      };
+      myChart.setOption(option);
+    } catch (error) {
+      console.error(error)
     }
   }
-  /** 导出按钮操作 */
-  const exportLoading = ref(false)
-  const handleExport = async () => {
-    chartParams.codes=[dataForm.value.pointNo];
-    chartParams.startDate = dataForm.value.startTime;
-    chartParams.endDate = dataForm.value.endTime;
-    try {
-      // 导出的二次确认
-      await message.exportConfirm()
-      // 发起导出
-      exportLoading.value = true
-      const data = await DaPoint.exportDaPointValue(chartParams)
-      download.excel(data, dataForm.value.pointName +'.xls')
-    } catch {
-    } finally {
-      exportLoading.value = false
-    }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await DaPoint.exportDaPointValue({
+      pointNo: dataForm.value.pointNo,
+      start: dataForm.value.startTime,
+      end: dataForm.value.endTime
+    })
+    download.excel(data, dataForm.value.pointName + '.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
   }
+}
 </script>
 <style>
-  .el-select {
-    width: 100%;
-  }
+.el-select {
+  width: 100%;
+}
 
-  .result-chart {
-    height: 500px;
-  }
+.result-chart {
+  height: 500px;
+}
 </style>
diff --git a/src/views/data/video/camera/CameraImage.vue b/src/views/data/video/camera/CameraImage.vue
new file mode 100644
index 0000000..335983b
--- /dev/null
+++ b/src/views/data/video/camera/CameraImage.vue
@@ -0,0 +1,114 @@
+<template>
+  <el-dialog v-model="dialogVisible" :title="dialogTitle" :close="handleClose"
+             style="width: 50%; margin-top: 20px; overflow: auto; z-index: 1">
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column
+        label="截图时间"
+        align="center"
+        prop="createDate"
+        :formatter="dateFormatter"
+        width="200"
+      />
+      <el-table-column label="图片路径" align="center" prop="imagePath" width="500"
+                       :show-overflow-tooltip="true"/>
+      <el-table-column label="图片预览" align="center" prop="imageUrl">
+        <template #default="scope">
+          <el-image style="height: 50px; z-index: 1"
+                    :src="getPreviewUrl(scope.row.imageUrl)"
+                    :preview-src-list="getPreviewSrcList(scope.row.imageUrl)" fit="cover"
+                    preview-teleported/>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" min-width="60" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="danger"
+            style="z-index: 1"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['video:camera:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="open"
+    />
+  </el-dialog>
+</template>
+<script lang="ts" setup>
+import * as ImageApi from '@/api/data/video/image'
+import {getPreviewUrl} from "@/api/data/video/image";
+import {dateFormatter} from "@/utils/formatTime";
+
+defineOptions({name: 'CameraImage'})
+
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+const dialogTitle = ref('截图列表')
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  id: undefined,
+  brand: undefined,
+  code: undefined,
+  device: undefined,
+  cameraId: '',
+  imagePath: undefined,
+  imageUrl: undefined,
+  createDate: undefined,
+})
+const cameraId = ref('')
+const dialogVisible = ref(false)
+
+/** 查询列表 */
+const open = async (camera_id: string) => {
+  dialogVisible.value = true
+  cameraId.value = camera_id
+  queryParams.cameraId = camera_id
+  loading.value = true
+  try {
+    const data = await ImageApi.getImagePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+const handleClose = async () => {
+  dialogVisible.value = false
+}
+
+const getPreviewSrcList = (imageUrl: string) => {
+  let previewSrcList: string[] = []
+  previewSrcList.push(getPreviewUrl(imageUrl))
+  return previewSrcList
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ImageApi.deleteImage(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await open(cameraId.value)
+  } catch {
+  }
+}
+</script>
diff --git a/src/views/data/video/camera/index.vue b/src/views/data/video/camera/index.vue
index d0fbbe6..add49c5 100644
--- a/src/views/data/video/camera/index.vue
+++ b/src/views/data/video/camera/index.vue
@@ -61,11 +61,11 @@
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
-          <Icon icon="ep:search" class="mr-5px" />
+          <Icon icon="ep:search" class="mr-5px"/>
           搜索
         </el-button>
         <el-button @click="resetQuery">
-          <Icon icon="ep:refresh" class="mr-5px" />
+          <Icon icon="ep:refresh" class="mr-5px"/>
           重置
         </el-button>
         <el-button
@@ -74,7 +74,7 @@
           @click="openForm('create')"
           v-hasPermi="['video:camera:save']"
         >
-          <Icon icon="ep:plus" class="mr-5px" />
+          <Icon icon="ep:plus" class="mr-5px"/>
           新增
         </el-button>
         <el-button
@@ -84,7 +84,7 @@
           :loading="exportLoading"
           v-hasPermi="['video:camera:export']"
         >
-          <Icon icon="ep:download" class="mr-5px" />
+          <Icon icon="ep:download" class="mr-5px"/>
           导出
         </el-button>
       </el-form-item>
@@ -96,20 +96,21 @@
     <el-table v-loading="loading" :data="list">
       <el-table-column label="品牌" align="center" prop="brand" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand" />
+          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand"/>
         </template>
       </el-table-column>
       <el-table-column label="设备类型" align="center" prop="device" width="200"/>
       <el-table-column label="编码" align="center" prop="code" width="200"/>
-      <el-table-column label="IP" align="center" prop="ip" />
-      <el-table-column label="端口" align="center" prop="port" width="100"/>
+      <el-table-column label="IP" align="center" prop="ip"/>
+      <el-table-column label="端口" align="center" prop="port" width="80"/>
+      <el-table-column label="通道" align="center" prop="channel" width="80"/>
       <el-table-column label="用户名" align="center" prop="username" width="100"/>
       <el-table-column label="状态" prop="status" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status" />
+          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status"/>
         </template>
       </el-table-column>
-      <el-table-column label="位置" align="center" prop="location" />
+      <el-table-column label="位置" align="center" prop="location"/>
       <el-table-column label="备注" align="center" prop="remark" width="150"/>
       <el-table-column label="操作" align="center" min-width="110" fixed="right">
         <template #default="scope">
@@ -129,6 +130,14 @@
           >
             删除
           </el-button>
+          <el-button
+            link
+            type="success"
+            @click="imageHandle(scope.row.id)"
+            v-hasPermi="['video:image:query']"
+          >
+            查看截图
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -142,99 +151,109 @@
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
-  <CameraForm ref="formRef" @success="getList" />
+  <CameraForm ref="formRef" @success="getList"/>
 
+  <CameraImage ref="imageFormRef"/>
 
 </template>
 <script lang="ts" setup>
 import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
-  import download from '@/utils/download'
-  import * as CameraApi from '@/api/data/video/camera'
-  import CameraForm from './CameraForm.vue'
+import download from '@/utils/download'
+import * as CameraApi from '@/api/data/video/camera'
+import CameraForm from './CameraForm.vue'
+import CameraImage from './CameraImage.vue'
 
-  defineOptions({name: 'Camera'})
 
-  const message = useMessage() // 消息弹窗
-  const {t} = useI18n() // 国际化
+defineOptions({name: 'Camera'})
 
-  const loading = ref(true) // 列表的加载中
-  const total = ref(0) // 列表的总页数
-  const list = ref([]) // 列表的数据
-  const queryParams = reactive({
-    pageNo: 1,
-    pageSize: 10,
-    type: 1,
-    brand: undefined,
-    ip: undefined,
-    code: undefined,
-    device: undefined,
-    location: undefined,
-    status: undefined
-  })
-  const queryFormRef = ref() // 搜索的表单
-  const exportLoading = ref(false) // 导出的加载中
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
 
-  /** 查询列表 */
-  const getList = async () => {
-    loading.value = true
-    try {
-      const data = await CameraApi.getCameraPage(queryParams)
-      list.value = data.list
-      total.value = data.total
-    } finally {
-      loading.value = false
-    }
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  type: 1,
+  brand: undefined,
+  ip: undefined,
+  code: undefined,
+  device: undefined,
+  location: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await CameraApi.getCameraPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
+}
 
-  /** 搜索按钮操作 */
-  const handleQuery = () => {
-    queryParams.pageNo = 1
-    getList()
-  }
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
 
-  /** 重置按钮操作 */
-  const resetQuery = () => {
-    queryFormRef.value.resetFields()
-    handleQuery()
-  }
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.brand = undefined
+  handleQuery()
+}
 
-  /** 添加/修改操作 */
-  const formRef = ref()
-  const openForm = (type: string, id?: number) => {
-    formRef.value.open(type, id)
-  }
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
 
-  /** 删除按钮操作 */
-  const handleDelete = async (id: number) => {
-    try {
-      // 删除的二次确认
-      await message.delConfirm()
-      // 发起删除
-      await CameraApi.deleteCamera(id)
-      message.success(t('common.delSuccess'))
-      // 刷新列表
-      await getList()
-    } catch {
-    }
-  }
-
-  /** 导出按钮操作 */
-  const handleExport = async () => {
-    try {
-      // 导出的二次确认
-      await message.exportConfirm()
-      // 发起导出
-      exportLoading.value = true
-      const data = await CameraApi.exportCamera(queryParams)
-      download.excel(data, '录像机列表.xls')
-    } catch {
-    } finally {
-      exportLoading.value = false
-    }
-  }
-
-  /** 初始化 **/
-  onMounted(async () => {
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await CameraApi.deleteCamera(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
     await getList()
-  })
+  } catch {
+  }
+}
+
+/** 查看截图 */
+const imageFormRef = ref()
+const imageHandle = (id: string) => {
+  imageFormRef.value.open(id)
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await CameraApi.exportCamera(queryParams)
+    download.excel(data, '录像机列表.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
 </script>
diff --git a/src/views/data/video/nvr/NvrCamera.vue b/src/views/data/video/nvr/NvrCamera.vue
index 0ff49b3..212ff57 100644
--- a/src/views/data/video/nvr/NvrCamera.vue
+++ b/src/views/data/video/nvr/NvrCamera.vue
@@ -6,95 +6,104 @@
     @close="handleClose"
     size="60%">
     <div class="mod-dev__camera" style="padding: 10px;">
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label="监控区域" prop="code">
-        <el-input
-          v-model="queryParams.location"
-          placeholder="请输入监控区域"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery">
-          <Icon icon="ep:search" class="mr-5px" />
-          搜索
-        </el-button>
-        <el-button @click="resetQuery">
-          <Icon icon="ep:refresh" class="mr-5px" />
-          重置
-        </el-button>
-        <el-button
-          type="primary"
-          plain
-          @click="openForm('create')"
-        >
-          <Icon icon="ep:plus" class="mr-5px" />
-          新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['dev:camera:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" />
-          导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-
-  <!-- 列表 -->
-    <el-table v-loading="loading" :data="list">
-      <el-table-column label="编码" align="center" prop="code" width="80" />
-      <el-table-column label="抓图方式" align="center" prop="captureType" width="80">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.CAPTURE_TYPE" :value="scope.row.captureType" />
-        </template>
-      </el-table-column>
-      <el-table-column label="通道" align="center" prop="channel" width="80"  />
-      <el-table-column label="监控区域" align="center" prop="location" />
-      <el-table-column label="备注" align="center" prop="remark" width="200" />
-      <el-table-column label="操作" align="center" min-width="110" fixed="right">
-        <template #default="scope">
+      <el-form
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+      >
+        <el-form-item label="监控区域" prop="code">
+          <el-input
+            v-model="queryParams.location"
+            placeholder="请输入监控区域"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button @click="handleQuery">
+            <Icon icon="ep:search" class="mr-5px"/>
+            搜索
+          </el-button>
+          <el-button @click="resetQuery">
+            <Icon icon="ep:refresh" class="mr-5px"/>
+            重置
+          </el-button>
           <el-button
-            link
             type="primary"
-            @click="openForm('update', scope.row.id)"
+            plain
+            @click="openForm('create')"
           >
-            编辑
+            <Icon icon="ep:plus" class="mr-5px"/>
+            新增
           </el-button>
           <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['dev:camera:export']"
           >
-            删除
+            <Icon icon="ep:download" class="mr-5px"/>
+            导出
           </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
+        </el-form-item>
+      </el-form>
+
+      <!-- 列表 -->
+      <el-table v-loading="loading" :data="list">
+        <el-table-column label="编码" align="center" prop="code" width="80"/>
+        <el-table-column label="抓图方式" align="center" prop="captureType" width="80">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.CAPTURE_TYPE" :value="scope.row.captureType"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="通道" align="center" prop="channel" width="80"/>
+        <el-table-column label="监控区域" align="center" prop="location"/>
+        <el-table-column label="备注" align="center" prop="remark" width="200"/>
+        <el-table-column label="操作" align="center" min-width="110" fixed="right">
+          <template #default="scope">
+            <el-button
+              link
+              type="primary"
+              @click="openForm('update', scope.row.id)"
+            >
+              编辑
+            </el-button>
+            <el-button
+              link
+              type="danger"
+              @click="handleDelete(scope.row.id)"
+            >
+              删除
+            </el-button>
+            <el-button
+              link
+              type="success"
+              @click="imageHandle(scope.row.id)"
+              v-hasPermi="['video:image:query']"
+            >
+              查看截图
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
     </div>
   </el-drawer>
 
   <!-- 表单弹窗:添加/修改 -->
-  <NvrCameraForm ref="formRef" @success="getList" />
+  <NvrCameraForm ref="formRef" @success="getList"/>
 
+  <CameraImage ref="imageFormRef"/>
 
 </template>
 <script lang="ts" setup>
@@ -102,6 +111,7 @@
 import * as CameraApi from '@/api/data/video/camera'
 import NvrCameraForm from './NvrCameraForm.vue'
 import {DICT_TYPE} from "@/utils/dict";
+import CameraImage from "../camera/CameraImage.vue";
 
 defineOptions({name: 'NvrCamera'})
 
@@ -134,7 +144,7 @@
   await getList()
 }
 
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
 
 /** 查询列表 */
 const getList = async () => {
@@ -148,6 +158,12 @@
   }
 }
 
+/** 查看截图 */
+const imageFormRef = ref()
+const imageHandle = (id: string) => {
+  imageFormRef.value.open(id)
+}
+
 /** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
@@ -156,7 +172,7 @@
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryParams.location = undefined
   handleQuery()
 }
 
diff --git a/src/views/data/video/nvr/index.vue b/src/views/data/video/nvr/index.vue
index 61d0163..b780a0e 100644
--- a/src/views/data/video/nvr/index.vue
+++ b/src/views/data/video/nvr/index.vue
@@ -52,11 +52,11 @@
       </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery">
-          <Icon icon="ep:search" class="mr-5px" />
+          <Icon icon="ep:search" class="mr-5px"/>
           搜索
         </el-button>
         <el-button @click="resetQuery">
-          <Icon icon="ep:refresh" class="mr-5px" />
+          <Icon icon="ep:refresh" class="mr-5px"/>
           重置
         </el-button>
         <el-button
@@ -65,7 +65,7 @@
           @click="openForm('create')"
           v-hasPermi="['video:nvr:save']"
         >
-          <Icon icon="ep:plus" class="mr-5px" />
+          <Icon icon="ep:plus" class="mr-5px"/>
           新增
         </el-button>
         <el-button
@@ -75,7 +75,7 @@
           :loading="exportLoading"
           v-hasPermi="['video:nvr:export']"
         >
-          <Icon icon="ep:download" class="mr-5px" />
+          <Icon icon="ep:download" class="mr-5px"/>
           导出
         </el-button>
       </el-form-item>
@@ -87,20 +87,19 @@
     <el-table v-loading="loading" :data="list">
       <el-table-column label="品牌" align="center" prop="brand" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand" />
+          <dict-tag :type="DICT_TYPE.CAMERA_BRAND" :value="scope.row.brand"/>
         </template>
       </el-table-column>
       <el-table-column label="编码" align="center" prop="code" width="100"/>
       <el-table-column label="名称" align="center" prop="name"/>
-      <el-table-column label="IP" align="center" prop="ip" />
+      <el-table-column label="IP" align="center" prop="ip"/>
       <el-table-column label="端口" align="center" prop="port" width="100"/>
       <el-table-column label="用户名" align="center" prop="username" width="100"/>
       <el-table-column label="状态" prop="status" width="80">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status" />
+          <dict-tag :type="DICT_TYPE.NVR_ONLINE_STATUS" :value="scope.row.status"/>
         </template>
       </el-table-column>
-      <el-table-column label="位置" align="center" prop="position" />
       <el-table-column label="备注" align="center" prop="remark" width="150"/>
       <el-table-column label="操作" align="center" min-width="110" fixed="right">
         <template #default="scope">
@@ -120,7 +119,8 @@
           >
             删除
           </el-button>
-          <el-button link type="success" size="small" @click="cameraHandle(scope.row.id)">摄像头</el-button>
+          <el-button link type="success" size="small" @click="cameraHandle(scope.row.id)">摄像头
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -134,7 +134,7 @@
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
-  <NvrForm ref="formRef" @success="getList" />
+  <NvrForm ref="formRef" @success="getList"/>
 
   <!-- 弹窗, 摄像头 -->
   <NvrCamera ref="videoCameraRef"/>
@@ -142,99 +142,100 @@
 </template>
 <script lang="ts" setup>
 import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
-  import download from '@/utils/download'
-  import * as NvrApi from '@/api/data/video/nvr'
-  import NvrForm from './NvrForm.vue'
-  import NvrCamera from './NvrCamera.vue'
+import download from '@/utils/download'
+import * as NvrApi from '@/api/data/video/nvr'
+import NvrForm from './NvrForm.vue'
+import NvrCamera from './NvrCamera.vue'
 
-  defineOptions({name: 'Nvr'})
+defineOptions({name: 'Nvr'})
 
-  const message = useMessage() // 消息弹窗
-  const {t} = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
 
-  const loading = ref(true) // 列表的加载中
-  const total = ref(0) // 列表的总页数
-  const list = ref([]) // 列表的数据
-  const queryParams = reactive({
-    pageNo: 1,
-    pageSize: 10,
-    brand: undefined,
-    ip: undefined,
-    code: undefined,
-    name: undefined,
-    status: undefined
-  })
-  const queryFormRef = ref() // 搜索的表单
-  const exportLoading = ref(false) // 导出的加载中
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  brand: undefined,
+  ip: undefined,
+  code: undefined,
+  name: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
 
-  const videoCameraRef = ref()
+const videoCameraRef = ref()
 
-  /** 查询列表 */
-  const getList = async () => {
-    loading.value = true
-    try {
-      const data = await NvrApi.getNvrPage(queryParams)
-      list.value = data.list
-      total.value = data.total
-    } finally {
-      loading.value = false
-    }
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await NvrApi.getNvrPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
+}
 
-  const cameraHandle = (id: string) => {
-    // devCameraVisible.value = true
-    videoCameraRef.value.open(id)
-  }
+const cameraHandle = (id: string) => {
+  // devCameraVisible.value = true
+  videoCameraRef.value.open(id)
+}
 
-  /** 搜索按钮操作 */
-  const handleQuery = () => {
-    queryParams.pageNo = 1
-    getList()
-  }
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
 
-  /** 重置按钮操作 */
-  const resetQuery = () => {
-    queryFormRef.value.resetFields()
-    handleQuery()
-  }
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.brand = undefined
+  handleQuery()
+}
 
-  /** 添加/修改操作 */
-  const formRef = ref()
-  const openForm = (type: string, id?: number) => {
-    formRef.value.open(type, id)
-  }
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
 
-  /** 删除按钮操作 */
-  const handleDelete = async (id: number) => {
-    try {
-      // 删除的二次确认
-      await message.delConfirm()
-      // 发起删除
-      await NvrApi.deleteNvr(id)
-      message.success(t('common.delSuccess'))
-      // 刷新列表
-      await getList()
-    } catch {
-    }
-  }
-
-  /** 导出按钮操作 */
-  const handleExport = async () => {
-    try {
-      // 导出的二次确认
-      await message.exportConfirm()
-      // 发起导出
-      exportLoading.value = true
-      const data = await NvrApi.exportNvr(queryParams)
-      download.excel(data, '录像机列表.xls')
-    } catch {
-    } finally {
-      exportLoading.value = false
-    }
-  }
-
-  /** 初始化 **/
-  onMounted(async () => {
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await NvrApi.deleteNvr(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
     await getList()
-  })
+  } catch {
+  }
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await NvrApi.exportNvr(queryParams)
+    download.excel(data, '录像机列表.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
 </script>
diff --git a/src/views/data/swagger/index.vue b/src/views/data/wiki/index.vue
similarity index 80%
rename from src/views/data/swagger/index.vue
rename to src/views/data/wiki/index.vue
index d9e9e4e..5f53507 100644
--- a/src/views/data/swagger/index.vue
+++ b/src/views/data/wiki/index.vue
@@ -6,7 +6,7 @@
 <script lang="ts" setup>
 import * as ConfigApi from '@/api/infra/config'
 
-defineOptions({ name: 'DataSwagger' })
+defineOptions({ name: 'DataWiki' })
 
 const loading = ref(true) // 是否加载中
 const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html')
@@ -14,7 +14,7 @@
 /** 初始化 */
 onMounted(async () => {
   try {
-    const data = await ConfigApi.getConfigKey('data.swagger')
+    const data = await ConfigApi.getConfigKey('data.wiki')
     if (data && data.length > 0) {
       src.value = data
     }
diff --git a/src/views/model/mpk/file/MpkForm.vue b/src/views/model/mpk/file/MpkForm.vue
index 6466eaf..102765c 100644
--- a/src/views/model/mpk/file/MpkForm.vue
+++ b/src/views/model/mpk/file/MpkForm.vue
@@ -116,7 +116,7 @@
                       float: right;
                       color: var(--el-text-color-secondary);
                       font-size: 13px;">
-                    <img :src="'/SimtreeUnitImage/' + item.iconName" style="height: 24px;" :alt=" item.iconDesc" />
+                    <img :src="staticDir + 'SimtreeUnitImage/' + item.iconName" style="height: 24px;" :alt=" item.iconDesc" />
                   </span>
                 </el-option>
               </el-select>
@@ -268,6 +268,8 @@
   const route = useRoute() // 路由
   const router = useRouter();
 
+  const staticDir = ref(import.meta.env.VITE_STATIC_DIR)
+
   const treeData = ref([])
   const iconList = ref([] as MpkIconApi.MpkIconVO)
   const pkgNameList = ref([] as MpkPackApi.MpkPackVO)
diff --git a/src/views/model/mpk/file/MpkRun.vue b/src/views/model/mpk/file/MpkRun.vue
index d3ff1c9..a99ae2b 100644
--- a/src/views/model/mpk/file/MpkRun.vue
+++ b/src/views/model/mpk/file/MpkRun.vue
@@ -36,7 +36,7 @@
       <el-divider content-position="left">模型参数信息</el-divider>
       <el-row :gutter="20">
         <el-col :span="2" style="margin-bottom: 10px;margin-left: 20px">
-          <el-button tag="a" href="/template/模型参数导入模板.xlsx" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button>
+          <el-button tag="a" :href="staticDir + '/template/模型参数导入模板.xlsx'" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button>
         </el-col>
         <el-col :span="2" style="margin-bottom: 10px;">
           <el-upload
@@ -116,6 +116,7 @@
   import * as MpkApi from '@/api/model/mpk/mpk'
   import {FormRules} from "element-plus";
   import {getAccessToken, getTenantId} from "@/utils/auth";
+  const staticDir = ref(import.meta.env.VITE_STATIC_DIR)
 
   const { t } = useI18n() // 国际化
   const message = useMessage() // 消息弹窗
diff --git a/src/views/model/mpk/icon/index.vue b/src/views/model/mpk/icon/index.vue
index 8d0f95f..2f97330 100644
--- a/src/views/model/mpk/icon/index.vue
+++ b/src/views/model/mpk/icon/index.vue
@@ -50,7 +50,7 @@
       <el-table-column prop="iconDesc" label="描述"/>
       <el-table-column align="center" label="图标" prop="icon" width="100">
         <template #default="scope">
-          <img :src="'/SimtreeUnitImage/' + scope.row.iconName" class="mpk-icon-list" :alt=" scope.row.iconDesc" />
+          <img :src="staticDir + 'SimtreeUnitImage/' + scope.row.iconName" class="mpk-icon-list" :alt=" scope.row.iconDesc" />
         </template>
       </el-table-column>
       <el-table-column prop="sort" label="排序"/>
@@ -91,6 +91,8 @@
 
 defineOptions({name: 'MpkIcon'})
 
+const staticDir = ref(import.meta.env.VITE_STATIC_DIR)
+
 const message = useMessage() // 消息弹窗
 const {t} = useI18n() // 国际化
 
diff --git a/src/views/model/pre/analysis/index.vue b/src/views/model/pre/analysis/index.vue
index 5418745..b0939c3 100644
--- a/src/views/model/pre/analysis/index.vue
+++ b/src/views/model/pre/analysis/index.vue
@@ -4,37 +4,42 @@
       <el-form :inline="true" :model="formData" label-width="80px">
         <el-form-item label="开始时间">
           <el-date-picker
-              size="mini"
-              v-model="formData.startTime"
-              type="datetime"
-              placeholder="选择日期时间"/>
+            v-model="formData.startTime"
+            type="datetime"
+            format="YYYY-MM-DD HH:mm:00"
+            value-format="YYYY-MM-DD HH:mm:00"
+            placeholder="选择日期时间"/>
         </el-form-item>
         <el-form-item label="结束时间">
           <el-date-picker
-              size="mini"
-              v-model="formData.endTime"
-              type="datetime"
-              placeholder="选择日期时间"/>
+            v-model="formData.endTime"
+            type="datetime"
+            format="YYYY-MM-DD HH:mm:00"
+            value-format="YYYY-MM-DD HH:mm:00"
+            placeholder="选择日期时间"/>
         </el-form-item>
         <el-form-item label="预测时间">
           <el-date-picker
-              size="mini"
-              v-model="formData.predictTime"
-              type="datetime"
-              placeholder="选择日期时间"/>
+            v-model="formData.predictTime"
+            type="datetime"
+            format="YYYY-MM-DD HH:mm:00"
+            value-format="YYYY-MM-DD HH:mm:00"
+            placeholder="选择日期时间"/>
         </el-form-item>
         <el-form-item label="预测频率">
-          <el-input-number size="mini" v-model="formData.predictFreq" controls-position="right" :min="1"
+          <el-input-number v-model="formData.predictFreq" controls-position="right"
+                           :min="1"
                            :max="10"/>
         </el-form-item>
         <el-form-item>
           <el-button-group>
-            <el-button size="mini" type="primary" plain :icon="ArrowLeft"
-                       v-loading="loading1" @click="leftSearchDataByRange()"/>
-            <el-button size="mini" type="primary" plain :icon="Search"
-                       v-loading="loading1" @click="getList()">查询</el-button>
-            <el-button size="mini" type="primary" plain :icon="ArrowRight"
-                       v-loading="loading1" @click="rightSearchDataByRange()"/>
+            <el-button type="primary" plain :icon="ArrowLeft"
+                       :loading="loading1" @click="leftSearchDataByRange()"/>
+            <el-button type="primary" plain :icon="Search"
+                       :loading="loading1" @click="getList()">查询
+            </el-button>
+            <el-button type="primary" plain :icon="ArrowRight"
+                       :loading="loading1" @click="rightSearchDataByRange()"/>
           </el-button-group>
         </el-form-item>
 
@@ -42,46 +47,52 @@
           <div class="his-body-left">
             <div class="his-body-tree">
               <el-tree
-                  :data="treeData"
-                  show-checkbox
-                  node-key="id"
-                  ref="tree"
-                  highlight-current
-                  :props="defaultProps"
-                  @check="onCheckTree"/>
+                :data="treeData"
+                show-checkbox
+                node-key="id"
+                ref="tree"
+                highlight-current
+                :props="defaultProps"
+                @check="onCheckTree"/>
             </div>
           </div>
           <div class="his-body-right">
             <div class="his-body-chart">
-              <el-form :inline="true" :model="calRateForm" :rules="formRules" ref="calRateForm" label-width="108px">
+              <el-form :inline="true" :model="calRateForm" :rules="formRules" ref="calRateFormRef"
+                       label-width="108px">
                 <el-row>
-                  <el-col :span="6" >
+                  <el-col :span="6">
                     <el-form-item label="预测项" prop="calItem" style="width: 90%">
-                      <el-select v-model="calRateForm.calItem" @change="calItemBaseVale" placeholder="请选择">
+                      <el-select size="small" v-model="calRateForm.calItem"
+                                 @change="calItemBaseVale"
+                                 placeholder="请选择">
                         <el-option
-                            v-for="item in formData.checkedItemData"
-                            :key="item.id"
-                            :label="item.label"
-                            :value="item.id"/>
+                          v-for="itemOut in formData.checkedItemData"
+                          :key="itemOut.id"
+                          :label="itemOut.label"
+                          :value="itemOut.id"/>
                       </el-select>
                     </el-form-item>
                   </el-col>
                   <el-col :span="6">
                     <el-form-item label="精准度偏差" prop="IN_DEVIATION">
-                      <el-input-number size="mini" v-model="calRateForm.IN_DEVIATION" controls-position="right" :min="1"
+                      <el-input-number size="small" v-model="calRateForm.IN_DEVIATION"
+                                       controls-position="right" :min="1"
                                        :max="10"/>
                     </el-form-item>
                   </el-col>
                   <el-col :span="6">
                     <el-form-item label="不可信率偏差" prop="OUT_DEVIATION">
-                      <el-input-number size="mini" v-model="calRateForm.OUT_DEVIATION" controls-position="right"
+                      <el-input-number size="small" v-model="calRateForm.OUT_DEVIATION"
+                                       controls-position="right"
                                        :min="1"
                                        :max="20"/>
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item>
-                      <el-button size="mini" type="primary" plain :loading="loading2" @click="calAccuracyRate">计算精准度
+                      <el-button size="small" type="primary" plain :loading="loading2"
+                                 @click="calAccuracyRate">计算精准度
                       </el-button>
                     </el-form-item>
                   </el-col>
@@ -89,54 +100,54 @@
                 <el-row>
                   <el-col :span="4">
                     <el-form-item label="精准度:">
-                      {{calRateForm.IN_ACCURACY_RATE}}%
+                      {{ calRateForm.IN_ACCURACY_RATE }}%
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="预测平均值:">
-                      {{calRateForm.itemPreAvg}}
+                      {{ calRateForm.itemPreAvg }}
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="预测最大值:">
-                      {{calRateForm.itemPreMax}}
+                      {{ calRateForm.itemPreMax }}
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="预测最小值:">
-                      {{calRateForm.itemPreMin}}
+                      {{ calRateForm.itemPreMin }}
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="预测累积量:">
-                      {{calRateForm.preCumulant}}
+                      {{ calRateForm.preCumulant }}
                     </el-form-item>
                   </el-col>
                 </el-row>
                 <el-row>
                   <el-col :span="4">
                     <el-form-item label="不可信率:">
-                      {{calRateForm.OUT_ACCURACY_RATE}}%
+                      {{ calRateForm.OUT_ACCURACY_RATE }}%
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="真实平均值:">
-                      {{calRateForm.itemAvg}}
+                      {{ calRateForm.itemAvg }}
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="真实最大值:">
-                      {{calRateForm.itemMax}}
+                      {{ calRateForm.itemMax }}
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="真实最小值:">
-                      {{calRateForm.itemMin}}
+                      {{ calRateForm.itemMin }}
                     </el-form-item>
                   </el-col>
                   <el-col :span="4">
                     <el-form-item label="真实累积量:">
-                      {{calRateForm.realCumulant}}
+                      {{ calRateForm.realCumulant }}
                     </el-form-item>
                   </el-col>
                 </el-row>
@@ -146,54 +157,506 @@
                   <el-col :span="12">
                     <el-form-item label="数据类型">
                       <el-checkbox-group v-model="formData.chartCheck" @change="changeChartCheck">
-                        <el-checkbox v-for="item in formData.chartOptions" :label="item" :key="item">{{item}}
+                        <el-checkbox v-for="item in formData.chartOptions" :label="item"
+                                     :key="item">{{ item }}
                         </el-checkbox>
                       </el-checkbox-group>
                     </el-form-item>
                   </el-col>
                   <el-col :span="6">
                     <el-form-item>
-                      <el-radio v-model="formData.isMultipleY" :label="false" @input="onChangeMultipleY">单坐标轴</el-radio>
-                      <el-radio v-model="formData.isMultipleY" :label="true" @input="onChangeMultipleY">多坐标轴</el-radio>
+                      <el-radio v-model="formData.isMultipleY" :label="false"
+                                @input="onChangeMultipleY">单坐标轴
+                      </el-radio>
+                      <el-radio v-model="formData.isMultipleY" :label="true"
+                                @input="onChangeMultipleY">多坐标轴
+                      </el-radio>
                     </el-form-item>
                   </el-col>
                 </el-row>
               </el-form>
-              <div id="data-analysis" style="height: 500px;"></div>
+              <div ref="dataAnalysisChart" style="height: 500px;"></div>
             </div>
           </div>
         </div>
       </el-form>
-
     </div>
   </el-card>
 </template>
 <script lang="ts" setup>
-  import {getYMDHMS} from "@/utils/dateUtil"
-  import * as CategoryApi from "@/api/data/ind/category";
-  import * as DmModule from '@/api/model/pre/dm'
-  import * as ItemApi from "@/api/data/ind/item/item";
-  import * as MmPredictItem from '@/api/model/pre/predict'
-  import * as echarts from "echarts";
-  import { onMounted, ref } from 'vue';
-  import { Search, ArrowLeft, ArrowRight,} from '@element-plus/icons-vue'
+import {getYMDHMS} from "@/utils/dateUtil"
+import * as McsApi from '@/api/model/mcs'
+import * as echarts from "echarts";
+import {Search, ArrowLeft, ArrowRight,} from '@element-plus/icons-vue'
 
-  defineOptions({name: 'AnalysisformData'})
+defineOptions({name: 'AnalysisformData'})
 
-  const message = useMessage() // 消息弹窗
-  const { t } = useI18n() // 国际化
-  const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[])
-  const loading1 = ref(false) // 列表的加载中
-  const loading2 = ref(false) // 列表的加载中
-  const total = ref(0) // 列表的总页数
-  const list = ref([]) // 字典表格数据
-  const queryParams = reactive({
-    pageNo: 1,
-    pageSize: 10,
-    itemno: '',
-    itemname: '',
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+const dataAnalysisChart = ref(null);
+const loading1 = ref(false) // 列表的加载中
+const loading2 = ref(false) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 字典表格数据
+let formData = ref({
+  rangeDate: '',
+  startTime: '',
+  endTime: '',
+  predictTime: '',
+  predictTimeStr: '',
+  startTimeStr: '',
+  endTimeStr: '',
+  predictTimeStamp: 0,
+  startTimeStamp: 0,
+  endTimeStamp: 0,
+  currentStamp: '',
+  currentStamp60: '',
+  predictStamp: '',
+  chartCheck: ['T+L', '真实值'],
+  chartOptions: ['T+N', 'T+L', '当时', '真实值', '调整值'],
+  checkedItemData: [],
+  backItem: '',
+  backValue: 0,
+  backCoe: 1,
+  preCumulant: 0,
+  realCumulant: 0,
+  queryStep: 2,
+  isMultipleYRadio: '单坐标轴',
+  isMultipleY: false,
+  predictFreq: 3,
+})
+const calRateFormRef = ref()
+const calRateForm = ref({
+  calItem: undefined,
+  IN_DEVIATION: 0,
+  OUT_DEVIATION: 0,
+  IN_ACCURACY_RATE: 0,
+  OUT_ACCURACY_RATE: 0,
+  itemAvg: 0,
+  itemMax: 0,
+  itemMin: 0,
+  itemPreAvg: 0,
+  itemPreMax: 0,
+  itemPreMin: 0,
+  preCumulant: 0,
+  realCumulant: 0
+})
+let itemData = ref({
+  currentTreeList: [],
+  chart: {},
+  option: {}
+})
+const treeData = ref([])
+const itemDataObject = ref()
+const timer = ref()
+let myChart = null;
+
+const formRules = reactive({
+  calItem: [{required: true, message: '预测项不能为空', trigger: 'blur'}],
+  IN_DEVIATION: [{required: true, message: '精准度偏差不能为空', trigger: 'blur'}],
+  OUT_DEVIATION: [{required: true, message: '不可信率偏差不能为空', trigger: 'blur'}],
+})
+
+/** 查询列表 */
+const getList = async () => {
+  loading1.value = true
+  try {
+    if (!formData.value.chartCheck) {
+      formData.value.chartCheck = ['真实值']
+    }
+    let chartCheckArray = formData.value.chartCheck;
+    if (!formData.value.checkedItemData || formData.value.checkedItemData.length == 0) {
+      itemData.value.option = {};
+      return;
+    }
+    let outIds = formData.value.checkedItemData.map(item => {
+      return item.id
+    })
+    const params = reactive({
+      outIds: outIds,
+      predictTime: formData.value.predictTime,
+      startTime: formData.value.startTime,
+      endTime: formData.value.endTime
+    })
+    const data = await McsApi.getPreDataCharts(params)
+    formData.value.predictTime = data.predictTime;
+    formData.value.startTime = data.startTime
+    formData.value.endTime = data.endTime
+
+    let xAxisData = data.categories;
+    let yAxisData = [];
+    let offset = 0;
+    let yAxisIndex = 0;
+    let legendData = [];
+    let yMaxArr = [];
+    let seriesData = [];
+    seriesData.push({
+      name: '',
+      data: [null],
+      type: 'line',
+      smooth: true,
+      color: 'green',
+      markLine: {
+        silent: true,
+        lineStyle: {
+          color: '#32a487',
+          width: 2
+        },
+        data: [{
+          xAxis: formData.value.predictTime
+        }],
+        label: {
+          normal: {
+            formatter: formData.value.predictTime
+          }
+        },
+        symbol: ['circle', 'none'],
+      },
+    });
+    itemDataObject.value = {}
+    for (let i = 0; i < data.dataViewList.length; i++) {
+      let dataView = data.dataViewList[i]
+      itemDataObject.value[dataView.outId] = dataView;
+      let maxValue = dataView.maxValue;
+      let minValue = dataView.minValue;
+      yAxisIndex = formData.value.isMultipleY ? i : 0;
+      let yMax = maxValue;
+      if (maxValue < 0) {
+        maxValue = 1;
+      } else if (maxValue < 10) {
+        yMax = (Math.ceil(maxValue * 11) / 10).toFixed(1);
+      } else if (maxValue < 100) {
+        yMax = (Math.ceil(maxValue * 1.1 / 5) * 5);
+      } else {
+        yMax = (Math.ceil(maxValue * 1.1 / 10) * 10);
+      }
+      yMaxArr.push(yMax);
+      let yMin = minValue;
+      if (minValue >= 0) {
+        yMin = 0;
+      } else if (minValue > -10) {
+        yMin = (Math.floor(minValue * 11) / 10).toFixed(1);
+      } else if (minValue > -100) {
+        yMin = (Math.floor(minValue * 1.1 / 5) * 5);
+      } else {
+        yMin = (Math.floor(minValue * 1.1 / 10) * 10);
+      }
+      yAxisData.push({
+        type: 'value',
+        name: "",
+        min: yMin,
+        max: yMax,
+        position: 'left',
+        offset: offset,
+        splitLine: {
+          show: false
+        },
+        axisLine: {
+          show: true,
+          lineStyle: {}
+        },
+        axisLabel: {
+          formatter: '{value}'
+        }
+      })
+      offset = offset + 40
+      if (chartCheckArray.indexOf('真实值') !== -1) {
+        let legendName = dataView.resultstr + '(真实)';
+        legendData.push(legendName);
+        seriesData.push({
+          name: legendName,
+          data: dataView.realData || [],
+          type: 'line',
+          yAxisIndex: yAxisIndex,
+          showSymbol: false,
+          smooth: true,
+          lineStyle: {
+            width: 2
+          }
+        });
+      }
+      if (chartCheckArray.indexOf('T+N') !== -1) {
+        let legendName = dataView.resultstr + '(T+N)';
+        seriesData.push({
+          name: legendName,
+          data: dataView.preDataN || [],
+          type: 'line',
+          yAxisIndex: yAxisIndex,
+          showSymbol: false,
+          smooth: true,
+          lineStyle: {
+            width: 2
+          }
+        });
+      }
+      if (chartCheckArray.indexOf('T+L') !== -1) {
+        let legendName = dataView.resultstr + '(T+L)';
+        legendData.push(legendName);
+        seriesData.push({
+          name: legendName,
+          data: dataView.preDataL || [],
+          type: 'line',
+          showSymbol: false,
+          connectNulls: true,
+          yAxisIndex: yAxisIndex,
+          smooth: true,
+          lineStyle: {
+            width: 2
+          }
+        });
+      }
+      if (chartCheckArray.indexOf('当时') !== -1) {
+        let legendName = dataView.resultstr + '(当时)';
+        legendData.push(legendName);
+        seriesData.push({
+          name: legendName,
+          data: dataView.curData || [],
+          type: 'line',
+          yAxisIndex: yAxisIndex,
+          showSymbol: false,
+          smooth: true,
+          lineStyle: {
+            width: 2
+          }
+        });
+      }
+      if (chartCheckArray.indexOf('调整值') !== -1) {
+        let legendName = dataView.resultstr + '(调整值)';
+        legendData.push(legendName);
+        seriesData.push({
+          name: legendName,
+          data: dataView.adjData || [],
+          type: 'line',
+          yAxisIndex: yAxisIndex,
+          showSymbol: false,
+          connectNulls: true,
+          smooth: true,
+          lineStyle: {
+            width: 2,
+            type: 'dashed'
+          }
+        });
+      }
+    }
+    //如果最大值相差不大,改成一致大小
+    if (yMaxArr.length > 1) {
+      let max = Math.max.apply(null, yMaxArr);
+      let min = Math.min.apply(null, yMaxArr);
+      if (Math.abs((max - min) / max) <= 0.2) {
+        for (let i = 0; i < yAxisData.length; i++) {
+          yAxisData[i].max = max;
+        }
+      }
+    }
+    myChart = echarts.init(dataAnalysisChart.value);
+    let option = {
+      title: {
+        text: ''
+      },
+      tooltip: {
+        trigger: 'axis'
+      },
+      legend: {
+        show: true,
+        data: legendData,
+        top: 10
+      },
+      grid: {
+        top: '20%',
+        left: '3%',
+        right: '6%',
+        bottom: '3%',
+        containLabel: true
+      },
+      xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: xAxisData
+      },
+      yAxis: formData.value.isMultipleY ? yAxisData : {
+        type: 'value',
+        splitLine: {show: false},
+        axisLine: {show: true}
+      },
+      dataZoom: [
+        {
+          type: 'inside',
+          start: 0,
+          end: 100
+        },
+        {
+          start: 0,
+          end: 10
+        }
+      ],
+      series: seriesData
+    }
+    myChart.clear()
+    myChart.setOption(option)
+  } finally {
+    loading1.value = false
+  }
+}
+
+onMounted(() => {
+  resetForm()
+  getPreItemTree()
+})
+
+async function getPreItemTree() {
+  treeData.value = await McsApi.getPredictItemTree()
+}
+
+function leftSearchDataByRange() {
+  let mins = getRangeMins();
+  let startTime = formData.value.startTime;
+  let endTime = formData.value.endTime;
+  let predictTime = formData.value.predictTime;
+  if (predictTime) {
+    predictTime = getYMDHMS(new Date(predictTime) - 1000 * 60 * mins);
+    formData.value.predictTime = predictTime;
+  }
+  if (startTime) {
+    startTime = getYMDHMS(new Date(startTime) - 1000 * 60 * mins);
+    formData.value.startTime = startTime;
+  }
+  if (endTime) {
+    endTime = getYMDHMS(new Date(endTime) - 1000 * 60 * mins);
+    formData.value.endTime = endTime;
+  }
+  getList();
+}
+
+function getRangeMins() {
+  let result: string | number = 0;
+  if (formData.value.startTime && formData.value.endTime) {
+    let startStamp = new Date(formData.value.startTime).getTime();
+    let endStamp = new Date(formData.value.endTime).getTime();
+    let queryStep = ((endStamp - startStamp) / (1000 * 60)).toFixed(0);
+    result = queryStep >= 0 ? queryStep : 0;
+  }
+  return result;
+}
+
+function onCheckTree(data, checked, indeterminate) {
+  formData.value.checkedItemData = [];
+  if (checked.checkedNodes) {
+    formData.value.checkedItemData = [...checked.checkedNodes]
+  }
+  debounce(getList, 1000);
+}
+
+function debounce(func, wait) {
+  let args = [];
+  if (timer.value) {
+    clearTimeout(timer.value);
+  }
+  timer.value = setTimeout(() => {
+    func.apply(this, args);
+    timer.value = null;
+  }, wait)
+}
+
+function calItemBaseVale() {
+  if (!calRateForm.value.calItem) {
+    calRateForm.value.itemPreMax = 0;
+    calRateForm.value.itemPreMin = 0;
+    calRateForm.value.itemPreAvg = 0;
+    calRateForm.value.preCumulant = 0;
+    calRateForm.value.itemMax = 0;
+    calRateForm.value.itemMin = 0;
+    calRateForm.value.itemAvg = 0;
+    calRateForm.value.realCumulant = 0;
+  } else {
+    let dataView = itemDataObject.value[calRateForm.value.calItem]
+    calRateForm.value.itemPreMax = dataView.preMax;
+    calRateForm.value.itemPreMin = dataView.preMin;
+    calRateForm.value.itemPreAvg = dataView.preAvg;
+    calRateForm.value.preCumulant = dataView.preCumulant;
+    calRateForm.value.itemMax = dataView.hisMax;
+    calRateForm.value.itemMin = dataView.hisMin;
+    calRateForm.value.itemAvg = dataView.hisAvg;
+    calRateForm.value.realCumulant = dataView.hisCumulant;
+  }
+}
+
+function calAccuracyRate() {
+  this.$refs['calRateForm'].validate((valid) => {
+    if (!valid) {
+      return false
+    }
+    let dataView = itemDataObject[calRateForm.value.calItem]
+    let seriesReaData = dataView.realData;
+    let seriesPreData = dataView.preDataL;
+    if (seriesReaData == null || seriesPreData == null ||
+      seriesReaData.length === 0 || seriesPreData.length === 0) {
+      loading2.value = false;
+      return;
+    }
+    let predictValueMap = {};
+    seriesPreData.forEach(function (item) {
+      predictValueMap[item[0]] = item[1];
+    })
+    let pointValueMap = {};
+    seriesReaData.forEach(function (item) {
+      pointValueMap[item[0]] = item[1];
+    })
+    let inDeviation = Number(calRateForm.value.IN_DEVIATION);
+    let outDeviation = Number(calRateForm.value.OUT_DEVIATION);
+    if (inDeviation === 0 && outDeviation === 0) {
+      loading2.value = false;
+      return;
+    }
+    let inDeviationCount = 0;
+    let outDeviationCount = 0;
+    let totalCount = 0;
+    for (let key in predictValueMap) {
+      let predictValue = predictValueMap[key];
+      let pointValue = pointValueMap[key];
+      if (pointValue == null || "" === pointValue || predictValue == null || "" === predictValue) {
+        continue;
+      }
+      let deviationAbs = (predictValue - pointValue) >= 0 ? (predictValue - pointValue) : (predictValue - pointValue) * -1;
+      if (deviationAbs < inDeviation) {
+        inDeviationCount = inDeviationCount + 1;
+      }
+      if (deviationAbs > outDeviation) {
+        outDeviationCount = outDeviationCount + 1;
+      }
+      totalCount = totalCount + 1;
+    }
+
+    let rateIn = (inDeviationCount / totalCount * 100).toFixed(2);
+    let rateOut = (outDeviationCount / totalCount * 100).toFixed(2);
+    calRateForm.value.IN_ACCURACY_RATE = Number(rateIn);
+    calRateForm.value.OUT_ACCURACY_RATE = Number(rateOut);
+    loading2.value = false;
   })
-  let formData = ref({
+}
+
+function rightSearchDataByRange() {
+  let mins = getRangeMins();
+  let startTime = formData.value.startTime;
+  let endTime = formData.value.endTime;
+  let predictTime = formData.value.predictTime;
+  if (predictTime) {
+    predictTime = getYMDHMS(new Date(predictTime) - 0 + 1000 * 60 * mins);
+    formData.value.predictTime = predictTime;
+  }
+  if (startTime) {
+    startTime = getYMDHMS(new Date(startTime) - 0 + 1000 * 60 * mins);
+    formData.value.startTime = startTime;
+  }
+  if (endTime) {
+    endTime = getYMDHMS(new Date(endTime) - 0 + 1000 * 60 * mins);
+    formData.value.endTime = endTime;
+  }
+  getList();
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
     rangeDate: '',
     startTime: '',
     endTime: '',
@@ -219,9 +682,9 @@
     isMultipleYRadio: '单坐标轴',
     isMultipleY: false,
     predictFreq: 3,
-  })
-  let calRateForm = ref({
-    calItem: '',
+  }
+  calRateForm.value = {
+    calItem: undefined,
     IN_DEVIATION: 0,
     OUT_DEVIATION: 0,
     IN_ACCURACY_RATE: 0,
@@ -232,462 +695,47 @@
     itemPreAvg: 0,
     itemPreMax: 0,
     itemPreMin: 0,
-    preCumulant:0,
-    realCumulant:0
-  })
-  let itemData = ref({
-    currentTreeList: [],
-    chart: {},
-    option: {}
-  })
-  const chartContainer = ref(null);
-  const treeData = ref([])
-  const itemDataObject = ref()
-  const timer = ref()
-
-  const formRules = reactive({
-    calItem: [{required: true, message: '预测项不能为空', trigger: 'blur'}],
-    IN_DEVIATION: [{required: true, message: '精准度偏差不能为空', trigger: 'blur'}],
-    OUT_DEVIATION: [{required: true, message: '不可信率偏差不能为空', trigger: 'blur'}],
-  })
-  //const myChart = echarts.init(document.getElementById("data-analysis"));
-  /** 查询列表 */
-  const getList = async () => {
-    loading1.value = true
-    try {
-      const data = formData.value
-      if (!formData.value.chartCheck) {
-        formData.value.chartCheck = ['真实值']
-      }
-      let chartCheckArray = formData.value.chartCheck;
-      if (!formData.value.checkedItemData || formData.value.checkedItemData.length == 0) {
-        itemData.value.option = {};
-        return;
-      }
-      let itemIdList = formData.value.checkedItemData.map(item => {
-        return item.id
-      })
-      const params = ref({
-        itemIds: itemIdList.join(','),
-        predictTime: formData.value.predictTime,
-        startTime: formData.value.startTime,
-        endTime: formData.value.endTime
-      })
-      const res = await MmPredictItem.getViewCharts(params)
-      if (res.code !== 0) {
-        return message.error(res.msg)
-      }
-      formData.value.predictTime = res.data.predictTime;
-      formData.value.startTime = res.data.startTime
-      formData.value.endTime = res.data.endTime
-
-      let xAxisData = res.data.categories;
-      let yAxisData = [];
-      let offset = 0;
-      let yAxisIndex = 0;
-      let legendData = [];
-      let yMaxArr = [];
-      let seriesData = [];
-      seriesData.push({
-        name: '',
-        data: [null],
-        type: 'line',
-        smooth: true,
-        color: 'green',
-        markLine: {
-          silent: true,
-          lineStyle: {
-            color: '#32a487',
-            width: 2
-          },
-          data: [{
-            xAxis: formData.value.predictTime
-          }],
-          label: {
-            normal: {
-              formatter: formData.value.predictTime
-            }
-          },
-          symbol: ['circle', 'none'],
-        },
-      });
-      itemDataObject.value = {}
-      for (let i = 0; i < res.data.dataViewList.length; i++) {
-        let dataView = res.data.dataViewList[i]
-        itemDataObject.value.dataView.itemId = dataView;
-        let maxValue = dataView.maxValue;
-        let minValue = dataView.minValue;
-        yAxisIndex = formData.value.isMultipleY ? i : 0;
-        let yMax = maxValue;
-        if (maxValue < 0) {
-          maxValue = 1;
-        } else if (maxValue < 10) {
-          yMax = (Math.ceil(maxValue * 11) / 10).toFixed(1);
-        } else if (maxValue < 100) {
-          yMax = (Math.ceil(maxValue * 1.1 / 5) * 5);
-        } else {
-          yMax = (Math.ceil(maxValue * 1.1 / 10) * 10);
-        }
-        yMaxArr.push(yMax);
-        let yMin = minValue;
-        if (minValue >= 0) {
-          yMin = 0;
-        } else if (minValue > -10) {
-          yMin = (Math.floor(minValue * 11) / 10).toFixed(1);
-        } else if (minValue > -100) {
-          yMin = (Math.floor(minValue * 1.1 / 5) * 5);
-        } else {
-          yMin = (Math.floor(minValue * 1.1 / 10) * 10);
-        }
-        yAxisData.push({
-          type: 'value',
-          name: "",
-          min: yMin,
-          max: yMax,
-          position: 'left',
-          offset: offset,
-          splitLine: {
-            show: false
-          },
-          axisLine: {
-            show: true,
-            lineStyle: {}
-          },
-          axisLabel: {
-            formatter: '{value}'
-          }
-        })
-        offset = offset + 40
-        if (chartCheckArray.indexOf('真实值') !== -1) {
-          let legendName = dataView.itemName + '(真实)';
-          legendData.push(legendName);
-          seriesData.push({
-            name: legendName,
-            data: dataView.realData || [],
-            type: 'line',
-            yAxisIndex: yAxisIndex,
-            showSymbol: false,
-            smooth: true,
-            lineStyle: {
-              width: 3
-            }
-          });
-        }
-        if (chartCheckArray.indexOf('T+N') !== -1) {
-          let legendName = dataView.itemName + '(T+N)';
-          seriesData.push({
-            name: legendName,
-            data: dataView.preDataN || [],
-            type: 'line',
-            yAxisIndex: yAxisIndex,
-            showSymbol: false,
-            smooth: true,
-            lineStyle: {
-              width: 3
-            }
-          });
-        }
-        if (chartCheckArray.indexOf('T+L') !== -1) {
-          let legendName = dataView.itemName + '(T+L)';
-          legendData.push(legendName);
-          seriesData.push({
-            name: legendName,
-            data: dataView.preDataL || [],
-            type: 'line',
-            showSymbol: false,
-            connectNulls: true,
-            yAxisIndex: yAxisIndex,
-            smooth: true,
-            lineStyle: {
-              width: 3
-            }
-          });
-        }
-        if (chartCheckArray.indexOf('当时') !== -1) {
-          let legendName = dataView.itemName + '(当时)';
-          legendData.push(legendName);
-          seriesData.push({
-            name: legendName,
-            data: dataView.curData || [],
-            type: 'line',
-            yAxisIndex: yAxisIndex,
-            showSymbol: false,
-            smooth: true,
-            lineStyle: {
-              width: 3
-            }
-          });
-        }
-        if (chartCheckArray.indexOf('调整值') !== -1) {
-          let legendName = dataView.itemName + '(调整值)';
-          legendData.push(legendName);
-          seriesData.push({
-            name: legendName,
-            data: dataView.adjData || [],
-            type: 'line',
-            yAxisIndex: yAxisIndex,
-            showSymbol: false,
-            connectNulls: true,
-            smooth: true,
-            lineStyle: {
-              width: 3,
-              type: 'dashed'
-            }
-          });
-        }
-      }
-      //如果最大值相差不大,改成一致大小
-      if (yMaxArr.length > 1) {
-        let max = Math.max.apply(null, yMaxArr);
-        let min = Math.min.apply(null, yMaxArr);
-        if (Math.abs((max - min) / max) <= 0.2) {
-          for (let i = 0; i < yAxisData.length; i++) {
-            yAxisData[i].max = max;
-          }
-        }
-      }
-      let option = {
-        title: {
-          text: ''
-        },
-        tooltip: {
-          trigger: 'axis'
-        },
-        legend: {
-          show: true,
-          data: legendData,
-          top: 10
-        },
-        grid: {
-          top: 50,
-          left: '3%',
-          right: '6%',
-          bottom: '3%',
-          containLabel: true
-        },
-        xAxis: {
-          type: 'category',
-          boundaryGap: false,
-          data: xAxisData
-        },
-        yAxis: formData.value.isMultipleY ? yAxisData : {
-          type: 'value',
-          splitLine: {show: false},
-          axisLine: {show: true}
-        },
-        dataZoom: [
-          {
-            type: 'inside',
-            start: 0,
-            end: 100
-          },
-          {
-            start: 0,
-            end: 10
-          }
-        ],
-        series: seriesData
-      }
-      //chart.setOption(option)
-    } finally {
-      loading1.value = false
-    }
+    preCumulant: 0,
+    realCumulant: 0
   }
-
-  onMounted(() => {
-    getPreItemTree()
-    getList()
-  })
-
-  async function getPreItemTree() {
-    treeData.value = await MmPredictItem.getMmPredictItemTree()
-  }
-
-  function leftSearchDataByRange() {
-    let mins = getRangeMins();
-    let startTime = formData.value.startTime;
-    let endTime = formData.value.endTime;
-    let predictTime = formData.value.predictTime;
-    if (predictTime) {
-      predictTime = getYMDHMS(new Date(predictTime) - 1000 * 60 * mins);
-      formData.value.predictTime = predictTime;
-    }
-    if (startTime) {
-      startTime = getYMDHMS(new Date(startTime) - 1000 * 60 * mins);
-      formData.value.startTime = startTime;
-    }
-    if (endTime) {
-      endTime = getYMDHMS(new Date(endTime) - 1000 * 60 * mins);
-      formData.value.endTime = endTime;
-    }
-    getList();
-  }
-
-  function getRangeMins () {
-    let result: string | number = 0;
-    if(formData.value.startTime && formData.value.endTime) {
-      let startStamp = new Date(formData.value.startTime).getTime();
-      let endStamp = new Date(formData.value.endTime).getTime();
-      let queryStep = ((endStamp - startStamp) / (1000 * 60)).toFixed(0);
-      result = queryStep >= 0 ? queryStep : 0;
-    }
-    return result;
-  }
-
-  function onCheckTree(data, checked, indeterminate) {
-    formData.value.checkedItemData = [];
-    if (checked.checkedNodes) {
-      formData.value.checkedItemData = [...checked.checkedNodes]
-    }
-    //myChart.clear()
-    debounce(getList(), 1000);
-  }
-
-  function debounce(func, wait) {
-    let args = [];
-    if (timer.value) {
-      clearTimeout(timer.value);
-    }
-    timer.value = setTimeout(() => {
-      func.apply(this, args);
-      timer.value = null;
-    }, wait)
-  }
-
-  function calItemBaseVale() {
-    if (!calRateForm.value.calItem) {
-      calRateForm.value.itemPreMax = 0;
-      calRateForm.value.itemPreMin = 0;
-      calRateForm.value.itemPreAvg = 0;
-      calRateForm.value.preCumulant = 0;
-      calRateForm.value.itemMax = 0;
-      calRateForm.value.itemMin = 0;
-      calRateForm.value.itemAvg = 0;
-      calRateForm.value.realCumulant = 0;
-      return
-    } else {
-      let dataView = itemDataObject[calRateForm.value.calItem]
-      calRateForm.value.itemPreMax = dataView.preMax;
-      calRateForm.value.itemPreMin = dataView.preMin;
-      calRateForm.value.itemPreAvg = dataView.preAvg;
-      calRateForm.value.preCumulant = dataView.preCumulant;
-      calRateForm.value.itemMax = dataView.hisMax;
-      calRateForm.value.itemMin = dataView.hisMin;
-      calRateForm.value.itemAvg = dataView.hisAvg;
-      calRateForm.value.realCumulant = dataView.hisCumulant;
-    }
-  }
-
-  function calAccuracyRate() {
-    this.$refs['calRateForm'].validate((valid) => {
-      if (!valid) {
-        return false
-      }
-      let dataView = itemDataObject[calRateForm.value.calItem]
-      let seriesReaData = dataView.realData;
-      let seriesPreData = dataView.preDataL;
-      if (seriesReaData == null || seriesPreData == null ||
-        seriesReaData.length === 0 || seriesPreData.length === 0) {
-        loading2.value = false;
-        return;
-      }
-      let predictValueMap = {};
-      seriesPreData.forEach(function (item) {
-        predictValueMap[item[0]] = item[1];
-      })
-      let pointValueMap = {};
-      seriesReaData.forEach(function (item) {
-        pointValueMap[item[0]] = item[1];
-      })
-      let inDeviation = Number(calRateForm.value.IN_DEVIATION);
-      let outDeviation = Number(calRateForm.value.OUT_DEVIATION);
-      if (inDeviation === 0 && outDeviation === 0) {
-        loading2.value = false;
-        return;
-      }
-      let inDeviationCount = 0;
-      let outDeviationCount = 0;
-      let totalCount = 0;
-      for (let key in predictValueMap) {
-        let predictValue = predictValueMap[key];
-        let pointValue = pointValueMap[key];
-        if (pointValue == null || "" === pointValue || predictValue == null || "" === predictValue) {
-          continue;
-        }
-        let deviationAbs = (predictValue - pointValue) >= 0 ? (predictValue - pointValue) : (predictValue - pointValue) * -1;
-        if (deviationAbs < inDeviation) {
-          inDeviationCount = inDeviationCount + 1;
-        }
-        if (deviationAbs > outDeviation) {
-          outDeviationCount = outDeviationCount + 1;
-        }
-        totalCount = totalCount + 1;
-      }
-
-      let rateIn = (inDeviationCount / totalCount * 100).toFixed(2);
-      let rateOut = (outDeviationCount / totalCount * 100).toFixed(2);
-      calRateForm.value.IN_ACCURACY_RATE = Number(rateIn);
-      calRateForm.value.OUT_ACCURACY_RATE = Number(rateOut);
-      loading2.value = false;
-    })
-  }
-
-  function rightSearchDataByRange() {
-    let mins = getRangeMins();
-    let startTime = formData.value.startTime;
-    let endTime = formData.value.endTime;
-    let predictTime = formData.value.predictTime;
-    if (predictTime) {
-      predictTime = getYMDHMS(new Date(predictTime) - 0 + 1000 * 60 * mins);
-      formData.value.predictTime = predictTime;
-    }
-    if (startTime) {
-      startTime = getYMDHMS(new Date(startTime) - 0 + 1000 * 60 * mins);
-      formData.value.startTime = startTime;
-    }
-    if (endTime) {
-      endTime = getYMDHMS(new Date(endTime) - 0 + 1000 * 60 * mins);
-      formData.value.endTime = endTime;
-    }
-    getList();
-  }
-
+  calRateFormRef.value?.resetFields()
+}
 </script>
 <style scoped>
-  .el-form-item {
-    margin-bottom: 0 !important;
-  }
+.el-form-item {
+  margin-bottom: 0 !important;
+}
 
-  .his-body-chart {
-    height: 100%;
-    border: 1px solid lightgray;
-    padding: 10px;
-  }
+.his-body-chart {
+  height: 100%;
+  border: 1px solid lightgray;
+  padding: 10px;
+}
 
-  .his-body-tree {
-    height: 100%;
-    border: 1px solid lightgray;
-    padding: 10px;
-  }
+.his-body-tree {
+  height: 100%;
+  border: 1px solid lightgray;
+  padding: 10px;
+}
 
-  .his-body-right {
-    width: 80%;
-    height: 100%;
-    padding-top: 10px;
-  }
+.his-body-right {
+  width: 80%;
+  height: 100%;
+  padding-top: 10px;
+}
 
-  .his-body-left {
-    width: 20%;
-    height: 100%;
-    padding: 10px 10px 0 0;
-  }
+.his-body-left {
+  width: 20%;
+  height: 100%;
+  padding: 10px 10px 0 0;
+}
 
-  .his-body {
-    width: 100%;
-    height: calc(calc(100vh - 48px - 38px - 130px));
-    display: flex;
-    flex-direction: row;
-    justify-content: flex-start;
-    align-content: flex-start;
-  }
+.his-body {
+  width: 100%;
+  height: calc(calc(100vh - 68px - 38px - 160px));
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+  align-content: flex-start;
+}
 </style>
diff --git a/src/views/model/pre/item/MmPredictItemChart.vue b/src/views/model/pre/item/MmPredictItemChart.vue
new file mode 100644
index 0000000..a0f867a
--- /dev/null
+++ b/src/views/model/pre/item/MmPredictItemChart.vue
@@ -0,0 +1,253 @@
+<template>
+  <el-dialog
+    title="预测数据"
+    :close-on-click-modal="false"
+    width="50%"
+    v-model="visible"
+  >
+    <el-form
+      :inline="true"
+      :model="dataForm"
+      @keydown.enter="getDataList()"
+    >
+      <el-form-item label="开始时间">
+        <el-date-picker
+          size="mini"
+          v-model="dataForm.startTime"
+          format="YYYY-MM-DD HH:mm:00"
+          value-format="YYYY-MM-DD HH:mm:00"
+          type="datetime"
+          :clearable="false"
+          placeholder="选择日期时间"/>
+      </el-form-item>
+      <el-form-item label="结束时间">
+        <el-date-picker
+          size="mini"
+          v-model="dataForm.endTime"
+          format="YYYY-MM-DD HH:mm:00"
+          value-format="YYYY-MM-DD HH:mm:00"
+          type="datetime"
+          :clearable="false"
+          placeholder="选择日期时间"/>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="getDataList()">查询</el-button>
+      </el-form-item>
+      <el-form-item>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+        >
+          <Icon icon="ep:download"/>
+          导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <div ref="chartDomPre" class="result-chart"></div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import {ref} from 'vue';
+import * as echarts from 'echarts';
+import * as McsApi from '@/api/model/mcs'
+import {getYMDHM0} from "@/utils/dateUtil"
+import download from "@/utils/download";
+
+const message = useMessage() // 消息弹窗
+const visible = ref(false);
+const chartDomPre = ref(null);
+let myChart = null;
+const chartParams = reactive({
+  itemId: undefined,
+  startTime: undefined,
+  endTime: undefined,
+})
+const dataForm = ref({
+  id: "",
+  itemName: "",
+  startTime: getYMDHM0(new Date() - 60 * 60 * 1000),
+  endTime: getYMDHM0(new Date() + 60 * 60 * 1000),
+});
+
+/** 打开弹窗 */
+const open = async (row: object) => {
+  visible.value = true
+  resetForm()
+  dataForm.value.id = row.id;
+  dataForm.value.itemName = row.itemname;
+  if (row.id) {
+    getDataList();
+  }
+}
+
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+async function getDataList() {
+  visible.value = true;
+  if (dataForm.value.id) {
+    try {
+      chartParams.itemId = dataForm.value.id;
+      chartParams.startTime = dataForm.value.startTime;
+      chartParams.endTime = dataForm.value.endTime;
+      const data = await McsApi.getPreDataItemChart(chartParams)
+      let legendData = []
+      if (data.legend && data.legend.length > 0) {
+        data.legend.forEach(item => {
+          legendData.push(item + ":" + '真实值')
+          legendData.push(item + ":" + '预测值')
+        })
+      }
+
+      let seriesData = []
+      if (data.predictTime) {
+        seriesData.push({
+          name: '',
+          data: [null],
+          type: 'line',
+          smooth: true,
+          color: 'green',
+          markLine: {
+            silent: true,
+            lineStyle: {
+              color: '#32a487',
+              width: 2
+            },
+            data: [{
+              xAxis: data.predictTime
+            }],
+            label: {
+              normal: {
+                formatter: data.predictTime
+              }
+            },
+            symbol: ['circle', 'none'],
+          },
+        });
+      }
+
+      if (data.viewMap) {
+        Object.keys(data.viewMap).forEach(key => {
+          let viewData = data.viewMap[key]
+          seriesData.push({
+            name: key + ":" + '真实值',
+            type: "line",
+            data: viewData.realData,
+            showSymbol: false,
+            smooth: false,
+            lineStyle: {
+              normal: {
+                width: 1,
+              },
+            },
+          })
+          seriesData.push({
+            name: key + ":" + '预测值',
+            type: "line",
+            data: viewData.preDataN,
+            showSymbol: false,
+            smooth: false,
+            lineStyle: {
+              normal: {
+                width: 1,
+              },
+            },
+          })
+        })
+      }
+
+      myChart = echarts.init(chartDomPre.value);
+      const option = {
+        title: {
+          text: dataForm.value.itemName,
+          top: 0,
+          left: "1%",
+          textStyle: {
+            fontSize: 14,
+          },
+        },
+        tooltip: {
+          trigger: "axis",
+          axisPointer: {
+            type: "line",
+            lineStyle: {
+              color: "#cccccc",
+              width: "1",
+              type: "dashed",
+            },
+          },
+        },
+        legend: {
+          show: true,
+          top: 20,
+          data: legendData
+        },
+        grid: {
+          top: "20%",
+          left: "3%",
+          right: "5%",
+          bottom: 10,
+          containLabel: true,
+        },
+        xAxis: {
+          type: "category",
+          boundaryGap: false,
+          data: data.categories,
+        },
+        yAxis: {
+          type: "value",
+        },
+        dataZoom: [
+          {
+            type: "inside",
+          },
+        ],
+        series: seriesData,
+      };
+      myChart.setOption(option);
+    } catch (error) {
+      console.error(error)
+    }
+  }
+}
+
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  chartParams.itemId = dataForm.value.id;
+  chartParams.startTime = dataForm.value.startTime?dataForm.value.startTime:"";
+  chartParams.endTime = dataForm.value.endTime?dataForm.value.endTime:"";
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await McsApi.exportPredictValue(chartParams)
+    download.excel(data, dataForm.value.itemName + '.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  dataForm.value = {
+    id: undefined,
+    itemName: undefined,
+    startTime: undefined,
+    endTime: undefined
+  }
+}
+</script>
+<style>
+.el-select {
+  width: 100%;
+}
+
+.result-chart {
+  height: 500px;
+}
+</style>
diff --git a/src/views/model/pre/item/MmPredictItemForm.vue b/src/views/model/pre/item/MmPredictItemForm.vue
index 9e5fb9f..cb01f79 100644
--- a/src/views/model/pre/item/MmPredictItemForm.vue
+++ b/src/views/model/pre/item/MmPredictItemForm.vue
@@ -17,7 +17,8 @@
         </el-col>
         <el-col :span="12">
           <el-form-item label="编号" prop="mmPredictItem.itemno">
-            <el-input v-model="dataForm.mmPredictItem.itemno" placeholder="编号" maxlength="50" readonly/>
+            <el-input v-model="dataForm.mmPredictItem.itemno" placeholder="编号" maxlength="50"
+                      readonly/>
           </el-form-item>
         </el-col>
       </el-row>
@@ -39,7 +40,7 @@
           <el-form-item label="粒度" prop="mmPredictItem.granularity">
             <el-select v-model="dataForm.mmPredictItem.granularity" placeholder="请选择">
               <el-option
-                v-for="dict in getIntDictOptions(DICT_TYPE.TIME_GRANULARITY)"
+                v-for="dict in getIntDictOptions(DICT_TYPE.PRED_GRANULARITY)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
@@ -99,21 +100,6 @@
                              controls-position="right"/>
           </el-form-item>
         </el-col>
-        <el-col :span="12">
-          <el-form-item label="数据点" prop="mmItemOutput.pointid">
-            <el-select
-              v-model="dataForm.mmItemOutput.pointid"
-              filterable
-              @change="changeOutputPoint"
-              placeholder="请选择">
-              <el-option
-                v-for="item in pointList"
-                :key="item.id"
-                :label="item.pointName"
-                :value="item.id"/>
-            </el-select>
-          </el-form-item>
-        </el-col>
       </el-row>
       <el-row v-if="dataForm.itemtypename === 'MergeItem'">
         <el-col :span="12">
@@ -143,6 +129,7 @@
             :on-success="uploadModelSuccess"
             :on-error="uploadModelError"
             :action="uploadUrl"
+            :show-file-list="false"
             :http-request="httpRequest">
             <el-button type="primary" @click="setReplaceModelOnly(false)">
               <Icon icon="ep:upload"/>
@@ -158,18 +145,7 @@
         </el-col>
       </el-row>
       <el-row v-if="dataForm.itemtypename === 'NormalItem'">
-        <el-col :span="8">
-          <el-form-item label="结果">
-            <el-select v-model="dataForm.mmPredictModel.resultstrid" placeholder="请选择">
-              <el-option
-                v-for="item in resultstridList"
-                :key="item.id"
-                :label="item.resultstr"
-                :value="item.id"/>
-            </el-select>
-          </el-form-item>
-        </el-col>
-        <el-col :span="8">
+        <el-col :span="12">
           <el-form-item label="关联项目">
             <el-select v-model="dataForm.mmPredictModel.mpkprojectid" placeholder="请选择">
               <el-option
@@ -180,7 +156,7 @@
             </el-select>
           </el-form-item>
         </el-col>
-        <el-col :span="8">
+        <el-col :span="12">
           <el-form-item label="编号">
             <el-input
               v-model="dataForm.mmPredictModel.modelno" placeholder="编号" maxlength="30" readonly
@@ -222,14 +198,83 @@
           </el-form-item>
         </el-col>
       </el-row>
+      <el-divider content-position="left" v-if="dataForm.itemtypename === 'NormalItem'">模型输出
+      </el-divider>
+      <el-button
+        @click="addItemOutput(dataForm.mmItemOutputList)"
+        type="primary"
+        size="small">
+        添加
+      </el-button>
+      <el-table
+        v-if="dataForm.itemtypename === 'NormalItem'"
+        :data="dataForm.mmItemOutputList"
+        border
+        style="width: 100%; margin-top: 5px;">
+        <el-table-column prop="outputorder" label="排序" align="center" width="80px"/>
+        <el-table-column label="结果" align="center" width="150px">
+          <template #default="scope">
+            <el-input v-model="scope.row.resultstr" placeholder="请输入"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="结果数据类型" align="center" width="150px">
+          <template #default="scope">
+            <el-select
+              v-model="scope.row.resultType"
+              @change="(value) => resultTypeChange(value,scope.row)"
+              filterable
+              placeholder="请选择">
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.MODEL_RESULT_TYPE)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"/>
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column label="索引" align="center" width="120px">
+          <template #default="scope">
+            <el-input-number style="width:100%;hight:100%" :disabled="scope.row.resultType !== 2"
+                             v-model="scope.row.resultIndex" :min="0" step-strictly
+                             controls-position="right"/>
+          </template>
+        </el-table-column>
+        <el-table-column label="数据点" align="center">
+          <template #default="scope">
+            <el-select
+              v-model="scope.row.pointid"
+              filterable
+              @change="(value) => changeOutputPoint(value,scope.row)"
+              placeholder="请选择">
+              <el-option
+                v-for="item in pointList"
+                :key="item.id"
+                :label="item.pointName"
+                :value="item.id"/>
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column prop="" label="操作" width="80" align="center">
+          <template #default="scope">
+            <el-button
+              @click="deleteItemOutput(scope.$index, dataForm.mmItemOutputList)"
+              type="text"
+              size="small">
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-divider content-position="left" v-if="dataForm.itemtypename === 'NormalItem'">模型设置参数
+      </el-divider>
       <el-table
         v-if="dataForm.itemtypename === 'NormalItem'"
         :data="dataForm.mmModelArithSettingsList"
         border
         style="width: 100%; margin-top: 5px;">
-        <el-table-column prop="key" label="键" align="center"/>
-        <el-table-column prop="name" label="名称" align="center"/>
-        <el-table-column prop="valuetype" label="类型" align="center"/>
+        <el-table-column prop="key" label="键" align="center" min-width="150"/>
+        <el-table-column prop="name" label="名称" align="center" min-width="150"/>
+        <el-table-column prop="valuetype" label="类型" align="center" min-width="150"/>
         <el-table-column prop="" label="值" align="center" min-width="150">
           <template #default="scope">
             <el-input size="mini" v-model="scope.row.value" maxlength="256"
@@ -237,7 +282,7 @@
           </template>
         </el-table-column>
       </el-table>
-      <el-divider content-position="left" v-if="dataForm.itemtypename === 'NormalItem'">输入参数
+      <el-divider content-position="left" v-if="dataForm.itemtypename === 'NormalItem'">模型输入参数
       </el-divider>
       <el-table
         v-if="dataForm.itemtypename === 'NormalItem'"
@@ -248,7 +293,9 @@
         <el-table-column prop="modelparamorder" label="序号" width="60" align="center"/>
         <el-table-column prop="" label="类型" width="200" align="center">
           <template #default="scope">
-            <el-select v-model="scope.row.modelparamtype" placeholder="请选择">
+            <el-select v-model="scope.row.modelparamtype"
+                       @change="changeModelparamtype(scope.row)"
+                       placeholder="请选择">
               <el-option
                 v-for="dict in getStrDictOptions(DICT_TYPE.MODEL_PARAM_TYPE)"
                 :key="dict.value"
@@ -280,18 +327,18 @@
                              style="width:100%;hight:100%"/>
           </template>
         </el-table-column>
-        <el-table-column prop="" label="操作" width="140" align="center">
+        <el-table-column prop="" label="操作" width="120" align="center">
           <template #default="scope">
             <el-button
               @click="addRow(scope.$index, dataForm.mmModelParamList)"
               type="text"
-              size="small">
+              size="mini">
               添加
             </el-button>
             <el-button
               @click="deleteRow(scope.$index, dataForm.mmModelParamList)"
               type="text"
-              size="small">
+              size="mini">
               删除
             </el-button>
           </template>
@@ -316,8 +363,8 @@
               <el-option
                 v-for="(item, index) in predictItemList"
                 :key="index"
-                :label="item.name"
-                :value="item.code"/>
+                :label="item.itemname"
+                :value="item.itemno"/>
             </el-select>
           </template>
         </el-table-column>
@@ -330,7 +377,7 @@
           label="运算符"
           align="center">
           <template #default="scope">
-            <el-select v-model="scope.row.operator" placeholder="请选择">
+            <el-select v-model="scope.row.operator" placeholder="请选择" clearable>
               <el-option
                 v-for="item in operatorList"
                 :key="item"
@@ -369,13 +416,12 @@
 </template>
 <script lang="ts" setup>
 import {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict'
-import * as MmPredictItem from '@/api/model/pre/predict'
-import * as MmItemType from '@/api/model/pre/item'
+import * as MmPredictItem from '@/api/model/pre/item'
+import * as MmItemType from '@/api/model/pre/type'
 import * as DmModule from '@/api/model/pre/dm'
-import * as MmResultTable from '@/api/model/pre/result'
 import * as ProjectApi from '@/api/model/mpk/project'
 import * as DaPoint from '@/api/data/da/point'
-import {useUpload} from '@/api/model/pre/predict'
+import {useUpload} from '@/api/model/pre/item'
 import * as ScheduleModelApi from '@/api/model/sche/model'
 
 const {uploadUrl, httpRequest} = useUpload()
@@ -391,7 +437,6 @@
 const itemTypeList = ref([])
 const itemTypeMap = ref({})
 const moduleList = ref([])
-const resultstridList = ref([])
 const mpkProjectList = ref([])
 const pointNoList = ref([])
 const pointList = ref([])
@@ -435,14 +480,7 @@
     status: undefined,
     categoryid: undefined
   },
-  mmItemOutput: {
-    id: undefined,
-    itemid: undefined,
-    pointid: undefined,
-    resulttableid: undefined,
-    tagname: undefined,
-    outputorder: undefined
-  },
+  mmItemOutputList: [],
   mmPredictModel: {
     id: undefined,
     modelno: undefined,
@@ -489,7 +527,6 @@
   'mmPredictItem.status': [{required: true, message: '是否启用不能为空', trigger: 'blur'}],
   'dmModuleItem.moduleid': [{required: true, message: '管网不能为空', trigger: 'blur'}],
   'dmModuleItem.itemorder': [{required: true, message: '排序不能为空', trigger: 'blur'}],
-  'mmItemOutput.pointid': [{required: true, message: '数据点不能为空', trigger: 'blur'}],
 
 })
 const formRef = ref() // 表单 Ref
@@ -503,15 +540,6 @@
   resetForm()
   resetFields(dataForm.value)
   setDefaultFields()
-  // 修改时,设置数据
-  if (id) {
-    formLoading.value = true
-    try {
-      getInfo(id)
-    } finally {
-      /*formLoading.value = false*/
-    }
-  }
 
   // 加载参数列表
   modelparamListMap.value = await ScheduleModelApi.getModelParamList()
@@ -524,26 +552,37 @@
   if (!dataForm.value.id) {
     dataForm.value.mmPredictItem.itemtypeid = itemTypeList.value[0].id
   }
-  dataForm.value.itemtypename = itemTypeMap[dataForm.value.mmPredictItem.itemtypeid]
 
   // 获取管网列表
   moduleList.value = await DmModule.getModuleList()
 
-  // 获取结果字符串列表
-  resultstridList.value = await MmResultTable.getResultstridList()
-
   // 获取mpk项目列表
   mpkProjectList.value = await ProjectApi.list()
+
+  // 获取normal列表
+  predictItemList.value = await MmPredictItem.getMmPredictItemList({
+    itemtypename: 'NormalItem'
+  })
 
   // 获取数据点列表
   pointNoList.value = await DaPoint.getPointList(queryParams)
   if (pointNoList.value.length > 0) {
+    pointList.value = []
     pointNoList.value.forEach(function (value) {
       pointList.value.push(value)
-      pointMap[value.id] = value.pointname
+      pointMap[value.id] = value.pointName
     })
   }
 
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      getInfo(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
   formLoading.value = false
 }
 defineExpose({open}) // 提供 open 方法,用于打开弹窗
@@ -555,6 +594,22 @@
   if (!formRef) return
   const valid = await formRef.value.validate()
   if (!valid) return
+
+  //校验模型输出
+  if (dataForm.value.mmItemOutputList == undefined || dataForm.value.mmItemOutputList.length <= 0) {
+    message.error("模型输出不为空")
+    return
+  }
+
+  let flag = false
+  dataForm.value.mmItemOutputList.forEach(e => {
+    if (e.resultstr == undefined || e.resultstr === '' || e.resultType == undefined || e.resultType === '' || e.pointid == undefined || e.pointid === '' || (e.resultType === 2 && (e.resultIndex == undefined || e.resultIndex === ''))) {
+      message.error("模型输出数据异常")
+      flag = true
+    }
+  })
+  if (flag) return
+
   // 提交请求
   formLoading.value = true
   try {
@@ -608,6 +663,7 @@
 
 const getInfo = async (id) => {
   dataForm.value = await MmPredictItem.getMmPredictItem(id)
+  dataForm.value.itemtypename = itemTypeMap[dataForm.value.mmPredictItem.itemtypeid]
   expressionList.value = []
   if (dataForm.value.mmPredictMergeItem && dataForm.value.mmPredictMergeItem.expression) {
     let expression = dataForm.value.mmPredictMergeItem.expression
@@ -710,12 +766,12 @@
   dataForm.value.itemtypename = itemTypeMap[value]
 }
 
-function changeModelparamtype(value, row) {
+function changeModelparamtype(row) {
   row.modelparamid = ''
 }
 
-function changeOutputPoint(value) {
-  dataForm.value.mmItemOutput.tagname = pointMap[value]
+function changeOutputPoint(value, row) {
+  row.tagname = pointMap[value]
 }
 
 function deleteExpressionRow(index, rows) {
@@ -742,6 +798,37 @@
   orderRow(rows)
 }
 
+function addItemOutput(list) {
+  list.push({})
+  orderItemOutput(list)
+}
+
+function deleteItemOutput(index: string, rows) {
+  if (!rows || rows.length === 1) {
+    message.error('不能全部删除!')
+    return
+  }
+  rows.splice(index, 1)
+  orderItemOutput(rows)
+}
+
+function orderItemOutput(list) {
+  list.sort((a, b) => a.outputorder - b.outputorder);
+  let outputorder = 1
+  list.forEach(function (value) {
+    value.outputorder = outputorder
+    outputorder++
+  })
+}
+
+function resultTypeChange(value, row) {
+  if (value === 1) {
+    row.resultIndex = undefined
+  } else if (value === 2) {
+    row.resultIndex = 0
+  }
+}
+
 function orderRow(rows) {
   let modelparamorder = 0
   let modelparamportorder = 0
@@ -766,8 +853,7 @@
   dataForm.value.mmPredictModel.trainsamplength = 60
   dataForm.value.mmPredictModel.isonlinetrain = 0
   dataForm.value.mmPredictModel.status = 1
-  dataForm.value.mmItemOutput.outputorder = 1
-  dataForm.value.mmItemOutput.resulttableid = '3cc2b483-3a01-40f7-a419-0c260210d8eb'
+  dataForm.value.mmItemOutputList = []
   expressionList.value = [{
     point: '',
     operator: ''
@@ -805,14 +891,7 @@
       status: 1,
       categoryid: ''
     },
-    mmItemOutput: {
-      id: '',
-      itemid: '',
-      pointid: '',
-      resulttableid: '3cc2b483-3a01-40f7-a419-0c260210d8eb',
-      tagname: '',
-      outputorder: 1
-    },
+    mmItemOutputList: [],
     mmPredictModel: {
       id: '',
       modelno: '',
diff --git a/src/views/model/pre/item/index.vue b/src/views/model/pre/item/index.vue
index e0e8861..ffc12bd 100644
--- a/src/views/model/pre/item/index.vue
+++ b/src/views/model/pre/item/index.vue
@@ -51,18 +51,18 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="编号" align="center" prop="itemno"/>
+      <el-table-column label="编号" align="center" min-width="150" prop="itemno"/>
       <el-table-column label="预测项名" header-align="center" align="left" min-width="200" prop="itemname"/>
-      <el-table-column label="类型名称" align="center" prop="itemtypename">
+      <el-table-column label="类型名称" align="center" min-width="120" prop="itemtypename">
         <template #default="scope">
           <el-tag v-if="scope.row.itemtypename === 'NormalItem'" size="small" type="success">{{scope.row.itemtypename}}</el-tag>
           <el-tag v-else size="small" type="primary">{{scope.row.itemtypename}}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="预测长度" align="center" prop="predictlength"/>
+      <el-table-column label="预测长度(min)" align="center" prop="predictlength"/>
       <el-table-column label="粒度" align="center" prop="granularity">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.TIME_GRANULARITY" :value="scope.row.granularity" />
+          <dict-tag :type="DICT_TYPE.PRED_GRANULARITY" :value="scope.row.granularity" />
         </template>
       </el-table-column>
       <el-table-column label="是否融合" align="center" prop="isfuse">
@@ -80,21 +80,29 @@
           <dict-tag :type="DICT_TYPE.COM_IS_INT" :value="scope.row.status" />
         </template>
       </el-table-column>
-      <el-table-column label="数据点名称" align="center" prop="tagname"/>
-      <el-table-column label="存放表" align="center" prop="tablename"/>
-      <el-table-column label="操作" align="center" min-width="110" fixed="right">
+      <el-table-column label="运行时间" min-width="150" align="center" prop="lastTime"/>
+      <el-table-column label="运行状态" align="center" prop="runStatus">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.ITEM_RUN_STATUS" :value="scope.row.runStatus" />
+        </template>
+      </el-table-column>
+      <el-table-column label="运行耗时(s)" align="center" prop="duration"/>
+      <el-table-column label="操作" align="center" min-width="120" fixed="right">
         <template #default="scope">
           <el-button
             link
             type="primary"
+            size="mini"
             @click="openForm('update', scope.row.id, scope.row.itemtypename)"
             v-hasPermi="['model:pre-item:update']"
           >
             编辑
           </el-button>
+          <el-button link size="mini" type="primary" @click="chartHandle(scope.row)">数据</el-button>
           <el-button
             link
             type="danger"
+            size="mini"
             @click="handleDelete(scope.row.id)"
             v-hasPermi="['model:pre-item:delete']"
           >
@@ -115,10 +123,14 @@
   <!-- 表单弹窗:添加/修改 -->
   <MmPredictItemForm ref="formRef" @success="getList"/>
 
+  <!-- 表单弹窗:数据 -->
+  <MmPredictItemChart ref="chartView" @success="getList"/>
+
 </template>
 <script lang="ts" setup>
 import MmPredictItemForm from './MmPredictItemForm.vue'
-import * as MmPredictItem from '@/api/model/pre/predict'
+import MmPredictItemChart from './MmPredictItemChart.vue'
+import * as MmPredictItem from '@/api/model/pre/item'
 import {DICT_TYPE} from "@/utils/dict";
 
 defineOptions({name: 'DataMmPredictItem'})
@@ -171,6 +183,12 @@
   handleQuery()
 }
 
+/** 查看数据操作 */
+const chartView  = ref()
+const chartHandle = (raw: object) => {
+  chartView.value.open(raw)
+}
+
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (type: string, id?: number, itemtypename?: string) => {
diff --git a/src/views/model/pre/result/MmResultTableForm.vue b/src/views/model/pre/result/MmResultTableForm.vue
deleted file mode 100644
index bcb0afa..0000000
--- a/src/views/model/pre/result/MmResultTableForm.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%">
-    <el-form
-      ref="formRef"
-      v-loading="formLoading"
-      :model="formData"
-      :rules="formRules"
-      label-width="120px"
-    >
-      <el-row>
-        <el-col :span="12">
-          <el-form-item label="表名" prop="tablename">
-            <el-input v-model="formData.tablename" placeholder="请输入表名"/>
-          </el-form-item>
-        </el-col>
-      </el-row>
-    </el-form>
-    <template #footer>
-      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
-      <el-button @click="dialogVisible = false">取 消</el-button>
-    </template>
-  </Dialog>
-</template>
-<script lang="ts" setup>
-import * as MmResultTable from '@/api/model/pre/result'
-
-defineOptions({name: 'DataMmResultTableForm'})
-
-const {t} = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-const dialogVisible = ref(false) // 弹窗的是否展示
-const dialogTitle = ref('') // 弹窗的标题
-const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
-const formType = ref('') // 表单的类型:create - 新增;update - 修改
-const formData = ref({
-  id: undefined,
-  tablename: undefined,
-})
-const formRules = reactive({
-  tablename: [{required: true, message: '表名不能为空', trigger: 'blur'}],
-})
-const formRef = ref() // 表单 Ref
-
-/** 打开弹窗 */
-const open = async (type: string, id?: number) => {
-  dialogVisible.value = true
-  dialogTitle.value = t('action.' + type)
-  formType.value = type
-  resetForm()
-  // 修改时,设置数据
-  if (id) {
-    formLoading.value = true
-    try {
-      formData.value = await MmResultTable.getMmResultTable(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-}
-defineExpose({open}) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  if (!formRef) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value as unknown as MmResultTable.MmResultTableVO
-    if (formType.value === 'create') {
-      await MmResultTable.createMmResultTable(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await MmResultTable.updateMmResultTable(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    tablename: undefined,
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/model/pre/result/index.vue b/src/views/model/pre/result/index.vue
deleted file mode 100644
index 271e8d6..0000000
--- a/src/views/model/pre/result/index.vue
+++ /dev/null
@@ -1,149 +0,0 @@
-<template>
-  <!-- 搜索 -->
-  <ContentWrap>
-    <el-form
-      class="-mb-15px"
-      :model="queryParams"
-      ref="queryFormRef"
-      :inline="true"
-      label-width="68px"
-    >
-      <el-form-item label="表名" prop="tablename">
-        <el-input
-          v-model="queryParams.tablename"
-          placeholder="请输入表名"
-          clearable
-          @keyup.enter="handleQuery"
-          class="!w-240px"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery">
-          <Icon icon="ep:search" class="mr-5px" />
-          搜索
-        </el-button>
-        <el-button @click="resetQuery">
-          <Icon icon="ep:refresh" class="mr-5px" />
-          重置
-        </el-button>
-        <el-button
-          type="primary"
-          plain
-          @click="openForm('create')"
-          v-hasPermi="['model:pre-result:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" />
-          新增
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list">
-      <el-table-column label="表名" align="center" prop="tablename" />
-
-      <el-table-column label="操作" align="center" min-width="110" fixed="right">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['model:pre-result:update']"
-          >
-            编辑
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['model:pre-result:delete']"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-  </ContentWrap>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <MmResultTableForm ref="formRef" @success="getList" />
-
-</template>
-<script lang="ts" setup>
-import MmResultTableForm from './MmResultTableForm.vue'
-import * as MmResultTable from '@/api/model/pre/result'
-
-defineOptions({name: 'DataMmResultTable'})
-
-  const message = useMessage() // 消息弹窗
-  const {t} = useI18n() // 国际化
-
-  const loading = ref(true) // 列表的加载中
-  const total = ref(0) // 列表的总页数
-  const list = ref([]) // 列表的数据
-  const queryParams = reactive({
-    pageNo: 1,
-    pageSize: 10,
-    tablename: undefined,
-  })
-  const queryFormRef = ref() // 搜索的表单
-  const exportLoading = ref(false) // 导出的加载中
-
-  /** 查询列表 */
-  const getList = async () => {
-    loading.value = true
-    try {
-      const page = await MmResultTable.getMmResultTablePage(queryParams)
-      list.value = page.list
-      total.value = page.total
-    } finally {
-      loading.value = false
-    }
-  }
-
-  /** 搜索按钮操作 */
-  const handleQuery = () => {
-    queryParams.pageNo = 1
-    getList()
-  }
-
-  /** 重置按钮操作 */
-  const resetQuery = () => {
-    queryFormRef.value.resetFields()
-    handleQuery()
-  }
-
-  /** 添加/修改操作 */
-  const formRef = ref()
-  const openForm = (type: string, id?: number) => {
-    formRef.value.open(type, id)
-  }
-
-  /** 删除按钮操作 */
-  const handleDelete = async (id: number) => {
-    try {
-      // 删除的二次确认
-      await message.delConfirm()
-      // 发起删除
-      await MmResultTable.deleteMmResultTable(id)
-      message.success(t('common.delSuccess'))
-      // 刷新列表
-      await getList()
-    } catch {
-    }
-  }
-
-  /** 初始化 **/
-  onMounted(async () => {
-    await getList()
-  })
-</script>
diff --git a/src/views/model/pre/type/ItemTypeForm.vue b/src/views/model/pre/type/ItemTypeForm.vue
index 5131d27..eb457f4 100644
--- a/src/views/model/pre/type/ItemTypeForm.vue
+++ b/src/views/model/pre/type/ItemTypeForm.vue
@@ -36,7 +36,7 @@
   </Dialog>
 </template>
 <script lang="ts" setup>
-import * as MmItemType from '@/api/model/pre/item'
+import * as MmItemType from '@/api/model/pre/type'
 
 defineOptions({name: 'DataMmItemTypeForm'})
 
diff --git a/src/views/model/pre/type/index.vue b/src/views/model/pre/type/index.vue
index 39d8216..601d7c1 100644
--- a/src/views/model/pre/type/index.vue
+++ b/src/views/model/pre/type/index.vue
@@ -82,7 +82,7 @@
 </template>
 <script lang="ts" setup>
 import MmItemTypeForm from './ItemTypeForm.vue'
-import * as MmItemType from '@/api/model/pre/item'
+import * as MmItemType from '@/api/model/pre/type'
 
 defineOptions({name: 'DataMmItemType'})
 
diff --git a/src/views/model/swagger/index.vue b/src/views/model/wiki/index.vue
similarity index 83%
rename from src/views/model/swagger/index.vue
rename to src/views/model/wiki/index.vue
index 8b09f85..cf19ad3 100644
--- a/src/views/model/swagger/index.vue
+++ b/src/views/model/wiki/index.vue
@@ -6,7 +6,7 @@
 <script lang="ts" setup>
 import * as ConfigApi from '@/api/infra/config'
 
-defineOptions({ name: 'ModelSwagger' })
+defineOptions({ name: 'ModelWiki' })
 
 const loading = ref(true) // 是否加载中
 const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html')
@@ -15,7 +15,7 @@
 /** 初始化 */
 onMounted(async () => {
   try {
-    const data = await ConfigApi.getConfigKey('model.swagger')
+    const data = await ConfigApi.getConfigKey('model.wiki')
     if (data && data.length > 0) {
       src.value = data
     }
diff --git a/types/env.d.ts b/types/env.d.ts
index 40ce343..25cef34 100644
--- a/types/env.d.ts
+++ b/types/env.d.ts
@@ -25,10 +25,12 @@
   readonly VITE_UPLOAD_URL: string
   readonly VITE_API_URL: string
   readonly VITE_BASE_PATH: string
+  readonly VITE_VIDEO_CAMERA_DOMAIN: string
   readonly VITE_DROP_DEBUGGER: string
   readonly VITE_DROP_CONSOLE: string
   readonly VITE_SOURCEMAP: string
   readonly VITE_OUT_DIR: string
+  readonly VITE_STATIC_DIR: string
 }
 
 declare global {

--
Gitblit v1.9.3