Jelajahi Sumber

增加报警 报表Excel导出按钮 折线图导出图片

wyd 6 bulan lalu
induk
melakukan
7900650425

+ 20 - 20
src/components/DataModal/common/RealtimeTable.vue

@@ -4,7 +4,7 @@
     <el-table
         v-loading="tableData.length <= 0"
         element-loading-background="inherit"
-        :span-method="objectSpanMethod275"
+        :span-method="objectSpanMethod"
         :data="tableData"
         :header-cell-style="{
         color: '#FFF',
@@ -12,11 +12,11 @@
         background: 'inherit',
         borderBottom: 'none'
       }"
-        :cell-style="getCellStyle275"
+        :cell-style="getCellStyle"
         row-key="id"
         :border="false"
         style="background-color: inherit">
-      <el-table-column
+<!--      <el-table-column
           prop="remark"
           label="操作间"
           align="center"
@@ -33,7 +33,7 @@
             </div>
           </div>
         </template>
-      </el-table-column>
+      </el-table-column>-->
       <!--          <el-tooltip :content="scope.row.remark" placement="top" v-if="scope.row.remark.length > 4">-->
       <!--            <div :style="objectSpanMethod261">-->
       <!--              {{ scope.row.remark.slice(0, 4) + '.' }}-->
@@ -368,22 +368,22 @@ export default {
       this.style5 = this.tableData[5].status || this.tableData[6].status? {color: '#5daf34'} : {color: '#f19409'}
       this.style7 = this.tableData[7].status || this.tableData[8].status? {color: '#5daf34'} : {color: '#f19409'}
     },
-    // getCellStyle({ row, column, rowIndex, columnIndex }) {
-    //   const obj = {
-    //     color: '#FFF',
-    //     fontSize: '18px',
-    //   }
-    //   const a = this.tableDataWrapper.length / 2 === 0 ? 0 : 1
-    //   if (rowIndex % 2 === a) {
-    //     obj.background = '#194364'
-    //   // #194364
-    //   // #04091F
-    //   // #133453
-    //   } else {
-    //     obj.background = ''
-    //   }
-    //   return obj
-    // },
+    getCellStyle({ row, column, rowIndex, columnIndex }) {
+      const obj = {
+        color: '#FFF',
+        fontSize: '18px',
+      }
+      const a = this.tableDataWrapper.length / 2 === 0 ? 0 : 1
+      if (rowIndex % 2 === a) {
+        obj.background = '#194364'
+      // #194364
+      // #04091F
+      // #133453
+      } else {
+        obj.background = ''
+      }
+      return obj
+    },
     getCellStyle261({row, column, rowIndex, columnIndex}) {
 
       const obj = {

+ 87 - 0
src/utils/utilsExcel.js

@@ -0,0 +1,87 @@
+// import XLSX from "xlsx";
+import * as XLSX from 'xlsx'
+import FileSaver from 'file-saver';
+
+/**
+ * 异步读取Excel文件的sheet表为json数据
+ * 不支持合并单元格
+ * @param {File对象} file
+ */
+export function readExcelToJson(file) {
+    return new Promise((resolve, reject) => {
+        const reader = new FileReader();
+
+        reader.onload = (e) => {
+            let data = new Uint8Array(e.target.result);
+            let workbook = XLSX.read(data, { type: "array" });
+            //  console.log("workbook: ", workbook);
+
+            //将Excel 第一个sheet内容转为json格式
+            let worksheet = workbook.Sheets[workbook.SheetNames[0]];
+            let json = XLSX.utils.sheet_to_json(worksheet);
+            //   console.log("jsonExcel:", jsonExcel);
+            resolve(json);
+        };
+
+        reader.readAsArrayBuffer(file.raw);
+    });
+}
+
+/**
+ * 保存json为Excel文件
+ * @param {*} data
+ * @param {*} filename  文件名后缀为.xlsx
+ */
+/*export function saveJsonToExcel(data, filename) {
+    let sheet = XLSX.utils.json_to_sheet(data);
+
+    let workbook = {
+        SheetNames: ["sheet1"],
+        Sheets: {
+            sheet1: sheet,
+        },
+    };
+
+    let wbout = XLSX.write(workbook, {
+        bookType: "xlsx",
+        bookSST: true,
+        type: "array",
+    });
+
+    FileSaver.saveAs(
+        new Blob([wbout], { type: "application/octet-stream" }),
+        filename
+    );
+}*/
+export function saveJsonToExcel(data, filename, customHeaders) {
+    // 使用 json_to_sheet 将数据转为表格
+    let sheet = XLSX.utils.json_to_sheet(data);
+
+    // 如果传入了自定义的表头,则更新表头
+    if (customHeaders && customHeaders.length > 0) {
+        customHeaders.forEach((header, index) => {
+            const cellAddress = XLSX.utils.encode_cell({ r: 0, c: index }); // 表头在第0行
+            sheet[cellAddress] = { v: header, t: 's' }; // 设置单元格内容为汉字
+        });
+    }
+
+    // 创建 Excel 工作簿
+    let workbook = {
+        SheetNames: ["sheet1"],
+        Sheets: {
+            sheet1: sheet,
+        },
+    };
+
+    // 导出 Excel
+    let wbout = XLSX.write(workbook, {
+        bookType: "xlsx",
+        bookSST: true,
+        type: "array",
+    });
+
+    FileSaver.saveAs(
+        new Blob([wbout], { type: "application/octet-stream" }),
+        filename
+    );
+}

+ 1 - 1
src/views/Data.vue

@@ -26,7 +26,7 @@
     <el-table
       :data="tableData"
       height="100%"
-      :span-method="objectSpanMethod275"
+      :span-method="objectSpanMethod"
       :header-cell-style="{
         background: '#BFBFBF',
         boxShadow: 'inset grey 0px -1px',

+ 133 - 86
src/views/Warning.vue

@@ -5,38 +5,40 @@
     <br/>
     <div>
       <el-button @click="dialogVisible = true">清空报警记录</el-button>
+
+      <el-button style="margin-left: 20px" @click="handleDownload">导出Excel表格</el-button>
     </div>
     <br/>
     <el-table
-      v-loading="tableLoading"
-      :data="tableData"
-      :header-cell-style="{
+        v-loading="tableLoading"
+        :data="tableData"
+        :header-cell-style="{
         background: '#BFBFBF',
         boxShadow: 'inset grey 0px -1px',
         color: '#333333',
         borderColor: '#eaeaea',
         fontSize: '20px',
       }"
-      :cell-style="{
+        :cell-style="{
         borderColor: '#919191',
         fontSize: '20px',
         backgroundColor: '#ebebeb',
         fontWeight: '500',
         height: '48px',
       }"
-      :row-class-name="tableRowClassName"
-      row-key="id"
-      border
-      stripe
-      style="width: 100%">
+        :row-class-name="tableRowClassName"
+        row-key="id"
+        border
+        stripe
+        style="width: 100%">
       <el-table-column
-        v-for="(item, index) in tableColumns"
-        :key="index"
-        :prop="item.prop"
-        :label="item.label"
-        :width="item.width"
-        :class-name="index === 0 ? 'warn-column' : 'warn-row'"
-        align="center"
+          v-for="(item, index) in tableColumns"
+          :key="index"
+          :prop="item.prop"
+          :label="item.label"
+          :width="item.width"
+          :class-name="index === 0 ? 'warn-column' : 'warn-row'"
+          align="center"
       />
     </el-table>
     <br/>
@@ -46,13 +48,13 @@
     </div>-->
 
     <el-dialog
-      title="提示"
-      :visible.sync="dialogVisible"
-      width="25%"
-      :append-to-body="false"
-      custom-class="delete-confirm"
-      top="40vh"
-      :show-close="false"
+        title="提示"
+        :visible.sync="dialogVisible"
+        width="25%"
+        :append-to-body="false"
+        custom-class="delete-confirm"
+        top="40vh"
+        :show-close="false"
     >
       <div style="line-height: 25px;display: flex">
         <div>
@@ -69,15 +71,16 @@
 </template>
 
 <script>
-import { v4 as uuidv4 } from 'uuid'
+import {v4 as uuidv4} from 'uuid'
 import NavigationName from '@/components/NavigationName'
 import moment from 'moment'
-import { mapState, mapMutations, mapActions } from 'vuex'
-import { clearWarnRecord } from '@/api/WarnRecord'
+import {mapState, mapMutations, mapActions} from 'vuex'
+import {clearWarnRecord} from '@/api/WarnRecord'
+import {saveJsonToExcel} from "@/utils/utilsExcel";
 
 export default {
   name: 'Warning',
-  components: { NavigationName },
+  components: {NavigationName},
   data() {
     return {
       msg: '报警显示',
@@ -151,6 +154,49 @@ export default {
   methods: {
     ...mapMutations(['CLEAR_WARN_DATA']),
     ...mapActions(['initWarnRecordData']),
+    handleDownload() {
+
+      // 要保留的字段
+      const includeFields = ['warnTime', 'desc', 'warnTypeText', 'currentValue','limitValue'];
+
+      // 过滤掉不需要的字段,保留需要的字段
+      const filteredData = this.tableData.map(item => {
+        // 只保留includeFields中的字段
+        let filteredItem = {};
+        includeFields.forEach(key => {
+          if (item.hasOwnProperty(key)) {
+            filteredItem[key] = item[key]; // 保留指定字段
+          }
+        });
+        return filteredItem;
+      });
+      // 自定义字段顺序
+      const customOrder = ['warnTime', 'desc', 'warnTypeText', 'currentValue','limitValue'];
+
+      // 对filteredData中的字段进行排序
+      const sortedData = filteredData.map(item => {
+        let sortedItem = {};
+        customOrder.forEach(key => {
+          if (item.hasOwnProperty(key)) {
+            sortedItem[key] = item[key]; // 按顺序添加字段
+          }
+        });
+        return sortedItem;
+      });
+
+      const customHeaders = ["时间","对象名","报警类型","当前值","界限值"];
+      // 获取当前时间(UTC时间)
+      const now = new Date();
+
+      // 将时间调整为北京时间(UTC + 8小时)
+      const timestamp = new Date(now.getTime() + 8 * 60 * 60 * 1000)
+          .toISOString()
+          .replace(/[-T:.]/g, ''); // 格式化为不含特殊符号的时间戳
+
+      // 拼接文件名
+      const filename = `报警记录_${timestamp}.xlsx`;
+      saveJsonToExcel(sortedData, filename, customHeaders)
+    },
     fetchData() {
       this.initWarnRecordData()
     },
@@ -184,7 +230,7 @@ export default {
       }
       return data
     },
-    tableRowClassName({ row, rowIndex }) {
+    tableRowClassName({row, rowIndex}) {
       const warnEvent = this.tableData[rowIndex].warnEvent
       if (warnEvent === 1) {
         return 'warning-produce'
@@ -199,85 +245,86 @@ export default {
 </script>
 
 <style lang="less" scoped>
-  .warning {
-    padding: 0 40px;
-    font-size: 16px;
-    display: flex;
-    flex-direction: column;
+.warning {
+  padding: 0 40px;
+  font-size: 16px;
+  display: flex;
+  flex-direction: column;
 
-    /deep/ .delete-confirm {
-      background-color: #ebebeb;
-      border-radius: 5px;
+  /deep/ .delete-confirm {
+    background-color: #ebebeb;
+    border-radius: 5px;
 
-      .el-dialog__header {
-        padding: 8px 8px 10px;
-        .el-dialog__title {
-          line-height: 18px;
-          font-size: 18px;
-          font-weight: bolder;
-          color: #303133;
-        }
+    .el-dialog__header {
+      padding: 8px 8px 10px;
+
+      .el-dialog__title {
+        line-height: 18px;
+        font-size: 18px;
+        font-weight: bolder;
+        color: #303133;
       }
+    }
 
     .el-dialog__body {
       padding: 20px;
     }
 
-      .el-dialog__footer {
-        padding: 8px 16px 16px;
-      }
+    .el-dialog__footer {
+      padding: 8px 16px 16px;
     }
+  }
 
-    /deep/ .el-table {
-      box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, 0.05rem 0.05rem 0.1rem #FFFFFF;
+  /deep/ .el-table {
+    box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, 0.05rem 0.05rem 0.1rem #FFFFFF;
 
-      .cell {
-        white-space: pre-line;
-      }
+    .cell {
+      white-space: pre-line;
     }
+  }
 
-    /deep/ .el-table--border, .el-table--group {
-      border-radius: 10px;
-    }
+  /deep/ .el-table--border, .el-table--group {
+    border-radius: 10px;
+  }
 
-    /deep/ .el-table__row {
-      .warn-column {
-        background-color: #d9d9d9 !important;
-        box-shadow: inset #BFBFBF -1px -1px !important;
-      }
+  /deep/ .el-table__row {
+    .warn-column {
+      background-color: #d9d9d9 !important;
+      box-shadow: inset #BFBFBF -1px -1px !important;
+    }
 
-      .warn-row {
-      }
+    .warn-row {
     }
   }
+}
 
-  /deep/ .el-table .warning-produce {
-    color: #45c4ff;
-  }
+/deep/ .el-table .warning-produce {
+  color: #45c4ff;
+}
 
-  /deep/ .el-table .warning-end {
-    color: #d2262e;
-  }
+/deep/ .el-table .warning-end {
+  color: #d2262e;
+}
 
-  /deep/ .el-table .warning-answer {
-    color: #24d393;
-  }
+/deep/ .el-table .warning-answer {
+  color: #24d393;
+}
 
-  .el-button {
-    border: none;
-    background-color: #EBEBEB;
-    box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
-  }
+.el-button {
+  border: none;
+  background-color: #EBEBEB;
+  box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
+}
 
-  .el-button:active {
-    border: none;
-    background-color: #A20D13;
-    box-shadow: inset 0.1rem 0.1rem 0.2rem #6E090C, inset -0.1rem -0.1rem 0.2rem #c7494d;
-  }
+.el-button:active {
+  border: none;
+  background-color: #A20D13;
+  box-shadow: inset 0.1rem 0.1rem 0.2rem #6E090C, inset -0.1rem -0.1rem 0.2rem #c7494d;
+}
 
-  .el-button:focus, .el-button:hover {
-    color: #FFF;
-    border-color: transparent;
-    background-color: #A20D13;
-  }
+.el-button:focus, .el-button:hover {
+  color: #FFF;
+  border-color: transparent;
+  background-color: #A20D13;
+}
 </style>

+ 52 - 2
src/views/modules/DataReportModal.vue

@@ -32,8 +32,10 @@
         </el-form-item>
         <el-form-item>
           <el-button @click="fetchData">查询</el-button>
+          <el-button style="margin-left: 20px" @click="handleDownload">导出Excel表格</el-button>
         </el-form-item>
       </el-form>
+
     </div>
     <br/>
     <el-table
@@ -58,6 +60,7 @@
       style="width: 100%">
       <el-table-column
         type="index"
+        width="100"
         label="序号"
         align="center"
       />
@@ -85,7 +88,7 @@
         @size-change="handleSizeChange"
         @current-change="handleCurrentChange"
         :current-page="paginationOption.currentPage"
-        :page-sizes="[10, 30, 50, 100]"
+        :page-sizes="[10, 30, 50, 100,500,1000]"
         :page-size="paginationOption.pageSize"
         layout="total, sizes, prev, pager, next, jumper"
         :total="paginationOption.total">
@@ -98,6 +101,7 @@
 import { findSaveData } from '@/api/HistoryData'
 import { findTableColumnsNotStatus } from '@/api/TableConfig'
 import moment from 'moment'
+import {saveJsonToExcel} from "@/utils/utilsExcel";
 
 export default {
   name: 'DataReportModal',
@@ -169,7 +173,9 @@ export default {
               align: item.align,
             })
           }
-          this.tableColumns = tableColumns
+
+          // this.tableColumns = tableColumns
+          this.tableColumns = tableColumns.filter(column => column.label !== '操作间');
           console.log('aaaaaaaaaaaa', tableColumns)
         }
       })
@@ -194,6 +200,50 @@ export default {
         this.tableLoading = false
       })
     },
+    handleDownload() {
+      // 要保留的字段
+      const includeFields = ['dateTime', 'electric', 'vibration', 'noise'];
+      // 过滤掉不需要的字段,保留需要的字段
+      const filteredData = this.tableData.map(item => {
+        // 只保留includeFields中的字段
+        let filteredItem = {};
+        includeFields.forEach(key => {
+          if (item.hasOwnProperty(key)) {
+            filteredItem[key] = item[key]; // 保留指定字段
+          }
+        });
+        return filteredItem;
+      });
+
+      // 自定义字段顺序
+      const customOrder = ['dateTime', 'electric', 'vibration', 'noise'];
+
+      // 对filteredData中的字段进行排序
+      const sortedData = filteredData.map(item => {
+        let sortedItem = {};
+        customOrder.forEach(key => {
+          if (item.hasOwnProperty(key)) {
+            sortedItem[key] = item[key]; // 按顺序添加字段
+          }
+        });
+        return sortedItem;
+      });
+
+      // 自定义表头
+      const customHeaders = ["时间", "电流", "振动", "噪声"];
+      // 获取当前时间(UTC时间)
+      const now = new Date();
+
+      // 将时间调整为北京时间(UTC + 8小时)
+      const timestamp = new Date(now.getTime() + 8 * 60 * 60 * 1000)
+          .toISOString()
+          .replace(/[-T:.]/g, ''); // 格式化为不含特殊符号的时间戳
+      // 拼接文件名
+      const filename = `数据报表_${timestamp}.xlsx`;
+      // 调用saveJsonToExcel并传入自定义表头
+      saveJsonToExcel(sortedData, filename, customHeaders);
+    },
+
     handleSizeChange(val) {
       this.paginationOption.pageSize = val
       this.fetchData()

+ 40 - 0
src/views/modules/HistoryLineModal.vue

@@ -32,6 +32,12 @@
           <el-button @click="fetchData">查询</el-button>
         </el-form-item>
       </el-form>
+<!--      <div>-->
+<!--        &lt;!&ndash; 图表容器 &ndash;&gt;-->
+<!--        <div ref="chart" style="width: 100%; height: 400px;"></div>-->
+<!--        &lt;!&ndash; 打印按钮 &ndash;&gt;-->
+<!--        <button @click="printChart">打印图表</button>-->
+<!--      </div>-->
     </div>
     <div class="line-chart">
       <jg-chart _height="100%" :option="option" v-loading="chartLoading"/>
@@ -107,6 +113,12 @@ export default {
             }
           }
         },
+        toolbox: {
+          show: true,
+          feature: {
+            saveAsImage: {}
+          }
+        },
         grid: {
           top: '8%',
           left: '3%',
@@ -191,6 +203,34 @@ export default {
     },
   },
   methods: {
+
+    // 打印图表
+    printChart() {
+      if (!this.chart) return;
+
+      // 获取图表图片 URL
+      const dataURL = this.chart.getDataURL({
+        type: "png",
+        pixelRatio: 2, // 提高图片清晰度
+        backgroundColor: "#fff", // 设置背景颜色
+      });
+
+      // 创建新窗口用于打印
+      const printWindow = window.open("", "_blank");
+      const style = `
+        <style>
+          body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100%; }
+          img { width: 100%; max-width: 800px; }
+        </style>
+      `;
+      printWindow.document.write(`${style}<img src="${dataURL}" />`);
+      printWindow.document.close();
+      printWindow.focus();
+      printWindow.print();
+      printWindow.close();
+    },
+
+
     fetchData() {
       this.chartLoading = true
       const params = {

+ 245 - 195
src/views/modules/ShowWarnModal.vue

@@ -3,79 +3,82 @@
     <el-form :inline="true" :model="formInline">
       <el-form-item label="选择查询时间">
         <el-date-picker
-          v-model="formInline.dateRange"
-          type="datetimerange"
-          align="right"
-          unlink-panels
-          range-separator="至"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          popper-class="history-date-picker"
-          :append-to-body="false"
-          :picker-options="pickerOptions"
-          :clearable="false"
+            v-model="formInline.dateRange"
+            type="datetimerange"
+            align="right"
+            unlink-panels
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            popper-class="history-date-picker"
+            :append-to-body="false"
+            :picker-options="pickerOptions"
+            :clearable="false"
         >
         </el-date-picker>
       </el-form-item>
       <el-form-item>
         <el-button @click="fetchData">查询</el-button>
       </el-form-item>
+      <el-button style="margin-left: 20px" @click="handleDownload">导出Excel表格</el-button>
     </el-form>
     <el-table
-      :data="tableData"
-      :header-cell-style="{
+        :data="tableData"
+        :header-cell-style="{
         background: '#BFBFBF',
         boxShadow: 'inset grey 0px -1px',
         color: '#333333',
         borderColor: '#eaeaea',
         fontSize: '20px',
       }"
-      :cell-style="{
+        :cell-style="{
         borderColor: '#919191',
         fontSize: '20px',
         backgroundColor: '#ebebeb',
         fontWeight: '500',
         height: '48px',
       }"
-      :row-class-name="tableRowClassName"
-      row-key="id"
-      border
-      stripe
-      style="width: 100%">
+        :row-class-name="tableRowClassName"
+        row-key="id"
+        border
+        stripe
+        style="width: 100%">
       <el-table-column
-        v-for="(item, index) in tableColumns"
-        :key="index"
-        :prop="item.prop"
-        :label="item.label"
-        :width="item.width"
-        :class-name="index === 0 ? 'warn-column' : 'warn-row'"
-        align="center"
+          v-for="(item, index) in tableColumns"
+          :key="index"
+          :prop="item.prop"
+          :label="item.label"
+          :width="item.width"
+          :class-name="index === 0 ? 'warn-column' : 'warn-row'"
+          align="center"
       />
     </el-table>
     <div style="float: right;margin-top: 8px">
       <el-pagination
-        @size-change="handleSizeChange"
-        @current-change="handleCurrentChange"
-        :current-page="paginationOption.currentPage"
-        :page-sizes="[10, 30, 50, 100]"
-        :page-size="paginationOption.pageSize"
-        layout="total, sizes, prev, pager, next, jumper"
-        :total="paginationOption.total">
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+          :current-page="paginationOption.currentPage"
+          :page-sizes="[10, 30, 50, 100]"
+          :page-size="paginationOption.pageSize"
+          layout="total, sizes, prev, pager, next, jumper"
+          :total="paginationOption.total">
       </el-pagination>
     </div>
   </div>
 </template>
 
 <script>
-import { findCollectEntityWarnRecord } from '@/api/WarnRecord'
+import {findCollectEntityWarnRecord} from '@/api/WarnRecord'
 import moment from 'moment'
+import {saveJsonToExcel} from "@/utils/utilsExcel";
 
 export default {
   name: 'ShowWarnModal',
   props: {
     collectEntity: {
       type: Object,
-      default: () => {}
+      default: () => {
+      }
     },
   },
   data() {
@@ -152,6 +155,50 @@ export default {
     }
   },
   methods: {
+    handleDownload() {
+
+      // 要保留的字段
+      const includeFields = ['warnTime', 'desc', 'warnTypeText', 'currentValue', 'limitValue'];
+
+      // 过滤掉不需要的字段,保留需要的字段
+      const filteredData = this.tableData.map(item => {
+        // 只保留includeFields中的字段
+        let filteredItem = {};
+        includeFields.forEach(key => {
+          if (item.hasOwnProperty(key)) {
+            filteredItem[key] = item[key]; // 保留指定字段
+          }
+        });
+        return filteredItem;
+      });
+      // 自定义字段顺序
+      const customOrder = ['warnTime', 'desc', 'warnTypeText', 'currentValue', 'limitValue'];
+
+      // 对filteredData中的字段进行排序
+      const sortedData = filteredData.map(item => {
+        let sortedItem = {};
+        customOrder.forEach(key => {
+          if (item.hasOwnProperty(key)) {
+            sortedItem[key] = item[key]; // 按顺序添加字段
+          }
+        });
+        return sortedItem;
+      });
+
+      const customHeaders = ["时间", "对象名", "报警类型", "当前值", "界限值"];
+
+       // 获取当前时间(UTC时间)
+      const now = new Date();
+
+       // 将时间调整为北京时间(UTC + 8小时)
+      const timestamp = new Date(now.getTime() + 8 * 60 * 60 * 1000)
+          .toISOString()
+          .replace(/[-T:.]/g, ''); // 格式化为不含特殊符号的时间戳
+
+       // 拼接文件名
+      const filename = `报警记录_${timestamp}.xlsx`;
+      saveJsonToExcel(sortedData, filename, customHeaders)
+    },
     fetchData() {
       const tagIds = this.collectEntity.tagList.map(e => e.id)
       const params = {
@@ -177,7 +224,7 @@ export default {
         }
       })
     },
-    tableRowClassName({ row, rowIndex }) {
+    tableRowClassName({row, rowIndex}) {
       const warnEvent = this.tableData[rowIndex].warnEvent
       if (warnEvent === 1) {
         return 'warning-produce'
@@ -209,207 +256,210 @@ export default {
 </script>
 
 <style lang="less" scoped>
-  .show-warn-modal {
-    /deep/ .el-table {
-      box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, 0.05rem 0.05rem 0.1rem #FFFFFF;
+.show-warn-modal {
+  /deep/ .el-table {
+    box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, 0.05rem 0.05rem 0.1rem #FFFFFF;
 
-      .cell {
-        white-space: pre-line;
-      }
+    .cell {
+      white-space: pre-line;
     }
+  }
 
-    /deep/ .el-table--border, .el-table--group {
-      border-radius: 10px;
-    }
+  /deep/ .el-table--border, .el-table--group {
+    border-radius: 10px;
+  }
 
-    /deep/ .el-table__row {
-      .warn-column {
-        background-color: #d9d9d9 !important;
-        box-shadow: inset #BFBFBF -1px -1px !important;
-      }
+  /deep/ .el-table__row {
+    .warn-column {
+      background-color: #d9d9d9 !important;
+      box-shadow: inset #BFBFBF -1px -1px !important;
+    }
 
-      .warn-row {
-      }
+    .warn-row {
     }
+  }
+
+  /deep/ .el-table .warning-produce {
+    color: #45c4ff;
+  }
+
+  /deep/ .el-table .warning-end {
+    color: #d2262e;
+  }
 
-    /deep/ .el-table .warning-produce {
-      color: #45c4ff;
+  /deep/ .el-table .warning-answer {
+    color: #24d393;
+  }
+
+  /deep/ .el-input__inner {
+    background-color: #ebebeb;
+    box-shadow: inset 0.1rem 0.1rem 0.1rem #AAA7A7, inset -0.1rem -0.1rem 0.1rem #FFFFFF;
+    border: none;
+    font-size: 16px;
+    color: #333333;
+  }
+
+  /deep/ .el-select-dropdown__item.selected {
+    font-weight: normal;
+    color: #FFFFFF;
+    background-color: #A20D13;
+    border-radius: 5px;
+    box-shadow: inset 0.2rem 0.2rem 0.5rem #6E090C, inset -0.2rem -0.2rem 0.5rem #C71016;
+  }
+
+  /deep/ .history-date-picker {
+    left: 10% !important;
+    background-color: #EBEBEB;
+    box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, -0.05rem -0.05rem 0.1rem #FFFFFF;
+
+    .el-picker-panel__sidebar {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      width: 110px;
+      border-right: 1px solid #e4e4e4;
+      box-sizing: border-box;
+      padding-top: 6px;
+      background-color: transparent;
+      overflow: auto;
     }
 
-    /deep/ .el-table .warning-end {
-      color: #d2262e;
+    .today {
+      div {
+        span {
+          color: #A20D13;
+          font-weight: bolder;
+        }
+      }
     }
 
-    /deep/ .el-table .warning-answer {
-      color: #24d393;
+    .start-date, .end-date {
+      div {
+        span {
+          color: #A20D13;
+          font-weight: bolder;
+          background-color: #EBEBEB;
+          box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, -0.05rem -0.05rem 0.1rem #FFFFFF;
+        }
+      }
     }
 
-    /deep/ .el-input__inner {
+    .el-picker-panel__footer {
+      border-top: 1px solid #e4e4e4;
+      padding: 4px;
+      text-align: right;
       background-color: #ebebeb;
-      box-shadow: inset 0.1rem 0.1rem 0.1rem #AAA7A7, inset -0.1rem -0.1rem 0.1rem #FFFFFF;
-      border: none;
-      font-size: 16px;
-      color: #333333;
+      position: relative;
+      font-size: 0;
     }
 
-    /deep/ .el-select-dropdown__item.selected {
-      font-weight: normal;
-      color: #FFFFFF;
-      background-color: #A20D13;
+    .el-picker-panel__shortcut {
+      display: block;
+      width: 90%;
+      border: 0;
       border-radius: 5px;
-      box-shadow: inset 0.2rem 0.2rem 0.5rem #6E090C, inset -0.2rem -0.2rem 0.5rem #C71016;
+      background-color: transparent;
+      line-height: 28px;
+      font-size: 14px;
+      color: #606266;
+      padding-left: 12px;
+      text-align: left;
+      outline: 0;
+      cursor: pointer;
+      margin: 8px 5px;
+      box-shadow: 0.1rem 0.1rem 0.1rem #aaa7a7, -0.1rem -0.1rem 0.1rem #ffffff;
     }
 
-    /deep/ .history-date-picker {
-      left: 10% !important;
-      background-color: #EBEBEB;
-      box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, -0.05rem -0.05rem 0.1rem #FFFFFF;
-
-      .el-picker-panel__sidebar {
-        position: absolute;
-        top: 0;
-        bottom: 0;
-        width: 110px;
-        border-right: 1px solid #e4e4e4;
-        box-sizing: border-box;
-        padding-top: 6px;
-        background-color: transparent;
-        overflow: auto;
-      }
-
-      .today {
-        div {
-          span {
-            color: #A20D13;
-            font-weight: bolder;
-          }
-        }
-      }
+    .el-button--text {
+      color: #A20D13;
+    }
 
-      .start-date, .end-date {
-        div {
-          span {
-            color: #A20D13;
-            font-weight: bolder;
-            background-color: #EBEBEB;
-            box-shadow: 0.05rem 0.05rem 0.1rem #AAA7A7, -0.05rem -0.05rem 0.1rem #FFFFFF;
-          }
-        }
-      }
+    .el-button--default {
+      color: #FFF;
+      background-color: #A20D13;
+    }
 
-      .el-picker-panel__footer {
-        border-top: 1px solid #e4e4e4;
-        padding: 4px;
-        text-align: right;
-        background-color: #ebebeb;
-        position: relative;
-        font-size: 0;
-      }
+    .el-button.is-plain:focus, .el-button.is-plain:hover {
+      border-color: transparent;
+      color: #FFF;
+    }
+  }
 
-      .el-picker-panel__shortcut {
-        display: block;
-        width: 90%;
-        border: 0;
-        border-radius: 5px;
-        background-color: transparent;
-        line-height: 28px;
-        font-size: 14px;
-        color: #606266;
-        padding-left: 12px;
-        text-align: left;
-        outline: 0;
-        cursor: pointer;
-        margin: 8px 5px;
-        box-shadow: 0.1rem 0.1rem 0.1rem #aaa7a7, -0.1rem -0.1rem 0.1rem #ffffff;
-      }
+  /deep/ .popper__arrow {
+    display: none;
+  }
 
-      .el-button--text {
-        color: #A20D13;
-      }
+  /deep/ .el-range-editor .el-range-input {
+    line-height: 1;
+    background-color: transparent;
+  }
 
-      .el-button--default {
-        color: #FFF;
-        background-color: #A20D13;
-      }
+  /deep/ .el-date-editor .el-range__icon {
+    color: inherit;
+  }
 
-      .el-button.is-plain:focus, .el-button.is-plain:hover {
-        border-color: transparent;
-        color: #FFF;
-      }
-    }
+  .el-button {
+    border: none;
+    background-color: #EBEBEB;
+    box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
+  }
 
-    /deep/ .popper__arrow {
-      display: none;
-    }
+  .el-button:active {
+    color: #FFF;
+    border: none;
+    background-color: #A20D13;
+    box-shadow: inset 0.1rem 0.1rem 0.2rem #6E090C, inset -0.1rem -0.1rem 0.2rem #c7494d;
+  }
 
-    /deep/ .el-range-editor .el-range-input {
-      line-height: 1;
-      background-color: transparent;
-    }
+  .el-button:focus, .el-button:hover {
+    color: #606266;
+    border-color: transparent;
+    /*background-color: #A20D13;*/
+  }
 
-    /deep/ .el-date-editor .el-range__icon {
-      color: inherit;
+  /deep/ .el-pagination {
+    .btn-prev {
+      background: inherit !important;
+      background-color: #EBEBEB;
+      box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
     }
 
-    .el-button {
-      border: none;
+    .btn-next {
       background-color: #EBEBEB;
       box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
     }
 
-    .el-button:active {
-      color: #FFF;
-      border: none;
-      background-color: #A20D13;
-      box-shadow: inset 0.1rem 0.1rem 0.2rem #6E090C, inset -0.1rem -0.1rem 0.2rem #c7494d;
-    }
+    /*
+          .btn-prev:active, .btn-next:active {
+            color: #FFF;
+            border: none;
+            background-color: #A20D13 !important;
+            box-shadow: inset 0.1rem 0.1rem 0.2rem #6E090C, inset -0.1rem -0.1rem 0.2rem #c7494d;
+          }*/
 
-    .el-button:focus, .el-button:hover {
-      color: #606266;
-      border-color: transparent;
-      /*background-color: #A20D13;*/
+    button:hover {
+      color: #c7494d;
     }
 
-    /deep/.el-pagination {
-      .btn-prev {
-        background: inherit !important;
-        background-color: #EBEBEB;
-        box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
-      }
-      .btn-next {
+    .el-pager {
+      li {
+        padding: 0 4px;
+        background: #FFF;
+        font-size: 13px;
+        min-width: 35.5px;
+        height: 28px;
+        line-height: 28px;
+        box-sizing: border-box;
+        text-align: center;
         background-color: #EBEBEB;
         box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
-      }/*
-      .btn-prev:active, .btn-next:active {
-        color: #FFF;
-        border: none;
-        background-color: #A20D13 !important;
-        box-shadow: inset 0.1rem 0.1rem 0.2rem #6E090C, inset -0.1rem -0.1rem 0.2rem #c7494d;
-      }*/
-
-      button:hover {
-        color: #c7494d;
       }
 
-      .el-pager {
-        li {
-          padding: 0 4px;
-          background: #FFF;
-          font-size: 13px;
-          min-width: 35.5px;
-          height: 28px;
-          line-height: 28px;
-          box-sizing: border-box;
-          text-align: center;
-          background-color: #EBEBEB;
-          box-shadow: 0.05rem 0.1rem 0.1rem #AAA7A7, -0.05rem -0.1rem 0.1rem #FFFFFF;
-        }
-
-        li.active, li:hover {
-          color: #c7494d;
-          cursor: default;
-        }
+      li.active, li:hover {
+        color: #c7494d;
+        cursor: default;
       }
     }
   }
+}
 </style>