From 227117205cbb62e9febf96870d2fbb9ce056eb43 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期三, 06 十一月 2024 14:09:54 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 src/views/data/plan/category/CategoryForm.vue       |  141 ++
 src/views/model/chart/param/index.vue               |  183 +++
 src/api/data/plan/item/index.ts                     |   64 +
 src/views/data/channel/common/tag/TagImportForm.vue |  147 ++
 src/views/data/plan/item/ItemForm.vue               |  177 +++
 src/views/data/point/index.vue                      |    1 
 src/api/model/mpk/chart.ts                          |   26 
 src/views/data/channel/opcda/tag/TagForm.vue        |   10 
 src/views/data/channel/kio/tag/TagForm.vue          |   10 
 src/views/model/mpk/file/MpkRun.vue                 |    2 
 src/views/data/channel/http/api/index.vue           |    6 
 src/api/data/plan/data/index.ts                     |   40 
 src/api/data/channel/modbus/tag.ts                  |   10 
 src/views/data/channel/modbus/tag/index.vue         |  109 ++
 src/views/data/plan/item/ItemChart.vue              |  225 ++++
 src/views/model/chart/index.vue                     |  177 +++
 src/api/data/channel/opcda/tag.ts                   |   10 
 src/views/data/channel/modbus/tag/TagForm.vue       |    6 
 src/views/model/chart/ChartForm.vue                 |  111 ++
 src/views/data/channel/opcda/index.vue              |    6 
 src/views/data/plan/category/index.vue              |  159 +++
 src/api/data/channel/http/tag.ts                    |   10 
 src/views/model/mpk/file/MpkGenerator.vue           |   16 
 src/views/data/plan/data/index.vue                  |  158 +++
 src/views/data/channel/kio/index.vue                |    4 
 src/views/data/plan/item/index.vue                  |  178 +++
 src/api/data/channel/kio/tag.ts                     |   10 
 src/api/data/plan/category/index.ts                 |   50 +
 src/views/data/channel/http/api/tag/index.vue       |  249 +++-
 src/views/data/plan/data/DataSetForm.vue            |  129 ++
 src/api/model/mpk/chartParam.ts                     |   26 
 src/views/data/channel/opcua/tag/TagForm.vue        |   10 
 src/views/model/chart/param/ChartParamForm.vue      |  130 ++
 src/views/data/channel/opcua/tag/index.vue          |   72 +
 src/views/data/channel/modbus/index.vue             |    6 
 src/api/data/channel/opcua/tag.ts                   |   10 
 src/views/data/channel/http/api/tag/TagForm.vue     |   10 
 src/views/model/pre/item/MmPredictItemForm.vue      |    2 
 src/views/data/channel/opcda/tag/index.vue          |  106 +
 src/views/data/channel/kio/tag/index.vue            |   94 +
 src/views/data/point/DaPointChart.vue               |   86 -
 src/views/data/point/DaPointForm.vue                |    2 
 42 files changed, 2,768 insertions(+), 210 deletions(-)

diff --git a/src/api/data/channel/http/tag.ts b/src/api/data/channel/http/tag.ts
index dfead66..281ab32 100644
--- a/src/api/data/channel/http/tag.ts
+++ b/src/api/data/channel/http/tag.ts
@@ -37,3 +37,13 @@
 export const deleteHttpTag = (id: number) => {
   return request.delete({ url: '/data/channel/http/tag/delete?id=' + id })
 }
+
+//导出HttpTag
+export const exportHttpTag = (params) => {
+  return request.download({ url: '/data/channel/http/tag/export', params })
+}
+
+// 下载用户导入模板
+export const importHttpTagTemplate = () => {
+  return request.download({ url: '/data/channel/http/tag/get-import-template' })
+}
diff --git a/src/api/data/channel/kio/tag.ts b/src/api/data/channel/kio/tag.ts
index 32a714e..b1ab25f 100644
--- a/src/api/data/channel/kio/tag.ts
+++ b/src/api/data/channel/kio/tag.ts
@@ -40,3 +40,13 @@
 export const deleteKioTag = (id: number) => {
   return request.delete({ url: '/data/channel/kio/tag/delete?id=' + id })
 }
+
+//导出KioTag
+export const exportKioTag = (params) => {
+  return request.download({ url: '/data/channel/kio/tag/export', params })
+}
+
+// 下载用户导入模板
+export const importKioTagTemplate = () => {
+  return request.download({ url: '/data/channel/kio/tag/get-import-template' })
+}
diff --git a/src/api/data/channel/modbus/tag.ts b/src/api/data/channel/modbus/tag.ts
index a6299df..5f49c61 100644
--- a/src/api/data/channel/modbus/tag.ts
+++ b/src/api/data/channel/modbus/tag.ts
@@ -41,3 +41,13 @@
 export const deleteModBusTag = (id: number) => {
   return request.delete({ url: '/data/channel/modbus/tag/delete?id=' + id })
 }
+
+//导出ModBusTag
+export const exportModBusTag = (params) => {
+  return request.download({ url: '/data/channel/modbus/tag/export', params })
+}
+
+// 下载用户导入模板
+export const importModBusTagTemplate = () => {
+  return request.download({ url: '/data/channel/modbus/tag/get-import-template' })
+}
diff --git a/src/api/data/channel/opcda/tag.ts b/src/api/data/channel/opcda/tag.ts
index 4f90eb1..089f2a8 100644
--- a/src/api/data/channel/opcda/tag.ts
+++ b/src/api/data/channel/opcda/tag.ts
@@ -38,3 +38,13 @@
 export const deleteOpcdaTag = (id: number) => {
   return request.delete({ url: '/data/channel/opcda/tag/delete?id=' + id })
 }
+
+//导出OpcdaTag
+export const exportOpcDaTag = (params) => {
+  return request.download({ url: '/data/channel/opcda/tag/export', params })
+}
+
+// 下载用户导入模板
+export const importOpcDaTagTemplate = () => {
+  return request.download({ url: '/data/channel/opcda/tag/get-import-template' })
+}
diff --git a/src/api/data/channel/opcua/tag.ts b/src/api/data/channel/opcua/tag.ts
index e1373fb..6f21a1d 100644
--- a/src/api/data/channel/opcua/tag.ts
+++ b/src/api/data/channel/opcua/tag.ts
@@ -39,3 +39,13 @@
 export const deleteOpcuaTag = (id: number) => {
   return request.delete({ url: '/data/channel/opcua/tag/delete?id=' + id })
 }
+
+//导出OpcdaTag
+export const exportOpcUaTag = (params) => {
+  return request.download({ url: '/data/channel/opcua/tag/export', params })
+}
+
+// 下载用户导入模板
+export const importOpcUaTagTemplate = () => {
+  return request.download({ url: '/data/channel/opcua/tag/get-import-template' })
+}
diff --git a/src/api/data/plan/category/index.ts b/src/api/data/plan/category/index.ts
new file mode 100644
index 0000000..f8faecd
--- /dev/null
+++ b/src/api/data/plan/category/index.ts
@@ -0,0 +1,50 @@
+import request from '@/config/axios'
+
+export interface IndItemCategoryVO {
+  id: string
+  label: string
+  pid: string
+  sort: number
+}
+
+export interface ItemCategoryReqVO {
+  label?: string
+}
+
+export const defaultProps = {
+  children: 'children',
+  label: 'label',
+  value: 'id',
+  isLeaf: 'leaf',
+  emitPath: false // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值
+}
+
+// 查询列表
+export const getCategoryList = (params) => {
+  return request.get({ url: '/data/plan/category/list', params})
+}
+
+// 查询列表
+export const getCategoryListAllSimple = () => {
+  return request.get({ url: '/data/plan/category/list-all-simple'})
+}
+
+// 查询详情
+export const getCategory = (id: number) => {
+  return request.get({ url: '/data/plan/category/get?id=' + id})
+}
+
+// 新增
+export const createCategory = (data: ScheduleModelVO) => {
+  return request.post({ url: '/data/plan/category/create', data })
+}
+
+// 修改
+export const updateCategory = (data: ScheduleModelVO) => {
+  return request.put({ url: '/data/plan/category/update', data })
+}
+
+// 删除
+export const deleteCategory = (id: number) => {
+  return request.delete({ url: '/data/plan/category/delete?id=' + id })
+}
diff --git a/src/api/data/plan/data/index.ts b/src/api/data/plan/data/index.ts
new file mode 100644
index 0000000..f04478a
--- /dev/null
+++ b/src/api/data/plan/data/index.ts
@@ -0,0 +1,40 @@
+import request from '@/config/axios'
+
+export type DataSetVO = {
+  id: number | undefined
+  name: string
+  dataSource: string
+  querySql: string
+  remark: string
+  sort: number
+}
+
+// 查询列表
+export const getDataSetList = () => {
+  return request.get({ url: '/data/plan/data-set/list-all-simple' })
+}
+
+// 查询列表
+export const getDataSetPage = (params: PageParam) => {
+  return request.get({ url: '/data/plan/data-set/page', params })
+}
+
+// 查询详情
+export const getDataSet = (id: number) => {
+  return request.get({ url: '/data/plan/data-set/get?id=' + id })
+}
+
+// 新增
+export const createDataSet = (data: DataSetVO) => {
+  return request.post({ url: '/data/plan/data-set/create', data })
+}
+
+// 修改
+export const updateDataSet = (data: DataSetVO) => {
+  return request.put({ url: '/data/plan/data-set/update', data })
+}
+
+// 删除
+export const deleteDataSet = (id: number) => {
+  return request.delete({ url: '/data/plan/data-set/delete?id=' + id })
+}
diff --git a/src/api/data/plan/item/index.ts b/src/api/data/plan/item/index.ts
new file mode 100644
index 0000000..e42e999
--- /dev/null
+++ b/src/api/data/plan/item/index.ts
@@ -0,0 +1,64 @@
+import request from '@/config/axios'
+
+export type ItemVO = {
+  id: string | undefined
+  itemNo: string
+  itemName: string
+  itemCategory: string
+  timeGranularity: string
+  dataSource: string
+  remark: string
+  status: string
+}
+
+export type PageParam = {
+  itemNo: string
+  itemName: string
+  itemCategory: string
+}
+
+export interface PlanChartReqVO {
+  itemNos?: [],
+  start?: Date,
+  end?: Date,
+}
+
+// 查询列表
+export const getItemPage = (params: PageParam) => {
+  return request.get({ url: '/data/plan-item/page', params })
+}
+
+// 查询详情
+export const getItem = (id: string) => {
+  return request.get({ url: '/data/plan-item/get?id=' + id })
+}
+
+// 新增
+export const createItem = (data: ItemVO) => {
+  return request.post({ url: '/data/plan-item/create', data })
+}
+
+// 修改
+export const updateItem = (data: ItemVO) => {
+  return request.put({ url: '/data/plan-item/update', data })
+}
+
+// 删除
+export const deleteItem = (id: number) => {
+  return request.delete({ url: '/data/plan-item/delete?id=' + id })
+}
+
+//获取下拉集合
+export const getItemList = (params: PageParam) => {
+  return request.get({ url: '/data/plan-item/getList', params})
+}
+
+// 查询Plan图表
+export const getPlanChart = (data: PlanChartReqVO) => {
+  return request.post({ url: '/data/api/query-plans/chart', data })
+}
+
+// 导出Plan值
+export const exportPlanValue = (data: PlanChartReqVO) => {
+  return request.post({ url: '/data/api/export-plan/history-value', data })
+}
diff --git a/src/api/model/mpk/chart.ts b/src/api/model/mpk/chart.ts
new file mode 100644
index 0000000..64daacc
--- /dev/null
+++ b/src/api/model/mpk/chart.ts
@@ -0,0 +1,26 @@
+import request from '@/config/axios'
+
+// 查询列表
+export const getPage = (params) => {
+  return request.get({ url: '/model/chart/page', params})
+}
+
+// 获得
+export const get = (id) => {
+  return request.get({ url: '/model/chart/get?id=' + id })
+}
+
+// 新增
+export const create = (data) => {
+  return request.post({ url: '/model/chart/create', data })
+}
+
+// 修改
+export const update = (data) => {
+  return request.put({ url: '/model/chart/update', data })
+}
+
+// 删除
+export const del = (id) => {
+  return request.delete({ url: '/model/chart/delete?id=' + id })
+}
diff --git a/src/api/model/mpk/chartParam.ts b/src/api/model/mpk/chartParam.ts
new file mode 100644
index 0000000..7a87452
--- /dev/null
+++ b/src/api/model/mpk/chartParam.ts
@@ -0,0 +1,26 @@
+import request from '@/config/axios'
+
+// 查询列表
+export const getPage = (params) => {
+  return request.get({ url: '/model/chart/param/page', params})
+}
+
+// 获得
+export const get = (id) => {
+  return request.get({ url: '/model/chart/param/get?id=' + id })
+}
+
+// 新增
+export const create = (data) => {
+  return request.post({ url: '/model/chart/param/create', data })
+}
+
+// 修改
+export const update = (data) => {
+  return request.put({ url: '/model/chart/param/update', data })
+}
+
+// 删除
+export const del = (id) => {
+  return request.delete({ url: '/model/chart/param/delete?id=' + id })
+}
diff --git a/src/views/data/channel/common/tag/TagImportForm.vue b/src/views/data/channel/common/tag/TagImportForm.vue
new file mode 100644
index 0000000..20baf6c
--- /dev/null
+++ b/src/views/data/channel/common/tag/TagImportForm.vue
@@ -0,0 +1,147 @@
+<template>
+  <Dialog v-model="dialogVisible" title="Tag导入" width="400">
+    <el-upload
+      ref="uploadRef"
+      v-model:file-list="fileList"
+      :action="importUrl + '?updateSupport=' + updateSupport + '&device=' + parameter"
+      :auto-upload="false"
+      :disabled="formLoading"
+      :headers="uploadHeaders"
+      :limit="1"
+      :on-error="submitFormError"
+      :on-exceed="handleExceed"
+      :on-success="submitFormSuccess"
+      accept=".xlsx, .xls"
+      drag
+    >
+      <Icon icon="ep:upload" />
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <template #tip>
+        <div class="el-upload__tip text-center">
+          <div class="el-upload__tip">
+            <el-checkbox v-model="updateSupport" />
+            是否更新已经存在的Tag数据
+          </div>
+          <span>仅允许导入 xls、xlsx 格式文件。</span>
+          <el-link
+            :underline="false"
+            style="font-size: 12px; vertical-align: baseline"
+            type="primary"
+            @click="importTemplate"
+          >
+            下载模板
+          </el-link>
+        </div>
+      </template>
+    </el-upload>
+    <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 { getAccessToken, getTenantId } from '@/utils/auth'
+import download from '@/utils/download'
+import * as ModBusTagApi from "@/api/data/channel/modbus/tag"
+
+defineOptions({ name: 'PointImportForm' })
+
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const uploadRef = ref()
+const importUrl = ref()
+const uploadHeaders = ref() // 上传 Header 头
+const fileList = ref([]) // 文件列表
+const updateSupport = ref(0) // 是否更新已经存在的测点数据
+const decName = ref()
+const typeName = ref()
+const parameter = ref()
+let importTemplateApi = reactive()
+/** 打开弹窗 */
+const open = (name?: string, url?:string, tagApi?:Object, type?:string, params?:string) => {
+  importTemplateApi = tagApi
+  dialogVisible.value = true
+  updateSupport.value = 0
+  fileList.value = []
+  decName.value = name
+  typeName.value = type
+  parameter.value = params
+  importUrl.value =  import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + url
+  resetForm()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  if (fileList.value.length == 0) {
+    message.error('请上传文件')
+    return
+  }
+  // 提交请求
+  uploadHeaders.value = {
+    Authorization: 'Bearer ' + getAccessToken(),
+    'tenant-id': getTenantId()
+  }
+  formLoading.value = true
+  uploadRef.value!.submit()
+}
+
+/** 文件上传成功 */
+const emits = defineEmits(['success'])
+const submitFormSuccess = (response: any) => {
+  if (response.code !== 0) {
+    message.error(response.msg)
+    formLoading.value = false
+    return
+  }
+  // 拼接提示语
+  const data = response.data
+  let text = '上传成功数量:' + data.createTagNames.length + ';'
+  for (let tagName of data.createTagNames) {
+    text += '< ' + tagName + ' >'
+  }
+  text += '更新成功数量:' + data.updateTagNames.length + ';'
+  for (const tagName of data.updateTagNames) {
+    text += '< ' + tagName + ' >'
+  }
+  text += '更新失败数量:' + Object.keys(data.failureTagNames).length + ';'
+  for (const tagName in data.failureTagNames) {
+    text += '< ' + tagName + ': ' + data.failureTagNames[tagName] + ' >'
+  }
+  message.alert(text)
+  formLoading.value = false
+  dialogVisible.value = false
+  // 发送操作成功的事件
+  emits('success')
+}
+
+/** 上传错误提示 */
+const submitFormError = (): void => {
+  message.error('上传失败,请您重新上传!')
+  formLoading.value = false
+}
+
+/** 重置表单 */
+const resetForm = async (): Promise<void> => {
+  // 重置上传状态和文件
+  formLoading.value = false
+  await nextTick()
+  uploadRef.value?.clearFiles()
+}
+
+/** 文件数超出提示 */
+const handleExceed = (): void => {
+  message.error('最多只能上传一个文件!')
+}
+
+/** 下载模板操作 */
+const importTemplate = async () => {
+  const res = await importTemplateApi
+  const excelName = typeName.value + '_' + decName.value + '_' + 'Tag导入模板.xlsx'
+  download.excel(res, excelName)
+}
+</script>
diff --git a/src/views/data/channel/http/api/index.vue b/src/views/data/channel/http/api/index.vue
index f3a7c9d..e3b06be 100644
--- a/src/views/data/channel/http/api/index.vue
+++ b/src/views/data/channel/http/api/index.vue
@@ -61,7 +61,7 @@
           <el-button
             link
             type="primary"
-            @click="openTagList(scope.row.id)"
+            @click="openTagList(scope.row.id, scope.row.name)"
             v-hasPermi="['data:channel-http:update']"
           >
             TAG
@@ -146,8 +146,8 @@
 
 /** TAG操作 */
 const tagRef = ref()
-const openTagList = (id?: string) => {
-  tagRef.value.open(id)
+const openTagList = (id?: string,name?: string) => {
+  tagRef.value.open(id,name)
 }
 
 /** 删除按钮操作 */
diff --git a/src/views/data/channel/http/api/tag/TagForm.vue b/src/views/data/channel/http/api/tag/TagForm.vue
index 210c944..5d61c53 100644
--- a/src/views/data/channel/http/api/tag/TagForm.vue
+++ b/src/views/data/channel/http/api/tag/TagForm.vue
@@ -31,7 +31,7 @@
           <el-form-item label="是否启用" prop="enabled">
             <el-select v-model="formData.enabled" placeholder="请选择">
               <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)"
+                v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
@@ -56,8 +56,8 @@
 </template>
 <script lang="ts" setup>
 import * as HttpTagApi from '@/api/data/channel/http/tag'
-import { CommonEnabledBool } from '@/utils/constants'
-import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
+import {CommonEnabled} from '@/utils/constants'
+import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
 
 defineOptions({name: 'HttpTagForm'})
 
@@ -73,7 +73,7 @@
   tagName: undefined,
   dataType: undefined,
   tagDesc: '',
-  enabled: CommonEnabledBool.ENABLE,
+  enabled: CommonEnabled.ENABLE,
 })
 const formRules = reactive({
   tagName: [{required: true, message: 'Tag名称不能为空', trigger: 'blur'}],
@@ -136,7 +136,7 @@
     tagName: undefined,
     dataType: undefined,
     tagDesc: '',
-    enabled: CommonEnabledBool.ENABLE,
+    enabled: CommonEnabled.ENABLE,
   }
   formRef.value?.resetFields()
 }
diff --git a/src/views/data/channel/http/api/tag/index.vue b/src/views/data/channel/http/api/tag/index.vue
index 4fbca5b..82cf55a 100644
--- a/src/views/data/channel/http/api/tag/index.vue
+++ b/src/views/data/channel/http/api/tag/index.vue
@@ -2,7 +2,7 @@
   <el-drawer
     v-model="drawer"
     size="50%"
-    title="Kio Tag"
+    title="Http Tag"
     :direction="direction"
     :before-close="handleClose"
   >
@@ -26,22 +26,45 @@
         </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
             type="primary"
             plain
             @click="openForm('create')"
-            v-hasPermi="['data:channel-kio:create']"
+            v-hasPermi="['data:channel-http:create']"
           >
-            <Icon icon="ep:plus" class="mr-5px" />
+            <Icon icon="ep:plus" class="mr-5px"/>
             新增
           </el-button>
+          <el-button
+            type="warning"
+            plain
+            @click="handleImport"
+            v-hasPermi="['data:channel-http-tag:import']">
+            <Icon icon="ep:upload"/>
+            导入
+          </el-button>
+          <el-button
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['data:channel-http-tag:export']">
+            <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>
@@ -75,8 +98,27 @@
           align="center"
         >
           <template #default="scope">
-            <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag>
+            <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag>
             <el-tag v-else size="small" type="danger">否</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="dataValue"
+          label="数据值"
+          header-align="center"
+          align="center"
+          :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
+        />
+        <el-table-column
+          prop="quality"
+          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">
@@ -85,7 +127,7 @@
               link
               type="primary"
               @click="openForm('update', scope.row.id)"
-              v-hasPermi="['data:channel-kio:update']"
+              v-hasPermi="['data:channel-http:update']"
             >
               编辑
             </el-button>
@@ -93,7 +135,7 @@
               link
               type="danger"
               @click="handleDelete(scope.row.id)"
-              v-hasPermi="['data:channel-kio:delete']"
+              v-hasPermi="['data:channel-http:delete']"
             >
               删除
             </el-button>
@@ -109,97 +151,128 @@
       />
     </ContentWrap>
     <!-- 表单弹窗:添加/修改 -->
-    <TagForm ref="formRef" @success="getList" />
+    <TagForm ref="formRef" @success="getList"/>
+    <TagImportForm ref="importFormRef" @success="getList"/>
   </el-drawer>
 </template>
 <script lang="ts" setup>
-import type { DrawerProps } from 'element-plus'
-import * as HttpTagApi from "@/api/data/channel/http/tag";
-import TagForm from './TagForm.vue'
+  import type {DrawerProps} from 'element-plus'
+  import * as HttpTagApi from "@/api/data/channel/http/tag";
+  import TagForm from './TagForm.vue'
+  import download from "@/utils/download";
+  import {ref} from "vue";
+  import {onBeforeUnmount, onMounted} from "vue";
+  import TagImportForm from '../../../common/tag/TagImportForm.vue'
 
-defineOptions({name: 'HttpTag'})
+  defineOptions({name: 'HttpTag'})
 
-const message = useMessage() // 消息弹窗
-const {t} = useI18n() // 国际化
+  const message = useMessage() // 消息弹窗
+  const {t} = useI18n() // 国际化
 
-const drawer = ref(false)
-const direction = ref<DrawerProps['direction']>('rtl')
-const loading = ref(true) // 列表的加载中
-const total = ref(0) // 列表的总页数
-const list = ref([]) // 列表的数据
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  apiId: undefined,
-  tagName: undefined
-})
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
+  const drawer = ref(false)
+  const direction = ref<DrawerProps['direction']>('rtl')
+  const loading = ref(true) // 列表的加载中
+  const total = ref(0) // 列表的总页数
+  const list = ref([]) // 列表的数据
+  const queryParams = reactive({
+    pageNo: 1,
+    pageSize: 10,
+    apiId: undefined,
+    tagName: undefined,
+    httpName: undefined,
+    currentValue:false,
+  })
+  const queryFormRef = ref() // 搜索的表单
+  const exportLoading = ref(false) // 导出的加载中
 
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const page = await HttpTagApi.getHttpTagPage(queryParams)
-    list.value = page.list
-    total.value = page.total
-  } finally {
-    loading.value = false
+  /** 查询列表 */
+  const getList = async () => {
+    loading.value = true
+    try {
+      const page = await HttpTagApi.getHttpTagPage(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, queryParams.apiId)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await HttpTagApi.deleteHttpTag(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {
-  }
-}
-
-/** 打开弹窗 */
-const open = async (apiId?: string) => {
-  resetForm()
-  drawer.value = true
-  queryParams.apiId = apiId
-  if (apiId) {
+  /** 搜索按钮操作 */
+  const handleQuery = () => {
+    queryParams.pageNo = 1
     getList()
   }
-}
-defineExpose({open}) // 提供 open 方法,用于打开弹窗
 
-/** 重置表单 */
-const resetForm = () => {
-  queryParams.pageNo = 1
-  queryParams.pageSize = 10
-  queryParams.apiId = ''
-  queryParams.tagName = ''
-}
+  /** 重置按钮操作 */
+  const resetQuery = () => {
+    queryFormRef.value.resetFields()
+    handleQuery()
+  }
 
-const handleClose = (done: () => void) => {
-  drawer.value = false
-}
+  /** 添加/修改操作 */
+  const formRef = ref()
+  const openForm = (type: string, id?: number) => {
+    formRef.value.open(type, id, queryParams.apiId)
+  }
+
+  /** 删除按钮操作 */
+  const handleDelete = async (id: number) => {
+    try {
+      // 删除的二次确认
+      await message.delConfirm()
+      // 发起删除
+      await HttpTagApi.deleteHttpTag(id)
+      message.success(t('common.delSuccess'))
+      // 刷新列表
+      await getList()
+    } catch {
+    }
+  }
+
+  /** 打开弹窗 */
+  const open = async (apiId?: string, name?: string) => {
+    resetForm()
+    drawer.value = true
+    queryParams.apiId = apiId
+    queryParams.httpName = name
+    if (apiId) {
+      getList()
+    }
+  }
+  defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+  /** 重置表单 */
+  const resetForm = () => {
+    queryParams.pageNo = 1
+    queryParams.pageSize = 10
+    queryParams.apiId = ''
+    queryParams.tagName = ''
+  }
+
+  const handleClose = (done: () => void) => {
+    drawer.value = false
+  }
+
+  /** tag导入 */
+  const importFormRef = ref()
+  const handleImport = () => {
+    if (queryParams.apiId) {
+      importFormRef.value.open(queryParams.httpName, '/data/channel/http/tag/import', HttpTagApi.importHttpTagTemplate(), 'Http', queryParams.apiId)
+    }
+  }
+
+  /** 导出按钮操作 */
+  const handleExport = async () => {
+    try {
+      // 导出的二次确认
+      await message.exportConfirm()
+      // 发起导出
+      exportLoading.value = true
+      const data = await HttpTagApi.exportHttpTag(queryParams)
+      download.excel(data, 'Http_' + queryParams.httpName + '_Tag列表.xlsx')
+    } catch {
+    } finally {
+      exportLoading.value = false
+    }
+  }
 </script>
diff --git a/src/views/data/channel/kio/index.vue b/src/views/data/channel/kio/index.vue
index dc9be25..ded1abb 100644
--- a/src/views/data/channel/kio/index.vue
+++ b/src/views/data/channel/kio/index.vue
@@ -145,8 +145,8 @@
 
   /** TAG操作 */
   const tagRef = ref()
-  const openTagList = (name?: string) => {
-    tagRef.value.open(name)
+  const openTagList = (id?: string,name?: string) => {
+    tagRef.value.open(id,name)
   }
 
   /** 删除按钮操作 */
diff --git a/src/views/data/channel/kio/tag/TagForm.vue b/src/views/data/channel/kio/tag/TagForm.vue
index ff2fc72..ad6da51 100644
--- a/src/views/data/channel/kio/tag/TagForm.vue
+++ b/src/views/data/channel/kio/tag/TagForm.vue
@@ -36,7 +36,7 @@
           <el-form-item label="是否启用" prop="enabled">
             <el-select v-model="formData.enabled" placeholder="请选择">
               <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)"
+                v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
@@ -61,9 +61,9 @@
 </template>
 <script lang="ts" setup>
   import * as KioTagApi from '@/api/data/channel/kio/tag'
-  import { CommonEnabledBool } from '@/utils/constants'
+  import {CommonEnabled} from '@/utils/constants'
   import {isPositiveInteger} from '@/utils/validate'
-  import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
+  import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
 
   defineOptions({name: 'KioTagForm'})
 
@@ -79,7 +79,7 @@
     dataType: undefined,
     tagId: undefined,
     tagDesc: '',
-    enabled: CommonEnabledBool.ENABLE,
+    enabled: CommonEnabled.ENABLE,
     device: undefined,
     samplingRate: undefined
 
@@ -152,7 +152,7 @@
       dataType: undefined,
       tagId: undefined,
       tagDesc: '',
-      enabled: CommonEnabledBool.ENABLE,
+      enabled: CommonEnabled.ENABLE,
       device: undefined,
       samplingRate: undefined
     }
diff --git a/src/views/data/channel/kio/tag/index.vue b/src/views/data/channel/kio/tag/index.vue
index 990c389..d334fb4 100644
--- a/src/views/data/channel/kio/tag/index.vue
+++ b/src/views/data/channel/kio/tag/index.vue
@@ -42,6 +42,27 @@
             <Icon icon="ep:plus" class="mr-5px" />
             新增
           </el-button>
+          <el-button
+            type="warning"
+            plain
+            @click="handleImport"
+            v-hasPermi="['data:channel-kio-tag:import']">
+            <Icon icon="ep:upload" /> 导入
+          </el-button>
+          <el-button
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['data:channel-kio-tag:export']">
+            <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>
@@ -75,8 +96,26 @@
           align="center"
         >
           <template #default="scope">
-            <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag>
+            <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag>
             <el-tag v-else size="small" type="danger">否</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="dataValue"
+          label="数据值"
+          header-align="center"
+          align="center"
+          :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
+        />
+        <el-table-column
+          prop="quality"
+          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">
@@ -110,12 +149,17 @@
     </ContentWrap>
     <!-- 表单弹窗:添加/修改 -->
     <TagForm ref="formRef" @success="getList" />
+    <TagImportForm ref="importFormRef" @success="getList" />
   </el-drawer>
 </template>
 <script lang="ts" setup>
   import type { DrawerProps } from 'element-plus'
   import * as KioTagApi from "@/api/data/channel/kio/tag";
   import TagForm from './TagForm.vue'
+  import download from "@/utils/download";
+  import TagImportForm from '../../common/tag/TagImportForm.vue'
+  import {ref, onBeforeUnmount, onMounted} from "vue";
+  import * as HttpTagApi from "@/api/data/channel/http/tag";
 
   defineOptions({name: 'KioTag'})
 
@@ -131,6 +175,8 @@
     pageNo: 1,
     pageSize: 10,
     device: undefined,
+    deviceId: undefined,
+    currentValue:false,
     tagName: undefined
   })
   const queryFormRef = ref() // 搜索的表单
@@ -181,10 +227,11 @@
   }
 
   /** 打开弹窗 */
-  const open = async (device?: string) => {
+  const open = async (deviceId?: string,device?: string) => {
     resetForm()
     drawer.value = true
     queryParams.device = device
+    queryParams.deviceId = deviceId
     if (device) {
       getList()
     }
@@ -202,4 +249,47 @@
   const handleClose = (done: () => void) => {
     drawer.value = false
   }
+
+  /** tag导入 */
+  const importFormRef = ref()
+  const handleImport = () => {
+    if(queryParams.device){
+      importFormRef.value.open(queryParams.device, '/data/channel/kio/tag/import',KioTagApi.importKioTagTemplate(), 'Kio', queryParams.device)
+    }
+  }
+
+  /** 导出按钮操作 */
+  const handleExport = async () => {
+    try {
+      // 导出的二次确认
+      await message.exportConfirm()
+      // 发起导出
+      exportLoading.value = true
+      const data = await KioTagApi.exportKioTag(queryParams)
+      download.excel(data, 'Kio_' + queryParams.device + '_Tag列表.xlsx')
+    } catch {
+    } finally {
+      exportLoading.value = false
+    }
+  }
+
+  let intervalId;
+
+  onMounted(async () => {
+    // 创建定时器
+    intervalId = setInterval(async () => {
+      if(queryParams.currentValue){
+        const page = await KioTagApi.getKioTagPage(queryParams)
+        list.value = page.list
+        total.value = page.total
+      }
+    }, 10000);
+  });
+
+  // 在组件卸载时清除定时器
+  onBeforeUnmount(() => {
+    if (intervalId) {
+      clearInterval(intervalId);
+    }
+  });
 </script>
diff --git a/src/views/data/channel/modbus/index.vue b/src/views/data/channel/modbus/index.vue
index 58b0942..c97cada 100644
--- a/src/views/data/channel/modbus/index.vue
+++ b/src/views/data/channel/modbus/index.vue
@@ -75,7 +75,7 @@
           <el-button
             link
             type="primary"
-            @click="openTagList(scope.row.name)"
+            @click="openTagList(scope.row.id,scope.row.name)"
             v-hasPermi="['data:channel-modbus:update']"
           >
             TAG
@@ -161,8 +161,8 @@
 
   /** TAG操作 */
   const tagRef = ref()
-  const openTagList = (name?: string) => {
-    tagRef.value.open(name)
+  const openTagList = (id?: string,name?: string) => {
+    tagRef.value.open(id,name)
   }
 
   /** 删除按钮操作 */
diff --git a/src/views/data/channel/modbus/tag/TagForm.vue b/src/views/data/channel/modbus/tag/TagForm.vue
index a8ec15d..d248a96 100644
--- a/src/views/data/channel/modbus/tag/TagForm.vue
+++ b/src/views/data/channel/modbus/tag/TagForm.vue
@@ -55,7 +55,7 @@
           <el-form-item label="是否启用" prop="enabled">
             <el-select v-model="formData.enabled" placeholder="请选择">
               <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)"
+                v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
@@ -82,7 +82,7 @@
   import * as ModBusTagApi from '@/api/data/channel/modbus/tag'
   import { CommonEnabled } from '@/utils/constants'
   import {isPositiveInteger} from '@/utils/validate'
-  import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
+  import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
 
   defineOptions({name: 'ModBusTagForm'})
 
@@ -98,7 +98,7 @@
     dataType: undefined,
     enabled: CommonEnabled.ENABLE,
     format: undefined,
-    device: '1',
+    device: '',
     address: '',
     samplingRate: undefined,
     tagDesc: '',
diff --git a/src/views/data/channel/modbus/tag/index.vue b/src/views/data/channel/modbus/tag/index.vue
index 335b5e1..7739240 100644
--- a/src/views/data/channel/modbus/tag/index.vue
+++ b/src/views/data/channel/modbus/tag/index.vue
@@ -35,22 +35,44 @@
         </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
             type="primary"
             plain
             @click="openForm('create')"
-            v-hasPermi="['data:channel-modbus:create']"
-          >
-            <Icon icon="ep:plus" class="mr-5px" />
+            v-hasPermi="['data:channel-modbus:create']">
+            <Icon icon="ep:plus" class="mr-5px"/>
             新增
           </el-button>
+          <el-button
+            type="warning"
+            plain
+            @click="handleImport"
+            v-hasPermi="['data:channel-modbus-tag:import']">
+            <Icon icon="ep:upload"/>
+            导入
+          </el-button>
+          <el-button
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['data:channel-modbus-tag:export']">
+            <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>
@@ -102,8 +124,26 @@
           align="center"
         >
           <template #default="scope">
-            <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag>
+            <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag>
             <el-tag v-else size="small" type="danger">否</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="dataValue"
+          label="数据值"
+          header-align="center"
+          align="center"
+          :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
+        />
+        <el-table-column
+          prop="quality"
+          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">
@@ -136,13 +176,19 @@
       />
     </ContentWrap>
     <!-- 表单弹窗:添加/修改 -->
-    <TagForm ref="formRef" @success="getList" />
+    <TagForm ref="formRef" @success="getList"/>
+    <TagImportForm ref="importFormRef" @success="getList"/>
   </el-drawer>
 </template>
 <script lang="ts" setup>
-  import type { DrawerProps } from 'element-plus'
+  import type {DrawerProps} from 'element-plus'
   import * as ModBusTagApi from "@/api/data/channel/modbus/tag";
   import TagForm from './TagForm.vue'
+  import download from "@/utils/download";
+  import {ref} from "vue";
+  import TagImportForm from '../../common/tag/TagImportForm.vue'
+  import {onBeforeUnmount, onMounted} from "vue";
+  import * as HttpTagApi from "@/api/data/channel/http/tag";
 
   defineOptions({name: 'ModBusTag'})
 
@@ -157,8 +203,10 @@
   const queryParams = reactive({
     pageNo: 1,
     pageSize: 10,
+    deviceId: undefined,
     device: undefined,
     tagName: undefined,
+    currentValue:false,
     address: undefined
   })
   const queryFormRef = ref() // 搜索的表单
@@ -209,10 +257,11 @@
   }
 
   /** 打开弹窗 */
-  const open = async (device?: string) => {
+  const open = async (deviceId?: string,device?: string) => {
     resetForm()
     drawer.value = true
     queryParams.device = device
+    queryParams.deviceId = deviceId
     if (device) {
       getList()
     }
@@ -231,4 +280,46 @@
   const handleClose = (done: () => void) => {
     drawer.value = false
   }
+
+  /** tag导入 */
+  const importFormRef = ref()
+  const handleImport = () => {
+    if (queryParams.device) {
+      importFormRef.value.open(queryParams.device, '/data/channel/modbus/tag/import', ModBusTagApi.importModBusTagTemplate(), 'ModBus', queryParams.device)
+    }
+  }
+
+  /** 导出按钮操作 */
+  const handleExport = async () => {
+    try {
+      // 导出的二次确认
+      await message.exportConfirm()
+      // 发起导出
+      exportLoading.value = true
+      const data = await ModBusTagApi.exportModBusTag(queryParams)
+      download.excel(data, 'ModBus_' + queryParams.device + '_Tag列表.xlsx')
+    } catch {
+    } finally {
+      exportLoading.value = false
+    }
+  }
+  let intervalId;
+
+  onMounted(async () => {
+    // 创建定时器
+    intervalId = setInterval(async () => {
+      if(queryParams.currentValue){
+        const page = await ModBusTagApi.getModBusTagPage(queryParams)
+        list.value = page.list
+        total.value = page.total
+      }
+    }, 10000);
+  });
+
+  // 在组件卸载时清除定时器
+  onBeforeUnmount(() => {
+    if (intervalId) {
+      clearInterval(intervalId);
+    }
+  });
 </script>
diff --git a/src/views/data/channel/opcda/index.vue b/src/views/data/channel/opcda/index.vue
index b538779..6481531 100644
--- a/src/views/data/channel/opcda/index.vue
+++ b/src/views/data/channel/opcda/index.vue
@@ -60,7 +60,7 @@
           <el-button
             link
             type="primary"
-            @click="openTagList(scope.row.id)"
+            @click="openTagList(scope.row.id,scope.row.serverName)"
             v-hasPermi="['data:channel-opcda:update']"
           >
             TAG
@@ -145,8 +145,8 @@
 
   /** TAG操作 */
   const tagRef = ref()
-  const openTagList = (id?: string) => {
-    tagRef.value.open(id)
+  const openTagList = (id?: string,serverName?:string) => {
+    tagRef.value.open(id,serverName)
   }
 
   /** 删除按钮操作 */
diff --git a/src/views/data/channel/opcda/tag/TagForm.vue b/src/views/data/channel/opcda/tag/TagForm.vue
index 9f4b6ab..813b9ca 100644
--- a/src/views/data/channel/opcda/tag/TagForm.vue
+++ b/src/views/data/channel/opcda/tag/TagForm.vue
@@ -36,7 +36,7 @@
           <el-form-item label="是否启用" prop="enabled">
             <el-select v-model="formData.enabled" placeholder="请选择">
               <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)"
+                v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
@@ -54,8 +54,8 @@
 </template>
 <script lang="ts" setup>
   import * as OpcdaTagApi from '@/api/data/channel/opcda/tag'
-  import { CommonEnabledBool } from '@/utils/constants'
-  import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
+  import {CommonEnabled} from '@/utils/constants'
+  import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
 
   defineOptions({name: 'OpcdaTagForm'})
 
@@ -70,7 +70,7 @@
     serverId: undefined,
     tagName: undefined,
     dataType: undefined,
-    enabled: CommonEnabledBool.ENABLE,
+    enabled: CommonEnabled.ENABLE,
     itemId: undefined
   })
   const formRules = reactive({
@@ -133,7 +133,7 @@
       serverId: undefined,
       tagName: undefined,
       dataType: undefined,
-      enabled: CommonEnabledBool.ENABLE,
+      enabled: CommonEnabled.ENABLE,
       itemId: undefined
     }
     formRef.value?.resetFields()
diff --git a/src/views/data/channel/opcda/tag/index.vue b/src/views/data/channel/opcda/tag/index.vue
index 98f57da..17d4011 100644
--- a/src/views/data/channel/opcda/tag/index.vue
+++ b/src/views/data/channel/opcda/tag/index.vue
@@ -2,7 +2,7 @@
   <el-drawer
     v-model="drawer"
     size="50%"
-    title="ModBus Tag"
+    title="OpcDA Tag"
     :direction="direction"
     :before-close="handleClose"
   >
@@ -37,11 +37,32 @@
             type="primary"
             plain
             @click="openForm('create')"
-            v-hasPermi="['data:channel-modbus:create']"
+            v-hasPermi="['data:channel-opcda:create']"
           >
             <Icon icon="ep:plus" class="mr-5px" />
             新增
           </el-button>
+          <el-button
+            type="warning"
+            plain
+            @click="handleImport"
+            v-hasPermi="['data:channel-opcda-tag:import']">
+            <Icon icon="ep:upload" /> 导入
+          </el-button>
+          <el-button
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['data:channel-opcda-tag:export']">
+            <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>
@@ -68,8 +89,26 @@
           align="center"
         >
           <template #default="scope">
-            <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag>
+            <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag>
             <el-tag v-else size="small" type="danger">否</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="dataValue"
+          label="数据值"
+          header-align="center"
+          align="center"
+          :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
+        />
+        <el-table-column
+          prop="quality"
+          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">
@@ -103,12 +142,18 @@
     </ContentWrap>
     <!-- 表单弹窗:添加/修改 -->
     <TagForm ref="formRef" @success="getList" />
+    <TagImportForm ref="importFormRef" @success="getList" />
   </el-drawer>
 </template>
 <script lang="ts" setup>
   import type { DrawerProps } from 'element-plus'
-  import * as OpcdaTagApi from "@/api/data/channel/opcda/tag";
+  import * as OpcDaTagApi from "@/api/data/channel/opcda/tag";
   import TagForm from './TagForm.vue'
+  import download from "@/utils/download";
+  import {ref,reactive} from "vue";
+  import TagImportForm from '../../common/tag/TagImportForm.vue'
+  import {onBeforeUnmount, onMounted} from "vue";
+  import * as OpcdaTagApi from "@/api/data/channel/opcda/tag";
 
   defineOptions({name: 'ModBusTag'})
 
@@ -124,7 +169,9 @@
     pageNo: 1,
     pageSize: 10,
     serverId: undefined,
-    tagName: undefined
+    tagName: undefined,
+    serverName: undefined
+    currentValue:false,
   })
   const queryFormRef = ref() // 搜索的表单
   const exportLoading = ref(false) // 导出的加载中
@@ -133,7 +180,7 @@
   const getList = async () => {
     loading.value = true
     try {
-      const page = await OpcdaTagApi.getOpcdaTagPage(queryParams)
+      const page = await OpcDaTagApi.getOpcdaTagPage(queryParams)
       list.value = page.list
       total.value = page.total
     } finally {
@@ -165,7 +212,7 @@
       // 删除的二次确认
       await message.delConfirm()
       // 发起删除
-      await OpcdaTagApi.deleteOpcdaTag(id)
+      await OpcDaTagApi.deleteOpcdaTag(id)
       message.success(t('common.delSuccess'))
       // 刷新列表
       await getList()
@@ -174,10 +221,11 @@
   }
 
   /** 打开弹窗 */
-  const open = async (serverId?: string) => {
+  const open = async (serverId?: string, serverName?: string) => {
     resetForm()
     drawer.value = true
     queryParams.serverId = serverId
+    queryParams.serverName = serverName
     if (serverId) {
       getList()
     }
@@ -195,4 +243,46 @@
   const handleClose = (done: () => void) => {
     drawer.value = false
   }
+
+  /** tag导入 */
+  const importFormRef = ref()
+  const handleImport = () => {
+    if(queryParams.serverId){
+      importFormRef.value.open(queryParams.serverName, '/data/channel/opcda/tag/import',OpcDaTagApi.importOpcDaTagTemplate(), 'OpcDa', queryParams.serverId)
+    }
+  }
+
+  /** 导出按钮操作 */
+  const handleExport = async () => {
+    try {
+      // 导出的二次确认
+      await message.exportConfirm()
+      // 发起导出
+      exportLoading.value = true
+      const data = await OpcDaTagApi.exportOpcDaTag(queryParams)
+      download.excel(data, 'OpcDa_' + queryParams.serverName + '_Tag列表.xlsx')
+    } catch {
+    } finally {
+      exportLoading.value = false
+    }
+  }
+  let intervalId;
+
+  onMounted(async () => {
+    // 创建定时器
+    intervalId = setInterval(async () => {
+      if(queryParams.currentValue){
+        const page = await OpcdaTagApi.getOpcdaTagPage(queryParams)
+        list.value = page.list
+        total.value = page.total
+      }
+    }, 10000);
+  });
+
+  // 在组件卸载时清除定时器
+  onBeforeUnmount(() => {
+    if (intervalId) {
+      clearInterval(intervalId);
+    }
+  });
 </script>
diff --git a/src/views/data/channel/opcua/tag/TagForm.vue b/src/views/data/channel/opcua/tag/TagForm.vue
index 810fbff..adb01a8 100644
--- a/src/views/data/channel/opcua/tag/TagForm.vue
+++ b/src/views/data/channel/opcua/tag/TagForm.vue
@@ -36,7 +36,7 @@
           <el-form-item label="是否启用" prop="enabled">
             <el-select v-model="formData.enabled" placeholder="请选择">
               <el-option
-                v-for="dict in getBoolDictOptions(DICT_TYPE.IS_ENABLED)"
+                v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)"
                 :key="dict.value"
                 :label="dict.label"
                 :value="dict.value"
@@ -54,8 +54,8 @@
 </template>
 <script lang="ts" setup>
   import * as OpcuaTagApi from '@/api/data/channel/opcua/tag'
-  import { CommonEnabledBool } from '@/utils/constants'
-  import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
+  import {CommonEnabled} from '@/utils/constants'
+  import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
 
   defineOptions({name: 'OpcuaTagForm'})
 
@@ -70,7 +70,7 @@
     device: undefined,
     tagName: undefined,
     dataType: undefined,
-    enabled: CommonEnabledBool.ENABLE,
+    enabled: CommonEnabled.ENABLE,
     address: undefined,
     samplingRate: undefined
   })
@@ -134,7 +134,7 @@
       device: undefined,
       tagName: undefined,
       dataType: undefined,
-      enabled: CommonEnabledBool.ENABLE,
+      enabled: CommonEnabled.ENABLE,
       address: undefined,
       samplingRate: undefined
     }
diff --git a/src/views/data/channel/opcua/tag/index.vue b/src/views/data/channel/opcua/tag/index.vue
index a535147..1ef3fc1 100644
--- a/src/views/data/channel/opcua/tag/index.vue
+++ b/src/views/data/channel/opcua/tag/index.vue
@@ -51,6 +51,21 @@
             <Icon icon="ep:plus" class="mr-5px" />
             新增
           </el-button>
+          <el-button
+            type="warning"
+            plain
+            @click="handleImport"
+            v-hasPermi="['data:channel-opcua-tag:import']">
+            <Icon icon="ep:upload" /> 导入
+          </el-button>
+          <el-button
+            type="success"
+            plain
+            @click="handleExport"
+            :loading="exportLoading"
+            v-hasPermi="['data:channel-opcua-tag:export']">
+            <Icon icon="ep:download" />导出
+          </el-button>
         </el-form-item>
       </el-form>
     </ContentWrap>
@@ -89,8 +104,26 @@
           align="center"
         >
           <template #default="scope">
-            <el-tag v-if="scope.row.enabled === true" size="small">是</el-tag>
+            <el-tag v-if="scope.row.enabled === 1" size="small">是</el-tag>
             <el-tag v-else size="small" type="danger">否</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="dataValue"
+          label="数据值"
+          header-align="center"
+          align="center"
+          :formatter="(row) => {if (row.dataValue === -2.0) {return '--';}return row.dataValue;}"
+        />
+        <el-table-column
+          prop="quality"
+          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">
@@ -124,12 +157,16 @@
     </ContentWrap>
     <!-- 表单弹窗:添加/修改 -->
     <TagForm ref="formRef" @success="getList" />
+    <TagImportForm ref="importFormRef" @success="getList" />
   </el-drawer>
 </template>
 <script lang="ts" setup>
   import type { DrawerProps } from 'element-plus'
-  import * as OpcuaTagApi from "@/api/data/channel/opcua/tag";
+  import * as OpcUaTagApi from "@/api/data/channel/opcua/tag";
   import TagForm from './TagForm.vue'
+  import download from "@/utils/download";
+  import {ref,reactive} from "vue";
+  import TagImportForm from '../../common/tag/TagImportForm.vue'
 
   defineOptions({name: 'OpcuaTag'})
 
@@ -145,6 +182,7 @@
     pageNo: 1,
     pageSize: 10,
     device: undefined,
+    deviceId: undefined,
     tagName: undefined,
     address: undefined
   })
@@ -155,7 +193,7 @@
   const getList = async () => {
     loading.value = true
     try {
-      const page = await OpcuaTagApi.getOpcuaTagPage(queryParams)
+      const page = await OpcUaTagApi.getOpcuaTagPage(queryParams)
       list.value = page.list
       total.value = page.total
     } finally {
@@ -187,7 +225,7 @@
       // 删除的二次确认
       await message.delConfirm()
       // 发起删除
-      await OpcuaTagApi.deleteOpcuaTag(id)
+      await OpcUaTagApi.deleteOpcuaTag(id)
       message.success(t('common.delSuccess'))
       // 刷新列表
       await getList()
@@ -196,10 +234,11 @@
   }
 
   /** 打开弹窗 */
-  const open = async (device?: string) => {
+  const open = async (deviceId?: string,device?: string) => {
     resetForm()
     drawer.value = true
     queryParams.device = device
+    queryParams.deviceId = deviceId
     if (device) {
       getList()
     }
@@ -218,4 +257,27 @@
   const handleClose = (done: () => void) => {
     drawer.value = false
   }
+
+  /** tag导入 */
+  const importFormRef = ref()
+  const handleImport = () => {
+    if(queryParams.device){
+      importFormRef.value.open(queryParams.device, '/data/channel/opcua/tag/import',OpcUaTagApi.importOpcUaTagTemplate(), 'OpcUa', queryParams.device)
+    }
+  }
+
+  /** 导出按钮操作 */
+  const handleExport = async () => {
+    try {
+      // 导出的二次确认
+      await message.exportConfirm()
+      // 发起导出
+      exportLoading.value = true
+      const data = await OpcUaTagApi.exportOpcUaTag(queryParams)
+      download.excel(data, 'OpcUa_' + queryParams.device + '_Tag列表.xlsx')
+    } catch {
+    } finally {
+      exportLoading.value = false
+    }
+  }
 </script>
diff --git a/src/views/data/plan/category/CategoryForm.vue b/src/views/data/plan/category/CategoryForm.vue
new file mode 100644
index 0000000..5bbbaad
--- /dev/null
+++ b/src/views/data/plan/category/CategoryForm.vue
@@ -0,0 +1,141 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+    >
+      <el-form-item label="上级菜单">
+        <el-tree-select
+          v-model="formData.pid"
+          :data="categoryTree"
+          :default-expanded-keys="[0]"
+          :props="categoryTreeProps"
+          check-strictly
+          node-key="id"
+        />
+      </el-form-item>
+      <el-form-item label="分类名称" prop="label">
+        <el-input v-model="formData.label" clearable placeholder="请输入分类名称" />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="sort">
+        <el-input-number v-model="formData.sort" :min="0" clearable controls-position="right" />
+      </el-form-item>
+    </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 { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as CategoryApi from '@/api/data/plan/category'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+defineOptions({ name: 'PlanItemCategoryForm' })
+
+const { wsCache } = useCache()
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const categoryTreeProps  = ref({
+  children: 'children',
+  label: 'label',
+  value: 'id',
+  isLeaf: 'leaf',
+  emitPath: false
+})
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  label: '',
+  pid: '0',
+  sort: Number(undefined)
+})
+const formRules = reactive({
+  label: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '分类顺序不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number, parentId?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  if (parentId) {
+    formData.value.parentId = parentId
+  }
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await CategoryApi.getCategory(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得菜单列表
+  await getTree()
+}
+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 CategoryApi.PlanItemCategoryVO
+    if (formType.value === 'create') {
+      await CategoryApi.createCategory(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await CategoryApi.updateCategory(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+    // 清空,从而触发刷新
+    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+  }
+}
+
+/** 获取下拉框[上级菜单]的数据  */
+const categoryTree = ref<Tree[]>([]) // 树形结构
+const getTree = async () => {
+  categoryTree.value = []
+  const res = await CategoryApi.getCategoryListAllSimple()
+  let menu: Tree = { id: '0', label: '主类目', children: [] }
+  menu.children = handleTree(res, 'id', 'pid')
+  categoryTree.value.push(menu)
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    label: '',
+    pid: '0',
+    sort: Number(undefined)
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/data/plan/category/index.vue b/src/views/data/plan/category/index.vue
new file mode 100644
index 0000000..4dace49
--- /dev/null
+++ b/src/views/data/plan/category/index.vue
@@ -0,0 +1,159 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      ref="queryFormRef"
+      :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
+      label-width="68px"
+    >
+      <el-form-item label="分类名称" prop="name">
+        <el-input
+          v-model="queryParams.label"
+          class="!w-240px"
+          clearable
+          placeholder="请输入分类名称"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          重置
+        </el-button>
+        <el-button
+          v-hasPermi="['data:plan-item-category:create']"
+          plain
+          type="primary"
+          @click="openForm('create')"
+        >
+          <Icon class="mr-5px" icon="ep:plus" />
+          新增
+        </el-button>
+        <el-button plain type="danger" @click="toggleExpandAll">
+          <Icon class="mr-5px" icon="ep:sort" />
+          展开/折叠
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="list"
+      :default-expand-all="isExpandAll"
+      row-key="id"
+    >
+      <el-table-column :show-overflow-tooltip="true" label="分类名称" prop="label" width="300" />
+      <el-table-column label="排序" prop="sort" width="60" />
+      <el-table-column align="center" label="操作">
+        <template #default="scope">
+          <el-button
+            v-hasPermi="['data:plan-item-category:update']"
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+          >
+            修改
+          </el-button>
+          <el-button
+            v-hasPermi="['data:plan-item-category:delete']"
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <CategoryForm ref="formRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { handleTree } from '@/utils/tree'
+import * as CategoryApi from '@/api/data/plan/category'
+import CategoryForm from './CategoryForm.vue'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+
+defineOptions({ name: 'PlanItemCategory' })
+
+const { wsCache } = useCache()
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const loading = ref(true) // 列表的加载中
+const list = ref<any>([]) // 列表的数据
+const queryParams = reactive({
+  label: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const isExpandAll = ref(false) // 是否展开,默认全部折叠
+const refreshTable = ref(true) // 重新渲染表格状态
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await CategoryApi.getCategoryList(queryParams)
+    list.value = handleTree(data, 'id', 'pid')
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number, parentId?: number) => {
+  formRef.value.open(type, id, parentId)
+}
+
+/** 展开/折叠操作 */
+const toggleExpandAll = () => {
+  refreshTable.value = false
+  isExpandAll.value = !isExpandAll.value
+  nextTick(() => {
+    refreshTable.value = true
+  })
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await CategoryApi.deleteCategory(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
diff --git a/src/views/data/plan/data/DataSetForm.vue b/src/views/data/plan/data/DataSetForm.vue
new file mode 100644
index 0000000..5c02606
--- /dev/null
+++ b/src/views/data/plan/data/DataSetForm.vue
@@ -0,0 +1,129 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-form-item label="名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入名称" />
+      </el-form-item>
+      <el-form-item label="数据源" prop="dataSource">
+        <el-select v-model="formData.dataSource" clearable placeholder="请选择数据源">
+          <el-option
+            v-for="item in dataSourceList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id + ''"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="查询语句" prop="querySql">
+        <el-input v-model="formData.querySql" placeholder="请输入内容" type="textarea" maxlength="300"
+                  show-word-limit :rows="6" spellcheck="false"/>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" maxlength="100"
+                  show-word-limit/>
+      </el-form-item>
+    </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 { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as DataSetApi from '@/api/data/plan/data'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as DataSourceConfigApi from "@/api/infra/dataSourceConfig";
+
+defineOptions({ name: 'PlanDataSetForm' })
+
+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,
+  name: '',
+  dataSource: '',
+  querySql: '',
+  remark: ''
+})
+const formRules = reactive({
+  name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
+  dataSource: [{ required: true, message: '数据源不能为空', trigger: 'blur' }],
+  querySql: [{ required: true, message: '查询语句不能为空', trigger: 'change' }]
+})
+const formRef = ref() // 表单 Ref
+const dataSourceList = ref([] as DataSourceConfigApi.DataSourceConfigVO[])
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+
+  // 加载数据源列表
+  dataSourceList.value = await DataSourceConfigApi.getDataSourceConfigList()
+
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await DataSetApi.getDataSet(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 DataSetApi.DataSetVO
+    if (formType.value === 'create') {
+      await DataSetApi.createDataSet(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await DataSetApi.updateDataSet(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    dataSource: '',
+    querySql: '',
+    remark: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/data/plan/data/index.vue b/src/views/data/plan/data/index.vue
new file mode 100644
index 0000000..94c58d5
--- /dev/null
+++ b/src/views/data/plan/data/index.vue
@@ -0,0 +1,158 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      ref="queryFormRef"
+      :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
+      label-width="68px"
+    >
+      <el-form-item label="名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          class="!w-240px"
+          clearable
+          placeholder="请输入名称"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          重置
+        </el-button>
+        <el-button
+          v-hasPermi="['data:plan-data-set:create']"
+          plain
+          type="primary"
+          @click="openForm('create')"
+        >
+          <Icon class="mr-5px" icon="ep:plus" />
+          新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column align="center" label="名称" prop="name" show-overflow-tooltip />
+      <el-table-column align="center" label="备注" prop="remark" />
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="创建时间"
+        prop="createTime"
+        width="180"
+      />
+      <el-table-column align="center" label="操作">
+        <template #default="scope">
+          <el-button
+            v-hasPermi="['data:plan-data-set:update']"
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+          >
+            修改
+          </el-button>
+          <el-button
+            v-hasPermi="['data:plan-data-set:delete']"
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      v-model:limit="queryParams.pageSize"
+      v-model:page="queryParams.pageNo"
+      :total="total"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <DataSetForm ref="formRef" @success="getList" />
+</template>
+
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import * as DataSetApi from '@/api/data/plan/data'
+import DataSetForm from './DataSetForm.vue'
+import download from '@/utils/download'
+
+defineOptions({ name: 'PlanDataSet' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 字典表格数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: ''
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询字典类型列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await DataSetApi.getDataSetPage(queryParams)
+    list.value = data.list
+    total.value = data.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 DataSetApi.deleteDataSet(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
diff --git a/src/views/data/plan/item/ItemChart.vue b/src/views/data/plan/item/ItemChart.vue
new file mode 100644
index 0000000..bb6d993
--- /dev/null
+++ b/src/views/data/plan/item/ItemChart.vue
@@ -0,0 +1,225 @@
+<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>
+        <el-date-picker
+          v-model="dataForm.startTime"
+          type="datetime"
+          format="YYYY-MM-DD HH:mm:ss"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          placeholder="选择日期时间"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-date-picker
+          v-model="dataForm.endTime"
+          type="datetime"
+          format="YYYY-MM-DD HH:mm:ss"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          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"
+          v-hasPermi="['data:point:export']"
+        >
+          <Icon icon="ep:download" />导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <div ref="chartDomPlan" class="result-chart"></div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import {ref} from 'vue';
+import * as echarts from 'echarts';
+import * as ItemApi from '@/api/data/plan/item'
+import download from "@/utils/download";
+const message = useMessage() // 消息弹窗
+const visible = ref(false);
+const chartDomPlan = ref(null);
+let myChart = null;
+const dataForm = ref({
+  id: "",
+  itemNo: "",
+  itemName: "",
+  startTime: getYMDHMS(),
+  endTime: undefined,
+});
+const queryParams = reactive({
+  itemNos: [],
+  start: undefined,
+  end: undefined,
+})
+function getYMDHMS() {
+  let timestamp = new Date().getTime();
+  let time = new Date(timestamp - 1000 * 60 * 60 * 3);
+  let year = time.getFullYear();
+  let month = (time.getMonth() + 1).toString();
+  let date = time.getDate().toString();
+  let hours = time.getHours().toString();
+  let minute = time.getMinutes().toString();
+
+  if (month < 10) {
+    month = "0" + month;
+  }
+  if (date < 10) {
+    date = "0" + date;
+  }
+  if (hours < 10) {
+    hours = "0" + hours;
+  }
+  if (minute < 10) {
+    minute = "0" + minute;
+  }
+
+  return (
+    year +
+    "-" +
+    month +
+    "-" +
+    date +
+    " " +
+    hours +
+    ":" +
+    minute +
+    ":" +
+    "00"
+  );
+}
+/** 打开弹窗 */
+const open = async (row: object) => {
+  visible.value = true
+  dataForm.value.id = row.id;
+  dataForm.value.itemNo = row.itemNo;
+  dataForm.value.itemName = row.itemName;
+  dataForm.value.startTime = getYMDHMS();
+  dataForm.value.endTime = "";
+  getDataList();
+}
+
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+async function getDataList() {
+  visible.value = true;
+  if (dataForm.value.id) {
+    try {
+      queryParams.itemNos=[dataForm.value.itemNo];
+      queryParams.start = dataForm.value.startTime;
+      queryParams.end = dataForm.value.endTime;
+      const data = await ItemApi.getPlanChart(queryParams)
+      let seriesData = []
+      data.series.forEach(item => {
+        seriesData.push({
+          name: item.name,
+          type: "line",
+          data: item.data,
+          showSymbol: true,
+          smooth: false,
+          lineStyle: {
+            normal: {
+              color: "#5B8FF9",
+              width: 1,
+            },
+          },
+        });
+      })
+
+      myChart = echarts.init(chartDomPlan.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: 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)
+    }
+  }
+}
+/** 导出按钮操作 */
+const exportLoading = ref(false)
+const handleExport = async () => {
+  queryParams.itemNos=[dataForm.value.itemNo];
+  queryParams.start = dataForm.value.startTime;
+  queryParams.end = dataForm.value.endTime;
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await ItemApi.exportPlanValue(queryParams)
+    download.excel(data, dataForm.value.itemName +'.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+</script>
+<style>
+.el-select {
+  width: 100%;
+}
+
+.result-chart {
+  height: 500px;
+}
+</style>
diff --git a/src/views/data/plan/item/ItemForm.vue b/src/views/data/plan/item/ItemForm.vue
new file mode 100644
index 0000000..97257c2
--- /dev/null
+++ b/src/views/data/plan/item/ItemForm.vue
@@ -0,0 +1,177 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="指标编码" prop="itemNo">
+            <el-input v-model="formData.itemNo" disabled/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="指标名称" prop="itemName">
+            <el-input v-model="formData.itemName" placeholder="请输入指标名称"/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="计划分类" prop="itemCategory">
+            <el-tree-select
+              v-model="formData.itemCategory"
+              :data="categoryTree"
+              :default-expanded-keys="[0]"
+              :props="defaultProps"
+              check-strictly
+              node-key="id"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="时间粒度" prop="timeGranularity">
+            <el-select v-model="formData.timeGranularity" placeholder="请选择">
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.TIME_GRANULARITY)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="数据集" prop="dataSet">
+            <el-select v-model="formData.dataSet" clearable placeholder="请选择数据集" @change="handleDataSetChange($event)">
+              <el-option
+                v-for="item in dataSetList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id + ''"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" maxlength="100"
+                  show-word-limit/>
+      </el-form-item>
+    </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 {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict'
+import * as ItemApi from '@/api/data/plan/item'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as DataSetApi from "@/api/data/plan/data";
+import * as CategoryApi from "@/api/data/plan/category";
+import {defaultProps} from "@/api/data/plan/category";
+import {handleTree} from "@/utils/tree";
+
+defineOptions({ name: 'PlanItemForm' })
+
+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,
+  itemNo: '',
+  itemName: '',
+  itemCategory: '',
+  timeGranularity: '',
+  dataSet: '',
+  remark: '',
+  status: undefined,
+})
+const formRules = reactive({
+  itemName: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
+  itemCategory: [{ required: true, message: '分类不能为空', trigger: 'blur' }],
+  dataSet: [{ required: true, message: '数据集不能为空', trigger: 'change' }]
+})
+const formRef = ref() // 表单 Ref
+const dataSetList = ref([] as DataSetApi.DataSetVO[])
+
+const categoryTree = ref<Tree[]>([])
+const getCategoryTree = async () => {
+  categoryTree.value = []
+  const res = await CategoryApi.getCategoryListAllSimple()
+  let category: Tree = { id: 0, label: '主类目', children: [] }
+  category.children = handleTree(res, 'id', 'pid')
+  categoryTree.value.push(category)
+}
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 加载数据源列表
+  dataSetList.value = await DataSetApi.getDataSetList()
+  // 加载类别
+  await getCategoryTree()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ItemApi.getItem(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 ItemApi.ItemVO
+    if (formType.value === 'create') {
+      await ItemApi.createItem(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ItemApi.updateItem(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    dataSource: '',
+    querySql: '',
+    remark: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/data/plan/item/index.vue b/src/views/data/plan/item/index.vue
new file mode 100644
index 0000000..cc85952
--- /dev/null
+++ b/src/views/data/plan/item/index.vue
@@ -0,0 +1,178 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px"
+             label-width="68px">
+      <el-form-item label="计划编码" prop="itemNo">
+        <el-input v-model="queryParams.itemNo" class="!w-200px" clearable
+                  placeholder="请输入计划编码"
+                  @keyup.enter="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="计划名称" prop="itemName">
+        <el-input v-model="queryParams.itemName" class="!w-200px" clearable
+                  placeholder="请输入计划名称"
+                  @keyup.enter="handleQuery"/>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search"/>
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon class="mr-5px" icon="ep:refresh"/>
+          重置
+        </el-button>
+        <el-button
+          v-hasPermi="['data:ind-item:create']"
+          plain
+          type="primary"
+          @click="openForm('create')"
+        >
+          <Icon class="mr-5px" icon="ep:plus"/>
+          新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column prop="itemNo" label="计划编码" header-align="center" align="center"
+                       min-width="80"/>
+      <el-table-column prop="itemName" label="计划名称" header-align="center" align="center"
+                       min-width="120"/>
+      <el-table-column prop="itemCategoryName" label="计划分类" header-align="center" align="center"
+                       min-width="100"/>
+      <el-table-column prop="timeGranularity" label="时间粒度" header-align="center" align="center"
+                       min-width="40">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.TIME_GRANULARITY" :value="scope.row.timeGranularity"/>
+        </template>
+      </el-table-column>
+      <el-table-column
+        :formatter="dateFormatter"
+        align="center"
+        label="创建时间"
+        prop="createTime"
+        width="180"/>
+      <el-table-column align="center" label="操作">
+        <template #default="scope">
+          <el-button
+            v-hasPermi="['data:ind-item:update']"
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)">
+            修改
+          </el-button>
+          <el-button link size="mini" type="primary" @click="chartHandle(scope.row)">数据
+          </el-button>
+          <el-button
+            v-hasPermi="['data:ind-item:delete']"
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)">
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      v-model:limit="queryParams.pageSize"
+      v-model:page="queryParams.pageNo"
+      :total="total"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <ItemForm ref="formRef" @success="getList"/>
+
+  <!-- 表单弹窗:计划数据 -->
+  <ItemChart ref="chartView"/>
+</template>
+
+<script lang="ts" setup>
+import {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict'
+import {dateFormatter} from '@/utils/formatTime'
+import ItemForm from './ItemForm.vue'
+import download from '@/utils/download'
+import * as ItemApi from '@/api/data/plan/item'
+import * as CategoryApi from "@/api/data/plan/category";
+import ItemChart from "./ItemChart.vue";
+
+defineOptions({name: 'PlanItem'})
+
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[])
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 字典表格数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  itemNo: '',
+  itemName: '',
+  itemCategory: ''
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询字典类型列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ItemApi.getItemPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+    dataCategoryList.value = await CategoryApi.getCategoryListAllSimple()
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 查看数据操作 */
+const chartView = ref()
+const chartHandle = (raw: object) => {
+  chartView.value.open(raw)
+}
+
+/** 重置按钮操作 */
+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 ItemApi.deleteItem(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
diff --git a/src/views/data/point/DaPointChart.vue b/src/views/data/point/DaPointChart.vue
index ddc9a2d..305e0e7 100644
--- a/src/views/data/point/DaPointChart.vue
+++ b/src/views/data/point/DaPointChart.vue
@@ -10,21 +10,21 @@
       :model="dataForm"
       @keydown.enter="getDataList()"
     >
-      <el-form-item>
+      <el-form-item label="开始时间">
         <el-date-picker
+          size="mini"
           v-model="dataForm.startTime"
           type="datetime"
-          value-format="yyyy-MM-dd HH:mm:ss"
-          placeholder="选择日期时间"
-       />
+          :clearable="false"
+          placeholder="选择日期时间"/>
       </el-form-item>
-      <el-form-item>
+      <el-form-item label="结束时间">
         <el-date-picker
+          size="mini"
           v-model="dataForm.endTime"
           type="datetime"
-          value-format="yyyy-MM-dd HH:mm:ss"
-          placeholder="选择日期时间"
-        />
+          :clearable="false"
+          placeholder="选择日期时间"/>
       </el-form-item>
       <el-form-item>
         <el-button @click="getDataList()">查询</el-button>
@@ -49,67 +49,39 @@
   import {ref} from 'vue';
   import * as echarts from 'echarts';
   import * as DaPoint from '@/api/data/da/point/daPointChart'
+  import {getYMDHMS} from "@/utils/dateUtil"
   import download from "@/utils/download";
   const message = useMessage() // 消息弹窗
   const visible = ref(false);
   const chartDom = ref(null);
   let myChart = null;
-  const dataForm = ref({
-    id: "",
-    pointNo: "",
-    pointName: "",
-    pointTypeName: "",
-    startTime: getYMDHMS(),
-    endTime: undefined,
-  });
   const queryParams = reactive({
     codes: [],
     startDate: undefined,
     endDate: undefined,
   })
-  function getYMDHMS() {
-    let timestamp = new Date().getTime();
-    let time = new Date(timestamp - 1000 * 60 * 30);
-    let year = time.getFullYear();
-    let month = (time.getMonth() + 1).toString();
-    let date = time.getDate().toString();
-    let hours = time.getHours().toString();
-    let minute = time.getMinutes().toString();
+  const chartParams = reactive({
+    pointNos:[],
+    start : undefined,
+    end : undefined,
+  })
+  const dataForm = ref({
+    id: "",
+    pointNo: "",
+    pointName: "",
+    pointTypeName: "",
+    startTime: getYMDHMS(new Date() - 1000 * 60 * 60),
+    endTime: getYMDHMS(new Date()),
+  });
 
-    if (month < 10) {
-      month = "0" + month;
-    }
-    if (date < 10) {
-      date = "0" + date;
-    }
-    if (hours < 10) {
-      hours = "0" + hours;
-    }
-    if (minute < 10) {
-      minute = "0" + minute;
-    }
-    return (
-      year +
-      "-" +
-      month +
-      "-" +
-      date +
-      " " +
-      hours +
-      ":" +
-      minute +
-      ":" +
-      "00"
-    );
-  }
   /** 打开弹窗 */
   const open = async (row: object) => {
     visible.value = true
     dataForm.value.id = row.id;
     dataForm.value.pointNo = row.pointNo;
     dataForm.value.pointName = row.pointName;
-    dataForm.value.startTime = getYMDHMS();
-    dataForm.value.endTime = "";
+    dataForm.value.startTime = getYMDHMS(new Date(this.dataForm.startTime) - 1000 * 60 * 60);
+    dataForm.value.endTime = getYMDHMS(new Date(this.dataForm.endTime) - 1000 * 60 * 60),
     getDataList();
   }
 
@@ -120,8 +92,8 @@
     if (dataForm.value.id) {
       try {
         queryParams.codes=[dataForm.value.pointNo];
-        queryParams.startDate = dataForm.value.startTime;
-        queryParams.endDate = dataForm.value.endTime;
+        queryParams.startDate = getYMDHMS(new Date(this.dataForm.startTime) - 1000 * 60 * 60);
+        queryParams.endDate = getYMDHMS(new Date(this.dataForm.endTime) - 1000 * 60 * 60);
         const data = await DaPoint.getPointDaChart(queryParams)
         let seriesData = []
         data.series.forEach(item => {
@@ -196,9 +168,9 @@
   /** 导出按钮操作 */
   const exportLoading = ref(false)
   const handleExport = async () => {
-    queryParams.pointNos=[dataForm.value.pointNo];
-    queryParams.start = dataForm.value.startTime;
-    queryParams.end = dataForm.value.endTime;
+    chartParams.pointNos=[dataForm.value.pointNo];
+    chartParams.start = dataForm.value.startTime;
+    chartParams.end = dataForm.value.endTime;
     try {
       // 导出的二次确认
       await message.exportConfirm()
diff --git a/src/views/data/point/DaPointForm.vue b/src/views/data/point/DaPointForm.vue
index ac36580..0279054 100644
--- a/src/views/data/point/DaPointForm.vue
+++ b/src/views/data/point/DaPointForm.vue
@@ -310,7 +310,7 @@
   pointNo: ''
 }])
 const queryParams = reactive({
-  pointType: "MEASURE"
+  pointTypes: "MEASURE,CONSTANT",
 })
 const operatorList = ref(['+', '-', '*', '/', '&', '|', '!', '>', '<'])
 const formData = ref({
diff --git a/src/views/data/point/index.vue b/src/views/data/point/index.vue
index af108ba..1b635f8 100644
--- a/src/views/data/point/index.vue
+++ b/src/views/data/point/index.vue
@@ -157,6 +157,7 @@
 import DaPointForm from './DaPointForm.vue'
 import DaPointChart from './DaPointChart.vue'
 import * as UserApi from "@/api/system/user";
+import PointImportForm from './PointImportForm.vue'
 
 defineOptions({name: 'DataPoint'})
 
diff --git a/src/views/model/chart/ChartForm.vue b/src/views/model/chart/ChartForm.vue
new file mode 100644
index 0000000..3a80398
--- /dev/null
+++ b/src/views/model/chart/ChartForm.vue
@@ -0,0 +1,111 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row :gutter="20">
+        <el-col :span="20">
+          <el-form-item label="图表名称" prop="chartName">
+            <el-input v-model="formData.chartName" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="20">
+          <el-form-item label="图表编码" prop="chartCode">
+            <el-input v-model="formData.chartCode" 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 ChartApi from '@/api/model/mpk/chart'
+
+defineOptions({ name: 'ChartForm' })
+
+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,
+  chartName: undefined,
+  chartCode: undefined,
+})
+const formRules = reactive({
+  chartName: [{ required: true, message: '不能为空', trigger: 'blur' }],
+  chartCode: [{ required: true, message: '不能为空', trigger: 'blur' }],
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: string) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ChartApi.get(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
+    if (formType.value === 'create') {
+      await ChartApi.create(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ChartApi.update(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    chartName: undefined,
+    chartCode: undefined,
+    paramName: undefined,
+    paramCode: undefined,
+    paramValue: undefined,
+    remark: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/model/chart/index.vue b/src/views/model/chart/index.vue
new file mode 100644
index 0000000..6fdb37f
--- /dev/null
+++ b/src/views/model/chart/index.vue
@@ -0,0 +1,177 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="图表名称" prop="chartName">
+        <el-input
+          v-model="queryParams.chartName"
+          placeholder="请输入图表名称"
+          clearable
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="图表编码" prop="chartCode">
+        <el-input
+          v-model="queryParams.chartCode"
+          placeholder="请输入图表编码"
+          clearable
+          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:chart:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" />
+          新增
+        </el-button>
+
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table
+      v-loading="loading"
+      :data="list"
+      row-key="id"
+    >
+      <el-table-column prop="chartName" label="图表名称"/>
+      <el-table-column prop="chartCode" label="图表编码"/>
+      <el-table-column prop="createTime" :formatter="dateFormatter" label="创建时间"/>
+      <el-table-column label="操作" align="center" width="200px">
+        <template #default="scope">
+          <div class="flex items-center justify-center">
+            <el-button
+              link
+              type="primary"
+              @click="openForm('update', scope.row.id)"
+              v-hasPermi="['model:chart:update']"
+            >
+              编辑
+            </el-button>
+            <el-button
+              link
+              type="primary"
+              @click="openChartParam(scope.row.id)"
+              v-hasPermi="['model:chart:update']"
+            >
+              参数
+            </el-button>
+            <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['model:chart:delete']">
+              删除
+            </el-button>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      v-model:limit="queryParams.limit"
+      v-model:page="queryParams.page"
+      :total="total"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <ChartForm ref="formRef" @success="getList" />
+
+  <!-- 分组列表 -->
+  <ChartParam ref="ChartParamRef" />
+
+</template>
+<script lang="ts" setup>
+import {dateFormatter} from '@/utils/formatTime'
+import * as ChartApi from '@/api/model/mpk/chart'
+import ChartForm from './ChartForm.vue'
+import ChartParam from './param/index.vue'
+
+
+defineOptions({name: 'Chart'})
+
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 字典表格数据
+const queryParams = reactive({
+  page: 1,
+  limit: 10,
+  chartName: '',
+  chartCode: ''
+})
+const queryFormRef = ref() // 搜索的表单
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ChartApi.getPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: string) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: string) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ChartApi.del(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {
+  }
+}
+
+/** List操作 */
+const ChartParamRef = ref()
+const openChartParam = (id?: string) => {
+  ChartParamRef.value.open(id)
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
+</script>
diff --git a/src/views/model/chart/param/ChartParamForm.vue b/src/views/model/chart/param/ChartParamForm.vue
new file mode 100644
index 0000000..7d27d88
--- /dev/null
+++ b/src/views/model/chart/param/ChartParamForm.vue
@@ -0,0 +1,130 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="参数名称" prop="paramName">
+            <el-input v-model="formData.paramName" placeholder=""/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="参数编码" prop="paramCode">
+            <el-input v-model="formData.paramCode" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="参数值" prop="paramValue">
+            <el-input v-model="formData.paramValue" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="formData.remark" type="textarea" :rows="2" 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 ChartParamApi from '@/api/model/mpk/chartParam'
+import {deleteIcon} from "@/api/model/mpk/icon";
+
+defineOptions({ name: 'ChartParamForm' })
+
+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,
+  chartId: undefined,
+  paramName: undefined,
+  paramCode: undefined,
+  paramValue: undefined,
+  remark: undefined,
+})
+const formRules = reactive({
+  paramName: [{ required: true, message: '不能为空', trigger: 'blur' }],
+  paramCode: [{ required: true, message: '不能为空', trigger: 'blur' }],
+  paramValue: [{ required: true, message: '不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: string, chartId?: string) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  if (chartId) {
+    formData.value.chartId = chartId
+  }
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ChartParamApi.get(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
+    if (formType.value === 'create') {
+      await ChartParamApi.create(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ChartParamApi.update(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    chartCode: undefined,
+    paramName: undefined,
+    paramCode: undefined,
+    paramValue: undefined,
+    remark: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/model/chart/param/index.vue b/src/views/model/chart/param/index.vue
new file mode 100644
index 0000000..8714213
--- /dev/null
+++ b/src/views/model/chart/param/index.vue
@@ -0,0 +1,183 @@
+<template>
+  <el-drawer
+    v-model="drawer"
+    size="40%"
+    title="参数列表"
+    direction="rtl"
+    :before-close="handleClose"
+  >
+    <!-- 搜索工作栏 -->
+    <ContentWrap>
+      <el-form
+        class="-mb-15px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="68px"
+      >
+        <el-form-item label="参数名称" prop="paramName">
+          <el-input
+            v-model="queryParams.paramName"
+            placeholder="请输入参数名称"
+            clearable
+            class="!w-240px"
+          />
+        </el-form-item>
+<!--        <el-form-item label="参数编码" prop="paramCode">-->
+<!--          <el-input-->
+<!--            v-model="queryParams.paramCode"-->
+<!--            placeholder="请输入"-->
+<!--            clearable-->
+<!--            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-form-item>
+      </el-form>
+    </ContentWrap>
+
+    <!-- 列表 -->
+    <ContentWrap>
+      <el-table
+        v-loading="loading"
+        :data="list"
+        row-key="id"
+      >
+        <el-table-column prop="paramName" label="参数名称"/>
+        <el-table-column prop="paramCode" label="参数编码"/>
+        <el-table-column prop="paramValue" label="参数值"/>
+        <el-table-column label="操作" align="center" width="150px">
+          <template #default="scope">
+            <div class="flex items-center justify-center">
+              <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>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        v-model:limit="queryParams.limit"
+        v-model:page="queryParams.page"
+        :total="total"
+        @pagination="getList"
+      />
+    </ContentWrap>
+
+    <!-- 表单弹窗:添加/修改 -->
+    <ChartParamForm ref="formRef" @success="getList" />
+  </el-drawer>
+</template>
+<script lang="ts" setup>
+import {dateFormatter} from '@/utils/formatTime'
+import * as ChartParamApi from '@/api/model/mpk/chartParam'
+import ChartParamForm from './ChartParamForm.vue'
+
+import type {DrawerProps} from "element-plus";
+
+defineOptions({name: 'ChartParam'})
+
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+
+const drawer = ref(false)
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 字典表格数据
+const queryParams = reactive({
+  page: 1,
+  limit: 10,
+  chartId: undefined,
+  paramName: undefined,
+  paramCode: undefined,
+})
+const queryFormRef = ref() // 搜索的表单
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ChartParamApi.getPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: string) => {
+  formRef.value.open(type, id, queryParams.chartId)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: string) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ChartParamApi.del(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {
+  }
+}
+
+/** 打开弹窗 */
+const open = async (chartId?: string) => {
+  resetForm()
+  drawer.value = true
+  queryParams.chartId = chartId
+  if (chartId) {
+    getList()
+  }
+}
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+/** 重置表单 */
+const resetForm = () => {
+  queryParams.chartId = ''
+  queryParams.name = ''
+}
+
+const handleClose = (done: () => void) => {
+  drawer.value = false
+}
+</script>
diff --git a/src/views/model/mpk/file/MpkGenerator.vue b/src/views/model/mpk/file/MpkGenerator.vue
index 2756e05..dbd3687 100644
--- a/src/views/model/mpk/file/MpkGenerator.vue
+++ b/src/views/model/mpk/file/MpkGenerator.vue
@@ -18,7 +18,7 @@
       </el-form-item>
     </el-form>
     <div style="width: 100%;display: flex;flex-direction: row;justify-content: end;margin-top: 16px">
-      <el-button @click="generatorCode()" type="primary">生成</el-button>
+      <el-button :loading="loading" @click="generatorCode()" type="primary">生成</el-button>
     </div>
   </Dialog>
 </template>
@@ -54,11 +54,19 @@
   }
   defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
+  // 代码生成loading
+  const loading = ref(false)
   /** 提交表单 */
   const generatorCode = async () => {
-    const data = await MpkApi.generatorCode(formData.value)
-    download.zip(data, formData.value.zipFileName)
-    dialogVisible.value = false
+    try {
+      loading.value = true
+      const data = await MpkApi.generatorCode(formData.value)
+      download.zip(data, formData.value.zipFileName)
+      dialogVisible.value = true
+    }finally {
+      dialogVisible.value = false
+    }
+
   }
   /** 重置表单 */
   const resetForm = () => {
diff --git a/src/views/model/mpk/file/MpkRun.vue b/src/views/model/mpk/file/MpkRun.vue
index 6b0111b..d677062 100644
--- a/src/views/model/mpk/file/MpkRun.vue
+++ b/src/views/model/mpk/file/MpkRun.vue
@@ -233,6 +233,7 @@
   const formRef = ref()
   // 运行
   const modelRun = async () => {
+    modelRunResult.value = ''
 // 校验表单
     if (!formRef) return
     const valid = await formRef.value.validate()
@@ -258,7 +259,6 @@
       }
 
       modelRunResult.value = await MpkApi.modelRun(data)
-      modelRunloading.value = false
       message.success('运行成功')
     } finally {
       modelRunloading.value = false
diff --git a/src/views/model/pre/item/MmPredictItemForm.vue b/src/views/model/pre/item/MmPredictItemForm.vue
index 4990942..9e5fb9f 100644
--- a/src/views/model/pre/item/MmPredictItemForm.vue
+++ b/src/views/model/pre/item/MmPredictItemForm.vue
@@ -170,7 +170,7 @@
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="关联模型">
+          <el-form-item label="关联项目">
             <el-select v-model="dataForm.mmPredictModel.mpkprojectid" placeholder="请选择">
               <el-option
                 v-for="item in mpkProjectList"

--
Gitblit v1.9.3