From 11e376f1eeb8c8f2fa9d0b052de6d05ce9f28716 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期四, 29 五月 2025 14:01:12 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 src/views/model/sche/suggest/suggestOperationRecord.vue |  142 ++++
 src/views/model/sche/suggest/index.vue                  |  181 ++++++
 src/api/model/sche/suggest/index.ts                     |    5 
 src/views/model/sche/snapshotConf/index.vue             |  176 +++++
 src/api/model/sche/suggest/snapshotConfigDet.ts         |   26 
 src/views/model/sche/snapshotConf/det/configDetForm.vue |  191 ++++++
 src/api/model/mcs/index.ts                              |    8 
 src/views/data/point/DaPointValue.vue                   |   13 
 src/api/model/sche/suggest/snapshotConfig.ts            |   26 
 src/views/model/pre/repair/index.vue                    |  522 +++++++++++++++++
 src/views/model/sche/snapshotConf/configForm.vue        |  116 +++
 src/utils/dict.ts                                       |    1 
 src/views/model/sche/snapshotConf/det/index.vue         |  192 ++++++
 src/views/model/sche/suggest/suggestSnapshot.vue        |  146 ++++
 src/api/model/sche/suggest/suggestSnapshotRecord.ts     |   12 
 src/api/model/sche/suggest/suggestOperationRecord.ts    |   14 
 src/views/data/point/DaPointForm.vue                    |    2 
 17 files changed, 1,769 insertions(+), 4 deletions(-)

diff --git a/src/api/model/mcs/index.ts b/src/api/model/mcs/index.ts
index 4f55207..086116f 100644
--- a/src/api/model/mcs/index.ts
+++ b/src/api/model/mcs/index.ts
@@ -28,3 +28,11 @@
 export const exportPredictValue = (params) => {
   return request.download({ url: '/model/api/mcs/predict-data/exportValue', params })
 }
+
+export const getPreDataMissDataList = (data: PreDataBarLineReqVO) => {
+  return request.post({ url: '/model/api/mcs/predict-data/missDataList', data })
+}
+
+export const repair = (params) => {
+  return request.post({ url: '/model/api/mcs/predict-data/repair', params })
+}
diff --git a/src/api/model/sche/suggest/index.ts b/src/api/model/sche/suggest/index.ts
index 68a3c84..d54d748 100644
--- a/src/api/model/sche/suggest/index.ts
+++ b/src/api/model/sche/suggest/index.ts
@@ -8,6 +8,11 @@
   outIds: []
 }
 
+// 查询ScheduleModel列表
+export const getScheduleSuggestPage = (params: ScheduleSuggestPageReqVO) => {
+  return request.get({ url: '/model/sche/suggest/page', params })
+}
+
 export const getListByOut = (data: StScheduleSuggestPageReqVO) => {
   return request.post({ url: '/model/sche/suggest/list-out', data })
 }
diff --git a/src/api/model/sche/suggest/snapshotConfig.ts b/src/api/model/sche/suggest/snapshotConfig.ts
new file mode 100644
index 0000000..6013522
--- /dev/null
+++ b/src/api/model/sche/suggest/snapshotConfig.ts
@@ -0,0 +1,26 @@
+// 查询列表
+import request from "@/config/axios";
+
+export const getPage = (params) => {
+  return request.get({ url: '/model/suggest/snapshot/conf-main/page', params})
+}
+
+// 获得
+export const get = (id) => {
+  return request.get({ url: '/model/suggest/snapshot/conf-main/get?id=' + id })
+}
+
+// 新增
+export const create = (data) => {
+  return request.post({ url: '/model/suggest/snapshot/conf-main/create', data })
+}
+
+// 修改
+export const update = (data) => {
+  return request.put({ url: '/model/suggest/snapshot/conf-main/update', data })
+}
+
+// 删除
+export const del = (id) => {
+  return request.delete({ url: '/model/suggest/snapshot/conf-main/delete?id=' + id })
+}
diff --git a/src/api/model/sche/suggest/snapshotConfigDet.ts b/src/api/model/sche/suggest/snapshotConfigDet.ts
new file mode 100644
index 0000000..3a22435
--- /dev/null
+++ b/src/api/model/sche/suggest/snapshotConfigDet.ts
@@ -0,0 +1,26 @@
+// 查询列表
+import request from "@/config/axios";
+
+export const getPage = (params) => {
+  return request.get({ url: '/model/suggest/snapshot/conf-det/page', params})
+}
+
+// 获得
+export const get = (id) => {
+  return request.get({ url: '/model/suggest/snapshot/conf-det/get?id=' + id })
+}
+
+// 新增
+export const create = (data) => {
+  return request.post({ url: '/model/suggest/snapshot/conf-det/create', data })
+}
+
+// 修改
+export const update = (data) => {
+  return request.put({ url: '/model/suggest/snapshot/conf-det/update', data })
+}
+
+// 删除
+export const del = (id) => {
+  return request.delete({ url: '/model/suggest/snapshot/conf-det/delete?id=' + id })
+}
diff --git a/src/api/model/sche/suggest/suggestOperationRecord.ts b/src/api/model/sche/suggest/suggestOperationRecord.ts
new file mode 100644
index 0000000..f53b5e9
--- /dev/null
+++ b/src/api/model/sche/suggest/suggestOperationRecord.ts
@@ -0,0 +1,14 @@
+import request from '@/config/axios'
+
+export interface SuggestOperationRecordPageReqVO extends PageParam {
+  modelId?: string;
+  modelName?: string;
+  scheduleTime?: string;
+  startTime?: string;
+  endTime?: string;
+  handler?: string;
+}
+
+export const getSuggestOperationRecordPage = (params: SuggestOperationRecordPageReqVO) => {
+  return request.get({ url: '/model/suggest/operation/record/page', params })
+}
diff --git a/src/api/model/sche/suggest/suggestSnapshotRecord.ts b/src/api/model/sche/suggest/suggestSnapshotRecord.ts
new file mode 100644
index 0000000..382ba52
--- /dev/null
+++ b/src/api/model/sche/suggest/suggestSnapshotRecord.ts
@@ -0,0 +1,12 @@
+// 建议快照记录
+import request from "@/config/axios";
+
+// 列表
+export const getList = (id) => {
+  return request.get({ url: '/model/suggest/snapshot/record/list?operationId=' + id })
+}
+
+// 图表
+export const getChartList = (data : selectedDataList) => {
+  return request.post({ url: '/model/suggest/snapshot/record/getChartData' , data})
+}
diff --git a/src/utils/dict.ts b/src/utils/dict.ts
index bfe5db5..13efa8f 100644
--- a/src/utils/dict.ts
+++ b/src/utils/dict.ts
@@ -168,6 +168,7 @@
   RESULT_TYPE = 'result_type',
   MATLAB_PLATFORM= 'matlab_platform',
   MATLAB_VERSION= 'matlab_version',
+  SUGGEST_SNAPSHOT_DATA_TYPE= 'suggest_snapshot_data_type',
   // ========== DATA - 数据平台模块  ==========
   DATA_FIELD_TYPE = 'data_field_type',
   TAG_DATA_TYPE = 'tag_data_type',
diff --git a/src/views/data/point/DaPointForm.vue b/src/views/data/point/DaPointForm.vue
index 43cb4f2..0b2376e 100644
--- a/src/views/data/point/DaPointForm.vue
+++ b/src/views/data/point/DaPointForm.vue
@@ -429,7 +429,7 @@
   pointNo: ''
 }])
 const queryParams = reactive({
-  pointTypes: "MEASURE,CONSTANT",
+  pointTypes: "MEASURE,CONSTANT,CUMULATE,EXTREMAL",
 })
 const pointList2 = ref([{
   pointName: '',
diff --git a/src/views/data/point/DaPointValue.vue b/src/views/data/point/DaPointValue.vue
index 671ede7..80f73af 100644
--- a/src/views/data/point/DaPointValue.vue
+++ b/src/views/data/point/DaPointValue.vue
@@ -8,7 +8,7 @@
     <el-form
       :model="dataForm"
       v-loading="formLoading"
-      label-width="120px"
+      label-width="100px"
     >
       <el-row>
         <el-col :span="12">
@@ -23,12 +23,17 @@
         </el-col>
       </el-row>
       <el-row>
-        <el-col :span="12">
+        <el-col :span="8">
           <el-form-item label="数据时间" prop="dataTime">
             <el-input v-model="dataForm.dataTime" readonly/>
           </el-form-item>
         </el-col>
-        <el-col :span="12">
+        <el-col :span="8">
+          <el-form-item label="单位转换" prop="unittransfactor">
+            <el-input v-model="dataForm.unittransfactor" readonly />
+          </el-form-item>
+        </el-col>
+        <el-col :span="8">
           <el-form-item label="数据值" prop="dataValue">
             <el-input v-model="dataForm.dataValue" readonly>
               <template #append>{{ dataForm.unit }}</template>
@@ -88,6 +93,7 @@
 
 /** 打开弹窗 */
 const open = async (row: object) => {
+  console.log(row)
   visible.value = true
   resetForm()
   dataForm.value.id = row.id;
@@ -95,6 +101,7 @@
   dataForm.value.pointName = row.pointName;
   dataForm.value.pointType = row.pointType;
   dataForm.value.unit = row.unit;
+  dataForm.value.unittransfactor = row.unittransfactor;
   getCurrentData()
   queryParams.pointNo = row.pointNo;
   if (dataForm.value.pointType === "CALCULATE") {
diff --git a/src/views/model/pre/repair/index.vue b/src/views/model/pre/repair/index.vue
new file mode 100644
index 0000000..1ba6129
--- /dev/null
+++ b/src/views/model/pre/repair/index.vue
@@ -0,0 +1,522 @@
+<template>
+  <el-card shadow="never" class="aui-card--fill">
+    <div class="mod-his__index">
+      <el-form :inline="true" :model="formData" label-width="80px">
+        <el-form-item label="开始时间">
+          <el-date-picker
+            v-model="formData.startTime"
+            type="datetime"
+            format="YYYY-MM-DD HH:mm:00"
+            value-format="YYYY-MM-DD HH:mm:00"
+            placeholder="选择日期时间"/>
+        </el-form-item>
+        <el-form-item label="结束时间">
+          <el-date-picker
+            v-model="formData.endTime"
+            type="datetime"
+            format="YYYY-MM-DD HH:mm:00"
+            value-format="YYYY-MM-DD HH:mm:00"
+            placeholder="选择日期时间"/>
+        </el-form-item>
+        <el-form-item>
+          <el-button-group>
+            <el-button type="primary" plain :icon="ArrowLeft"
+                       :loading="loading1" @click="leftSearchDataByRange()"/>
+            <el-button type="primary" plain :icon="Search"
+                       :loading="loading1" @click="getList()">查询
+            </el-button>
+            <el-button type="primary" plain :icon="ArrowRight"
+                       :loading="loading1" @click="rightSearchDataByRange()"/>
+          </el-button-group>
+        </el-form-item>
+
+        <div class="his-body">
+          <div class="his-body-left">
+            <div class="his-body-tree">
+              <el-tree
+                :data="treeData"
+                show-checkbox
+                node-key="id"
+                ref="tree"
+                highlight-current
+                :props="defaultProps"
+                @check="onCheckTree"/>
+            </div>
+          </div>
+          <div class="his-body-middle">
+            <div class="his-body-chart">
+              <el-form :inline="true" :model="formData" label-width="100px">
+                <el-row>
+                  <el-col>
+                    <el-form-item label="数据类型">
+                      <el-checkbox-group v-model="formData.chartCheck" @change="changeChartCheck">
+                        <el-checkbox v-for="item in formData.chartOptions" :label="item"
+                                     :key="item">{{ item }}
+                        </el-checkbox>
+                      </el-checkbox-group>
+                    </el-form-item>
+                  </el-col>
+                </el-row>
+              </el-form>
+              <div ref="dataAnalysisChart" style="height: 500px;"></div>
+            </div>
+          </div>
+          <div class="his-body-right">
+            <ContentWrap>
+              <el-table v-loading="loading" :data="list">
+                <el-table-column label="编号" align="center" min-width="100" prop="itemno"/>
+                <el-table-column label="名称" header-align="center" align="left" min-width="200"
+                                 prop="resultName"/>
+                <el-table-column label="开始时间" header-align="center" align="left" min-width="200"
+                                 prop="startTime"/>
+                <el-table-column label="结束时间" header-align="center" align="left" min-width="200"
+                                 prop="endTime"/>
+                <el-table-column label="丢失时间(min)" header-align="center" align="left" min-width="100"
+                                 prop="gap"/>
+                <el-table-column label="随机偏差" header-align="center" align="left" min-width="200"
+                                 prop="random">
+                  <el-input-number
+                    v-model="scope.row.random"
+                    :min="0"
+                    placeholder="请输入随机偏差"
+                  />
+                </el-table-column>
+                <el-table-column label="操作" align="center" min-width="120" fixed="right">
+                  <template #default="scope">
+                  <el-button
+                    link
+                    type="primary"
+                    @click="repair(scope.row)"
+                  >
+                    修复
+                  </el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+              <!-- 分页 -->
+              <Pagination
+                :total="total"
+                v-model:page="queryParams.pageNo"
+                v-model:limit="queryParams.pageSize"
+                @pagination="getList"
+              />
+            </ContentWrap>
+          </div>
+        </div>
+      </el-form>
+    </div>
+  </el-card>
+</template>
+<script lang="ts" setup>
+  import {getYMDHMS} from "@/utils/dateUtil"
+  import * as McsApi from '@/api/model/mcs'
+  import * as echarts from "echarts";
+  import {Search, ArrowLeft, ArrowRight,} from '@element-plus/icons-vue'
+
+  defineOptions({name: 'RepairformData'})
+
+  const dataAnalysisChart = ref(null);
+  const loading1 = ref(false) // 列表的加载中
+  const treeData = ref([])
+  const itemDataObject = ref()
+  const timer = ref()
+  const list = ref()
+  let myChart = null;
+  const queryParams = reactive({
+    pageNo: 1,
+    pageSize: 10,
+  })
+
+  const formData = ref({
+    startTime: '',
+    endTime: '',
+    predictTime: '',
+    chartCheck: ['T+L', '真实值'],
+    chartOptions: ['T+N', 'T+L', '真实值'],
+    checkedItemData: [],
+    isMultipleYRadio: '单坐标轴',
+    isMultipleY: false,
+  })
+
+  const repair = async (rows) => {
+    const params = reactive({
+      outIds: rows.outIds,
+      startTime: rows.startTime,
+      endTime: rows.endTime,
+      random: rows.random,
+    })
+
+      const result = await McsApi.repair(params);
+      if (result.code === 0) {
+        this.$alert('数据修复成功', '提示', {
+          confirmButtonText: '确定',
+          callback: () => {
+            this.getList();
+          }
+        });
+      } else {
+        this.$message.error(`数据修复失败`);
+      }
+  }
+  /** 查询列表 */
+  const getList = async () => {
+    loading1.value = true
+    try {
+      if (!formData.value.chartCheck) {
+        formData.value.chartCheck = ['真实值']
+      }
+      let chartCheckArray = formData.value.chartCheck;
+      if (!formData.value.checkedItemData || formData.value.checkedItemData.length == 0) {
+        return;
+      }
+      let outIds = formData.value.checkedItemData.map(item => {
+        return item.id
+      })
+      const params = reactive({
+        outIds: outIds,
+        predictTime: formData.value.predictTime,
+        startTime: formData.value.startTime,
+        endTime: formData.value.endTime
+      })
+      const data = await McsApi.getPreDataCharts(params)
+      const list = await McsApi.getPreDataMissDataList(params)
+      this.list = list
+      formData.value.predictTime = data.predictTime;
+      formData.value.startTime = data.startTime
+      formData.value.endTime = data.endTime
+
+      let xAxisData = data.categories;
+      let yAxisData = [];
+      let offset = 0;
+      let yAxisIndex = 0;
+      let legendData = [];
+      let yMaxArr = [];
+      let seriesData = [];
+      seriesData.push({
+        name: '',
+        data: [null],
+        type: 'line',
+        smooth: true,
+        color: 'green',
+        markLine: {
+          silent: true,
+          lineStyle: {
+            color: '#32a487',
+            width: 2
+          },
+          data: [{
+            xAxis: formData.value.predictTime
+          }],
+          label: {
+            normal: {
+              formatter: formData.value.predictTime
+            }
+          },
+          symbol: ['circle', 'none'],
+        },
+      });
+      itemDataObject.value = {}
+      for (let i = 0; i < data.dataViewList.length; i++) {
+        let dataView = data.dataViewList[i]
+        itemDataObject.value[dataView.outId] = dataView;
+        let maxValue = dataView.maxValue;
+        let minValue = dataView.minValue;
+        yAxisIndex = formData.value.isMultipleY ? i : 0;
+        let yMax = maxValue;
+        if (maxValue < 0) {
+          maxValue = 1;
+        } else if (maxValue < 10) {
+          yMax = (Math.ceil(maxValue * 11) / 10).toFixed(1);
+        } else if (maxValue < 100) {
+          yMax = (Math.ceil(maxValue * 1.1 / 5) * 5);
+        } else {
+          yMax = (Math.ceil(maxValue * 1.1 / 10) * 10);
+        }
+        yMaxArr.push(yMax);
+        let yMin = minValue;
+        if (minValue >= 0) {
+          yMin = 0;
+        } else if (minValue > -10) {
+          yMin = (Math.floor(minValue * 11) / 10).toFixed(1);
+        } else if (minValue > -100) {
+          yMin = (Math.floor(minValue * 1.1 / 5) * 5);
+        } else {
+          yMin = (Math.floor(minValue * 1.1 / 10) * 10);
+        }
+        yAxisData.push({
+          type: 'value',
+          name: "",
+          min: yMin,
+          max: yMax,
+          position: 'left',
+          offset: offset,
+          splitLine: {
+            show: false
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {}
+          },
+          axisLabel: {
+            formatter: '{value}'
+          }
+        })
+        offset = offset + 40
+        if (chartCheckArray.indexOf('真实值') !== -1) {
+          let legendName = dataView.resultName + '(真实)';
+          legendData.push(legendName);
+          seriesData.push({
+            name: legendName,
+            data: dataView.realData || [],
+            type: 'line',
+            yAxisIndex: yAxisIndex,
+            showSymbol: false,
+            smooth: true,
+            lineStyle: {
+              width: 2
+            }
+          });
+        }
+        if (chartCheckArray.indexOf('T+N') !== -1) {
+          let legendName = dataView.resultName + '(T+N)';
+          seriesData.push({
+            name: legendName,
+            data: dataView.preDataN || [],
+            type: 'line',
+            yAxisIndex: yAxisIndex,
+            showSymbol: false,
+            smooth: true,
+            lineStyle: {
+              width: 2
+            }
+          });
+        }
+        if (chartCheckArray.indexOf('T+L') !== -1) {
+          let legendName = dataView.resultName + '(T+L)';
+          legendData.push(legendName);
+          seriesData.push({
+            name: legendName,
+            data: dataView.preDataL || [],
+            type: 'line',
+            showSymbol: false,
+            connectNulls: true,
+            yAxisIndex: yAxisIndex,
+            smooth: true,
+            lineStyle: {
+              width: 2
+            }
+          });
+        }
+        if (chartCheckArray.indexOf('当时') !== -1) {
+          let legendName = dataView.resultName + '(当时)';
+          legendData.push(legendName);
+          seriesData.push({
+            name: legendName,
+            data: dataView.curData || [],
+            type: 'line',
+            yAxisIndex: yAxisIndex,
+            showSymbol: false,
+            smooth: true,
+            lineStyle: {
+              width: 2
+            }
+          });
+        }
+        if (chartCheckArray.indexOf('调整值') !== -1) {
+          let legendName = dataView.resultName + '(调整值)';
+          legendData.push(legendName);
+          seriesData.push({
+            name: legendName,
+            data: dataView.adjData || [],
+            type: 'line',
+            yAxisIndex: yAxisIndex,
+            showSymbol: false,
+            connectNulls: true,
+            smooth: true,
+            lineStyle: {
+              width: 2,
+              type: 'dashed'
+            }
+          });
+        }
+      }
+      //如果最大值相差不大,改成一致大小
+      if (yMaxArr.length > 1) {
+        let max = Math.max.apply(null, yMaxArr);
+        let min = Math.min.apply(null, yMaxArr);
+        if (Math.abs((max - min) / max) <= 0.2) {
+          for (let i = 0; i < yAxisData.length; i++) {
+            yAxisData[i].max = max;
+          }
+        }
+      }
+      myChart = echarts.init(dataAnalysisChart.value);
+      let option = {
+        title: {
+          text: ''
+        },
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          show: true,
+          data: legendData,
+          top: 10
+        },
+        grid: {
+          top: '20%',
+          left: '3%',
+          right: '6%',
+          bottom: '3%',
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          data: xAxisData
+        },
+        yAxis: formData.value.isMultipleY ? yAxisData : {
+          type: 'value',
+          splitLine: {show: false},
+          axisLine: {show: true}
+        },
+        dataZoom: [
+          {
+            type: 'inside',
+            start: 0,
+            end: 100
+          },
+          {
+            start: 0,
+            end: 10
+          }
+        ],
+        series: seriesData
+      }
+      myChart.clear()
+      myChart.setOption(option)
+    } finally {
+      loading1.value = false
+    }
+  }
+
+  onMounted(() => {
+    getPreItemTree()
+  })
+
+  async function getPreItemTree() {
+    treeData.value = await McsApi.getPredictItemTree()
+  }
+
+  function leftSearchDataByRange() {
+    let mins = getRangeMins();
+    let startTime = formData.value.startTime;
+    let endTime = formData.value.endTime;
+    let predictTime = formData.value.predictTime;
+    if (predictTime) {
+      predictTime = getYMDHMS(new Date(predictTime) - 1000 * 60 * mins);
+      formData.value.predictTime = predictTime;
+    }
+    if (startTime) {
+      startTime = getYMDHMS(new Date(startTime) - 1000 * 60 * mins);
+      formData.value.startTime = startTime;
+    }
+    if (endTime) {
+      endTime = getYMDHMS(new Date(endTime) - 1000 * 60 * mins);
+      formData.value.endTime = endTime;
+    }
+    getList();
+  }
+
+  function getRangeMins() {
+    let result: string | number = 0;
+    if (formData.value.startTime && formData.value.endTime) {
+      let startStamp = new Date(formData.value.startTime).getTime();
+      let endStamp = new Date(formData.value.endTime).getTime();
+      let queryStep = ((endStamp - startStamp) / (1000 * 60)).toFixed(0);
+      result = queryStep >= 0 ? queryStep : 0;
+    }
+    return result;
+  }
+
+  function onCheckTree(data, checked, indeterminate) {
+    formData.value.checkedItemData = [];
+    if (checked.checkedNodes) {
+      formData.value.checkedItemData = [...checked.checkedNodes]
+    }
+    debounce(getList, 1000);
+  }
+
+  function debounce(func, wait) {
+    let args = [];
+    if (timer.value) {
+      clearTimeout(timer.value);
+    }
+    timer.value = setTimeout(() => {
+      func.apply(this, args);
+      timer.value = null;
+    }, wait)
+  }
+
+  function rightSearchDataByRange() {
+    let mins = getRangeMins();
+    let startTime = formData.value.startTime;
+    let endTime = formData.value.endTime;
+    let predictTime = formData.value.predictTime;
+    if (predictTime) {
+      predictTime = getYMDHMS(new Date(predictTime) - 0 + 1000 * 60 * mins);
+      formData.value.predictTime = predictTime;
+    }
+    if (startTime) {
+      startTime = getYMDHMS(new Date(startTime) - 0 + 1000 * 60 * mins);
+      formData.value.startTime = startTime;
+    }
+    if (endTime) {
+      endTime = getYMDHMS(new Date(endTime) - 0 + 1000 * 60 * mins);
+      formData.value.endTime = endTime;
+    }
+    getList();
+  }
+</script>
+<style scoped>
+  .el-form-item {
+    margin-bottom: 0 !important;
+  }
+
+  .his-body-chart {
+    height: 100%;
+    border: 1px solid lightgray;
+    padding: 10px;
+  }
+
+  .his-body-tree {
+    height: 100%;
+    border: 1px solid lightgray;
+    padding: 10px;
+  }
+
+  .his-body-middle{
+    width: 40%;
+    height: 100%;
+    padding: 10px 10px 0 0;
+  }
+  .his-body-right {
+    width: 40%;
+    height: 100%;
+    padding-top: 10px;
+  }
+  .his-body-left {
+    width: 20%;
+    height: 100%;
+    padding: 10px 10px 0 0;
+  }
+
+  .his-body {
+    width: 100%;
+    height: calc(calc(100vh - 68px - 38px - 160px));
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-content: flex-start;
+  }
+</style>
diff --git a/src/views/model/sche/snapshotConf/configForm.vue b/src/views/model/sche/snapshotConf/configForm.vue
new file mode 100644
index 0000000..ce76930
--- /dev/null
+++ b/src/views/model/sche/snapshotConf/configForm.vue
@@ -0,0 +1,116 @@
+<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="title">
+            <el-input v-model="formData.title" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="20">
+          <el-form-item label="模型id" prop="modelId">
+            <el-input v-model="formData.modelId" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="20">
+          <el-form-item label="调整对象" prop="scheduleObj">
+            <el-input v-model="formData.scheduleObj" 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 SnapshotConfigApi from '@/api/model/sche/suggest/snapshotConfig'
+
+defineOptions({ name: 'SnapshotConfigForm' })
+
+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,
+  title: undefined,
+  modelId: undefined,
+  scheduleObj: 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 SnapshotConfigApi.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 SnapshotConfigApi.create(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await SnapshotConfigApi.update(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    title: undefined,
+    modelId: undefined,
+    scheduleObj: undefined,
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/model/sche/snapshotConf/det/configDetForm.vue b/src/views/model/sche/snapshotConf/det/configDetForm.vue
new file mode 100644
index 0000000..4051e8c
--- /dev/null
+++ b/src/views/model/sche/snapshotConf/det/configDetForm.vue
@@ -0,0 +1,191 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row :gutter="24">
+        <el-col :span="12">
+          <el-form-item label="数据类型" prop="dataType">
+            <el-select v-model="formData.dataType" placeholder="请选择">
+              <el-option
+                v-for="dict in getStrDictOptions(DICT_TYPE.SUGGEST_SNAPSHOT_DATA_TYPE)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="数据名称" prop="dataName">
+            <el-input v-model="formData.dataName" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="数据编号" prop="dataNo">
+            <el-input v-model="formData.dataNo" placeholder=""/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="左侧长度" prop="leftLength">
+            <el-input v-model="formData.leftLength" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="右侧长度" prop="rightLength">
+            <el-input v-model="formData.rightLength" placeholder=""/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="排序" prop="sort">
+            <el-input v-model="formData.sort" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="拓展字段1" prop="ext1">
+            <el-input v-model="formData.ext1" placeholder=""/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="拓展字段2" prop="ext2">
+            <el-input v-model="formData.ext2" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="拓展字段3" prop="ext3">
+            <el-input v-model="formData.ext3" placeholder=""/>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="拓展字段4" prop="ext4">
+            <el-input v-model="formData.ext4" placeholder=""/>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="拓展字段5" prop="ext5">
+            <el-input v-model="formData.ext5" 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 ConfigDetApi from '@/api/model/sche/suggest/snapshotConfigDet'
+  import {deleteIcon} from "@/api/model/mpk/icon";
+  import {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict'
+
+  defineOptions({name: 'ConfigDetForm'})
+  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,
+    confId: undefined,
+    dataType: undefined,
+    dataName: undefined,
+    dataNo: undefined,
+    leftLength: undefined,
+    rightLength: undefined,
+    sort: undefined,
+    ext1: undefined,
+    ext2: undefined,
+    ext3: undefined,
+    ext4: undefined,
+    ext5: undefined,
+  })
+  const formRules = reactive({
+    dataType: [{required: true, message: '不能为空', trigger: 'blur'}],
+    dataName: [{required: true, message: '不能为空', trigger: 'blur'}],
+    dataNo: [{required: true, message: '不能为空', trigger: 'blur'}],
+    leftLength: [{required: true, message: '不能为空', trigger: 'blur'}],
+    rightLength: [{required: true, message: '不能为空', trigger: 'blur'}],
+  })
+  const formRef = ref() // 表单 Ref
+
+  /** 打开弹窗 */
+  const open = async (type: string, id?: string, confId?: string) => {
+    dialogVisible.value = true
+    dialogTitle.value = t('action.' + type)
+    formType.value = type
+    resetForm()
+    formData.value.confId = confId
+    // 修改时,设置数据
+    if (id) {
+      formLoading.value = true
+      try {
+        formData.value = await ConfigDetApi.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 ConfigDetApi.create(data)
+        message.success(t('common.createSuccess'))
+      } else {
+        await ConfigDetApi.update(data)
+        message.success(t('common.updateSuccess'))
+      }
+      dialogVisible.value = false
+      // 发送操作成功的事件
+      emit('success')
+    } finally {
+      formLoading.value = false
+    }
+  }
+
+  /** 重置表单 */
+  const resetForm = () => {
+    formData.value = {
+      id: undefined,
+      confId: undefined,
+      dataType: undefined,
+      dataNo: undefined,
+      leftLength: undefined,
+      rightLength: undefined,
+      sort: undefined,
+      ext1: undefined,
+      ext2: undefined,
+      ext3: undefined,
+      ext4: undefined,
+      ext5: undefined,
+    }
+    formRef.value?.resetFields()
+  }
+</script>
diff --git a/src/views/model/sche/snapshotConf/det/index.vue b/src/views/model/sche/snapshotConf/det/index.vue
new file mode 100644
index 0000000..4d9516b
--- /dev/null
+++ b/src/views/model/sche/snapshotConf/det/index.vue
@@ -0,0 +1,192 @@
+<template>
+  <el-drawer
+    v-model="drawer"
+    size="60%"
+    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="dataNo">
+          <el-input
+            v-model="queryParams.dataNo"
+            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="dataType" align="center" label="数据类型">
+          <template #default="scope">
+            <dict-tag :type="DICT_TYPE.SUGGEST_SNAPSHOT_DATA_TYPE" :value="scope.row.dataType"/>
+          </template>
+        </el-table-column>
+        <el-table-column prop="dataName" label="数据名称"/>
+        <el-table-column prop="dataNo" label="数据编号"/>
+        <el-table-column prop="leftLength" label="左侧长度(min)"/>
+        <el-table-column prop="rightLength" label="右侧侧长度(min)"/>
+        <el-table-column prop="sort" label="排序"/>
+        <el-table-column prop="ext1" label="拓展字段1"/>
+        <el-table-column prop="ext2" label="拓展字段2"/>
+        <el-table-column prop="ext3" label="拓展字段3"/>
+        <el-table-column prop="ext4" label="拓展字段4"/>
+        <el-table-column prop="ext5" label="拓展字段5"/>
+        <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>
+
+    <!-- 表单弹窗:添加/修改 -->
+    <ConfigDetForm ref="formRef" @success="getList"/>
+  </el-drawer>
+</template>
+<script lang="ts" setup>
+  import {dateFormatter} from '@/utils/formatTime'
+  import * as configDetApi from '@/api/model/sche/suggest/snapshotConfigDet'
+  import ConfigDetForm from './configDetForm.vue'
+  import type {DrawerProps} from "element-plus";
+  import {DICT_TYPE, getIntDictOptions, getStrDictOptions} from '@/utils/dict'
+
+  defineOptions({name: 'ConfigDet'})
+
+  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,
+    confId: '',
+  })
+  const queryFormRef = ref() // 搜索的表单
+
+  const getList = async () => {
+    loading.value = true
+    try {
+      const data = await configDetApi.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.confId)
+  }
+
+  /** 删除按钮操作 */
+  const handleDelete = async (id: string) => {
+    try {
+      // 删除的二次确认
+      await message.delConfirm()
+      // 发起删除
+      await configDetApi.del(id)
+      message.success(t('common.delSuccess'))
+      // 刷新列表
+      await getList()
+    } catch {
+    }
+  }
+
+  /** 打开弹窗 */
+  const open = async (confId?: string) => {
+    resetForm()
+    drawer.value = true
+    queryParams.confId = confId
+    if (confId) {
+      getList()
+    }
+  }
+  defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+  /** 重置表单 */
+  const resetForm = () => {
+    queryParams.confId = ''
+  }
+
+  const handleClose = (done: () => void) => {
+    drawer.value = false
+  }
+</script>
diff --git a/src/views/model/sche/snapshotConf/index.vue b/src/views/model/sche/snapshotConf/index.vue
new file mode 100644
index 0000000..3bbb0b4
--- /dev/null
+++ b/src/views/model/sche/snapshotConf/index.vue
@@ -0,0 +1,176 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="标题" prop="snapshotConfigName">
+        <el-input
+          v-model="queryParams.snapshotConfigName"
+          placeholder="请输入图表名称"
+          clearable
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="模型ID" prop="snapshotConfigCode">
+        <el-input
+          v-model="queryParams.snapshotConfigCode"
+          placeholder="请输入模型ID"
+          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="['suggest:snapshot: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="title" label="标题"/>
+      <el-table-column prop="modelId" label="模型ID"/>
+      <el-table-column prop="scheduleObj" 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="['suggest:snapshot:update']"
+            >
+              编辑
+            </el-button>
+            <el-button
+              link
+              type="primary"
+              @click="openSnapshotConfigDet(scope.row.id)"
+            >
+              参数
+            </el-button>
+            <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['suggest:snapshot: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>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <SnapshotConfigForm ref="formRef" @success="getList" />
+
+  <!-- 分组列表 -->
+  <SnapshotConfigDet ref="SnapshotConfigDetRef" />
+
+</template>
+<script lang="ts" setup>
+import {dateFormatter} from '@/utils/formatTime'
+import * as SnapshotConfigApi from '@/api/model/sche/suggest/snapshotConfig'
+import SnapshotConfigForm from './configForm.vue'
+import SnapshotConfigDet from './det/index.vue'
+
+
+defineOptions({name: 'SnapshotConfig'})
+
+const message = useMessage() // 消息弹窗
+const {t} = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 字典表格数据
+const queryParams = reactive({
+  page: 1,
+  limit: 10,
+  modelId: '',
+  snapshotConfigCode: ''
+})
+const queryFormRef = ref() // 搜索的表单
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await SnapshotConfigApi.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 SnapshotConfigApi.del(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {
+  }
+}
+
+/** List操作 */
+const SnapshotConfigDetRef = ref()
+const openSnapshotConfigDet = (id?: string) => {
+  SnapshotConfigDetRef.value.open(id)
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+})
+</script>
diff --git a/src/views/model/sche/suggest/index.vue b/src/views/model/sche/suggest/index.vue
new file mode 100644
index 0000000..b653bde
--- /dev/null
+++ b/src/views/model/sche/suggest/index.vue
@@ -0,0 +1,181 @@
+<template>
+  <!-- 搜索 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="标题" prop="title">
+        <el-input
+          v-model="queryParams.title"
+          placeholder="标题"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="模型ID" prop="schemeId">
+        <el-input
+          v-model="queryParams.modelId"
+          placeholder="模型ID"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="调整对象" prop="scheduleObj">
+        <el-input
+          v-model="queryParams.scheduleObj"
+          placeholder="调整对象"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="state">
+        <el-select
+          v-model="queryParams.state"
+          placeholder="请选择状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="item in stateOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+      </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-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="标题" align="center" prop="title" min-width="100"/>
+      <el-table-column label="内容" header-align="center" align="left" prop="content" min-width="100"/>
+      <el-table-column label="方案ID" header-align="center" align="left" prop="schemeId" min-width="100"/>
+      <el-table-column label="预警ID" align="center" prop="alarmId" min-width="100"/>
+      <el-table-column label="预测项ID" align="center" prop="itemId" min-width="100"/>
+      <el-table-column label="模型ID" header-align="center" align="center" prop="modelId" min-width="100" />
+      <el-table-column label="调整对象" align="center" prop="scheduleObj" min-width="100"/>
+<!--      <el-table-column label="调整类型" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="调整策略" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="调整方式" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="调整值" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="调整单位" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="持续时长" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="调整开始时间" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="调整结束时间" align="center" min-width="100" fixed="right">-->
+      <el-table-column label="调度时间" align="center" prop="scheduleTime" min-width="100" fixed="right"/>
+      <el-table-column label="状态" align="center" prop="status" min-width="100">
+        <template #default="scope">
+          <span v-if="scope.row.status === 0">未处理</span>
+          <span v-else-if="scope.row.status === 1">已采纳</span>
+          <span v-else-if="scope.row.status === 2">已忽略</span>
+          <span v-else>{{ scope.row.status }}</span>
+        </template>
+      </el-table-column>
+<!--      <el-table-column label="处理人" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="处理时间" align="center" min-width="100" fixed="right">-->
+<!--      <el-table-column label="创建时间" align="center" min-width="100" fixed="right">-->
+      <el-table-column label="详情" align="center" min-width="100" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm(scope.row.modelId ,scope.row.scheduleTime)"
+          >
+            调度历史
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <SuggestOperationRecord ref="formRef" @success="getList" />
+
+</template>
+<script lang="ts" setup>
+  import * as ScheduleSuggestApi from '@/api/model/sche/suggest'
+  import SuggestOperationRecord from './suggestOperationRecord.vue'
+
+  defineOptions({name: 'ScheduleSuggest'})
+
+  const message = useMessage() // 消息弹窗
+  const {t} = useI18n() // 国际化
+
+  const loading = ref(true) // 列表的加载中
+  const total = ref(0) // 列表的总页数
+  const list = ref([]) // 列表的数据
+  const queryParams = reactive({
+    pageNo: 1,
+    pageSize: 10,
+    title: undefined,
+    modelId: undefined
+  })
+  const stateOptions = [
+    { value: 0, label: '未处理' },
+    { value: 1, label: '已采纳' },
+    { value: 2, label: '已忽略' }
+  ];
+  const queryFormRef = ref() // 搜索的表单
+  const exportLoading = ref(false) // 导出的加载中
+
+  /** 查询列表 */
+  const getList = async () => {
+    loading.value = true
+    try {
+      const page = await ScheduleSuggestApi.getScheduleSuggestPage(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 = (modelId?: string,scheduleTime?: string) => {
+    formRef.value.open(modelId, scheduleTime)
+  }
+
+  /** 初始化 **/
+  onMounted(async () => {
+    await getList()
+  })
+</script>
diff --git a/src/views/model/sche/suggest/suggestOperationRecord.vue b/src/views/model/sche/suggest/suggestOperationRecord.vue
new file mode 100644
index 0000000..f09ddea
--- /dev/null
+++ b/src/views/model/sche/suggest/suggestOperationRecord.vue
@@ -0,0 +1,142 @@
+<template>
+  <el-drawer
+    v-model="drawer"
+    size="60%"
+    title="建议调度历史"
+    :direction="direction"
+    :before-close="handleClose"
+  >
+    <!-- 列表 -->
+    <ContentWrap>
+      <el-table v-loading="loading" :data="list">
+        <el-table-column
+          prop="scheduleTime"
+          label="调度时间"
+          header-align="center"
+          align="left"
+          min-width="150"
+        />
+        <el-table-column
+          prop="resultCode"
+          label="结果code"
+          header-align="center"
+          align="left"
+          min-width="150"
+        />
+        <el-table-column
+          prop="resultData"
+          label="结果数据"
+          header-align="center"
+          align="center"
+        />
+        <el-table-column
+          prop="operate"
+          label="操作"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column
+          prop="handler"
+          label="处理人"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column
+          prop="handleTime"
+          label="处理时间"
+          header-align="center"
+          align="center"
+          min-width="150"
+        />
+        <el-table-column label="快照" align="center" min-width="100" fixed="right">
+          <template #default="scope">
+            <el-button
+              link
+              type="primary"
+              @click="openSnapshot(scope.row.id)"
+              v-if="scope.row.operate=='采纳建议'"
+            >
+              快照
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </ContentWrap>
+  </el-drawer>
+  <!-- 快照弹窗 -->
+  <SuggestSnapshot ref="suggestSnapshotRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+  import type {DrawerProps} from 'element-plus'
+  import { getSuggestOperationRecordPage } from '@/api/model/sche/suggest/suggestOperationRecord';
+  import SuggestSnapshot from './suggestSnapshot.vue'
+  import {ref} from "vue";
+
+  defineOptions({name: 'SuggestOperationRecord'})
+
+  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,
+    modelId: undefined,
+    scheduleTime: undefined
+  })
+  const queryFormRef = ref() // 搜索的表单
+  const exportLoading = ref(false) // 导出的加载中
+
+  /** 查询列表 */
+  const getList = async () => {
+    loading.value = true
+    try {
+      const page = await getSuggestOperationRecordPage(queryParams);
+      list.value = page.list
+      total.value = page.total
+    } finally {
+      loading.value = false
+    }
+  }
+
+  /** 搜索按钮操作 */
+  const handleQuery = () => {
+    queryParams.pageNo = 1
+    getList()
+  }
+
+  /** 快照 */
+  const suggestSnapshotRef = ref()
+  const openSnapshot = (id?: string) => {
+    suggestSnapshotRef.value.open(id)
+  }
+
+  /** 打开弹窗 */
+  const open = async (modelId?: string, scheduleTime?: string) => {
+    drawer.value = true
+    queryParams.modelId = modelId
+    queryParams.scheduleTime = scheduleTime
+    if (modelId) {
+      getList()
+    }
+  }
+  defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+  const handleClose = (done: () => void) => {
+    drawer.value = false
+  }
+
+</script>
diff --git a/src/views/model/sche/suggest/suggestSnapshot.vue b/src/views/model/sche/suggest/suggestSnapshot.vue
new file mode 100644
index 0000000..f64263c
--- /dev/null
+++ b/src/views/model/sche/suggest/suggestSnapshot.vue
@@ -0,0 +1,146 @@
+<template>
+  <el-dialog
+    title="历史值"
+    :close-on-click-modal="false"
+    width="80%"
+    v-model="visible"
+  >
+    <el-checkbox-group v-model="selectedData" @change="refreshCharts">
+      <el-checkbox
+        v-for="item in dataList"
+        :label="item.dataName"
+        :key="item.dataName"
+      >
+        {{ item.dataName }}
+      </el-checkbox>
+    </el-checkbox-group>
+
+    <div
+      v-for="(chart, index) in charts"
+      :key="chart.id"
+      class="chart-container"
+      :ref="el => chartDoms[index] = el"
+      v-loading="loading"
+    ></div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+  import { ref, reactive, nextTick } from 'vue'
+  import * as echarts from 'echarts'
+  import * as suggestSnapshotApi from '@/api/model/sche/suggest/suggestSnapshotRecord';
+
+  const { message } = useMessage()
+  const visible = ref(false)
+  const dataList = ref([])
+  const selectedData = ref([])
+  const charts = ref([])
+  const chartDoms = ref([])
+  const chartInstances = ref([])
+  const loading = ref(false)
+
+
+  const open = async (id: string) => {
+    visible.value = true
+    await getDataList(id)
+  }
+
+  defineExpose({ open })
+
+  /** 获取数据列表 */
+  const getDataList = async (id: string) => {
+    try {
+      const res = await suggestSnapshotApi.getList(id)
+      dataList.value = res
+      selectedData.value = [] // 清空已选项
+    } catch (error) {
+      console.error(error)
+      message.error('获取数据列表失败')
+    }
+  }
+
+  /** 刷新图表 */
+  const refreshCharts = async () => {
+    if (selectedData.value.length === 0) {
+      destroyCharts()
+      charts.value = []
+      return
+    }
+
+    loading.value = true
+    try {
+      const selectedDataList = selectedData.value.map(code =>
+        dataList.value.find(d => d.dataName === code)
+      ).filter(Boolean) // 过滤无效项
+      const chartData = await suggestSnapshotApi.getChartList(
+        selectedDataList
+      )
+      destroyCharts()
+
+      // 生成图表配置数据
+      charts.value = selectedData.value.map(code => {
+        const item = dataList.value.find(d => d.dataName === code)
+        return {
+          id: `chart-${code}`,
+          name: item?.dataName || code,
+          data: chartData.find((d: any) => d.dataName === code)
+        }
+      })
+
+      await nextTick()
+      renderCharts()
+    } catch (error) {
+      console.error(error)
+      message.error('获取图表数据失败')
+    } finally {
+      loading.value = false
+    }
+  }
+
+  /** 销毁图表实例 */
+  const destroyCharts = () => {
+    chartInstances.value.forEach(instance => instance?.dispose())
+    chartInstances.value = []
+  }
+
+  /** 渲染图表 */
+  const renderCharts = () => {
+    chartInstances.value = chartDoms.value.map((dom, index) => {
+      if (!dom) return null
+      const chart = echarts.init(dom)
+      const chartInfo = charts.value[index]
+
+      if (!chartInfo) return chart
+
+      const option = {
+        title: {
+          text: chartInfo.name,
+          textStyle: { fontSize: 14 }
+        },
+        tooltip: { trigger: 'axis' },
+        grid: { top: 30, left: '3%', right: '5%', bottom: 20 },
+        xAxis: {type: 'category'},
+        yAxis: { type: 'value' },
+        dataZoom: [{ type: 'inside' }],
+        series: [{
+          type: 'line',
+          data: chartInfo.data?.dataList || [],
+          lineStyle: { color: '#5B8FF9', width: 1 }
+        }]
+      }
+
+      chart.setOption(option)
+      return chart
+    }).filter(Boolean) as echarts.ECharts[]
+  }
+</script>
+
+<style scoped>
+  .chart-container {
+    height: 400px;
+    margin: 20px 0;
+    border: 1px solid #eee;
+    border-radius: 4px;
+    padding: 10px;
+  }
+</style>

--
Gitblit v1.9.3