瀏覽代碼

新增规则修改日志页面,新增表达式ctrl+z撤销事件

baiyanting 1 年之前
父節點
當前提交
3c07707759

+ 103 - 4
src/assets/styles/dialog.less

@@ -55,7 +55,7 @@
     font-size: 14px;
     border: none;
     width: 108px;
-    min-height: 25px !important;
+    // min-height: 25px !important;
 
     &:hover {
       background-color: rgba(5, 187, 76, 0.6);
@@ -74,6 +74,108 @@
       width: 310px !important;
     }
   }
+  .custom-comp-form {
+    .el-select {
+      width: 100% !important;
+      max-width: 100% !important;
+      .el-input__inner {
+        width: 100% !important;
+        max-width: 100% !important;
+      }
+    }
+    .first-row {
+      .el-form-item {
+        .el-form-item__content {
+          display: flex;
+          flex-direction: column;
+          align-items: flex-start;
+        }
+      }
+      .el-switch {
+        display: block;
+      }
+
+      .first-row-second-col {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .el-form-item {
+          flex: 1;
+          padding-right: 5px;
+        }
+      }
+    }
+    .second-row {
+      .operator {
+        display: grid;
+        grid-template-columns: auto auto auto auto auto;
+      }
+      .buttons {
+        background-color: rgba(5, 187, 76, 0.2);
+        border: 1px solid #3b6c53;
+        color: #b3b3b3;
+        font-size: 14px;
+        width: 60px;
+        height: 50px;
+        margin-bottom: 10px;
+        &.is-round {
+          padding: 0 !important;
+        }
+        &:hover {
+          background-color: rgba(5, 187, 76, 0.5);
+          color: #ffffff;
+        }
+      }
+      .el-button + .el-button {
+        margin-left: 0;
+      }
+      .el-form-item {
+        .el-form-item__content {
+          display: flex;
+          flex-direction: column;
+          align-items: flex-start;
+        }
+      }
+    }
+    .el-tabs {
+      .el-tabs__item {
+        margin-right: 25px !important;
+      }
+      .el-tabs__header::before,
+      .el-tabs__header::after {
+        background: transparent;
+      }
+      .el-tabs--border-card > .el-tabs__header .el-tabs__item + .el-tabs__item {
+        margin-left: 0;
+      }
+      &.el-tabs--border-card {
+        background: transparent !important;
+        border-color: rgba(83, 98, 104, 0.2) !important;
+      }
+      &.el-tabs--border-card > .el-tabs__header {
+        background: transparent !important;
+        border-color: rgba(83, 98, 104, 0.2) !important;
+      }
+
+      &.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active,
+      &.el-tabs--border-card > .el-tabs__header .el-tabs__item:hover {
+        color: #05bb4c !important;
+        background: transparent !important;
+        border-right-color: transparent !important;
+        border-left-color: transparent !important;
+      }
+      &.el-tabs--border-card > .el-tabs__content {
+        padding: 5px;
+      }
+    }
+    .el-tag {
+      margin-bottom: 5px;
+      --el-tag-background-color: #1f4f2e;
+      --el-tag-border-color: #1f4f2e;
+      --el-tag-font-color: #05bb4c;
+      font-size: 14px;
+    }
+  }
 
   .el-radio-group {
     height: 28px;
@@ -639,9 +741,6 @@ background-color: rgba(30,90,163, .5);
   /*.el-table__cell{*/
   /*    color: ;*/
   /*}*/
-  .el-switch__action {
-    background: transparent !important;
-  }
 
   .mentues .el-table__body .el-table__cell {
     padding: 5px 0 !important;

+ 21 - 11
src/router/index.js

@@ -663,6 +663,17 @@ export const asyncRoutes = [
         },
         children: [
           {
+            path: "customConfig", // 预警配置
+            name: "customConfig",
+            component: () =>
+              import("@/views/IntegratedAlarm/alarmConfig/customConfig"),
+            meta: {
+              title: "预警配置",
+              icon: "",
+              permissions: ["jn_safe_ssbj"],
+            },
+          },
+          {
             path: "historyConfig", // 报警配置
             name: "historyConfig",
             component: () =>
@@ -673,17 +684,16 @@ export const asyncRoutes = [
               permissions: ["jn_alarmConfig_bjpz"],
             },
           },
-          //   {
-          //     path: "historyAnalyse", // 报警分析
-          //     name: "historyAnalyse",
-          //     component: () =>
-          //       import("@/views/IntegratedAlarm/reliability/historyAnalyse"),
-          //     meta: {
-          //       title: "报警分析",
-          //       icon: "",
-          //       permissions: ["jn_safe_ssbj"],
-          //     },
-          //   },
+          {
+            path: "logs", // 规则修改日志
+            name: "logs",
+            component: () => import("@/views/IntegratedAlarm/alarmConfig/logs"),
+            meta: {
+              title: "规则修改日志",
+              icon: "",
+              permissions: ["jn_alarmConfig_bjpz"],
+            },
+          },
         ],
       },
     ],

+ 842 - 0
src/views/IntegratedAlarm/alarmConfig/components/custom_components.vue

@@ -0,0 +1,842 @@
+<template>
+  <el-dialog v-model="isShow" width="60%" @close="closeDialog" destroy-on-close>
+    <el-form
+      ref="ruleFormRef"
+      :model="form"
+      :rules="rules"
+      label-position="top"
+      label-width="200px"
+      class="custom-comp-form"
+    >
+      <el-row
+        type="flex"
+        justify="space-between"
+        align="middle"
+        class="first-row"
+        :gutter="10"
+      >
+        <el-col :span="15" class="first-row-first-col">
+          <el-form-item prop="name">
+            <el-tag size="small">规则名称</el-tag>
+            <el-input v-model="form.name" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="9" class="first-row-second-col">
+          <el-form-item prop="category">
+            <el-tag size="small">报警类别</el-tag>
+            <el-select
+              v-model="form.category"
+              size="mini"
+              @change="categorychanged"
+            >
+              <el-option
+                key="1"
+                label="风机报警"
+                value="windturbine"
+              ></el-option>
+              <el-option
+                key="2"
+                label="升压站报警"
+                value="booststation"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="rank">
+            <el-tag size="small">报警级别</el-tag>
+            <el-select v-model="form.rank" size="mini">
+              <el-option
+                v-for="item in rankList"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="enable">
+            <el-tag size="small">是否启用</el-tag>
+            <el-switch
+              v-model="form.enable"
+              :active-value="true"
+              :inactive-value="false"
+              active-color="#13ce66"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="10" class="second-row">
+        <el-col :span="6">
+          <el-form-item prop="station">
+            <el-tag size="small">{{
+              form.category == "windturbine" ? "风场" : "电站"
+            }}</el-tag>
+            <el-select
+              v-model="form.stationId"
+              size="mini"
+              @change="stationChange"
+            >
+              <el-option
+                v-for="item in stationList"
+                :key="item.id"
+                :value="item.id"
+                :label="item.name"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="modelId" v-if="form.category == 'windturbine'">
+            <el-tag size="small">风机型号</el-tag>
+            <el-select
+              v-model="form.modelId"
+              size="mini"
+              @change="modelIdChange"
+            >
+              <el-option
+                v-for="item in state.modelList"
+                :key="item"
+                :value="item.id"
+                :label="item.aname"
+              ></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item
+            prop="relatedParts"
+            v-if="form.category == 'windturbine'"
+          >
+            <el-tag size="small">所属部件</el-tag>
+            <el-select v-model="form.relatedParts" style="width: 100%">
+              <el-option
+                v-for="item in state.relatedPartsList"
+                :key="item.nemCode"
+                :label="item.name"
+                :value="item.nemCode"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item prop="fault">
+            <el-tag size="small">预警类型</el-tag>
+            <el-select v-model="form.range" style="width: 100%">
+              <el-option
+                v-for="i in faultList"
+                :key="i.nemCode"
+                :value="i.nemCode"
+                :label="i.name"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="9">
+          <el-form-item prop="expression">
+            <el-tag size="small">表达式</el-tag>
+            <el-input
+              type="textarea"
+              rows="14"
+              v-model="form.expression"
+              id="expressionInput"
+              @keyup.z.stop="handle"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="9">
+          <el-tabs type="border-card">
+            <el-tab-pane label="AI测点">
+              <el-input v-model="state.AIPointSearch"> </el-input>
+              <el-table
+                size="mini"
+                fit
+                :show-header="false"
+                stripe
+                height="204"
+                :data="filterAIList"
+                @row-dblclick="rowDbclick"
+              >
+                <el-table-column prop="uniformCode" width="120" />
+                <el-table-column prop="name" />
+              </el-table>
+            </el-tab-pane>
+            <el-tab-pane label="DI测点">
+              <el-input v-model="state.DIPointSearch"> </el-input>
+              <el-table
+                size="mini"
+                fit
+                :show-header="false"
+                stripe
+                height="204"
+                :data="filterDIList"
+                @row-dblclick="rowDbclick"
+              >
+                <el-table-column prop="uniformCode" width="120" />
+                <el-table-column prop="name" />
+              </el-table>
+            </el-tab-pane>
+            <el-tab-pane label="函数">
+              <el-table
+                size="mini"
+                fit
+                :show-header="false"
+                stripe
+                height="204"
+                :data="func"
+                @row-dblclick="tabFuncRowClickHandle"
+              >
+                <el-table-column min-width="60%">
+                  <template #default="scope">
+                    <el-popover trigger="hover" placement="bottom">
+                      <p>描述:{{ scope.row.describe }}</p>
+                      <p>参数:{{ scope.row.param }}</p>
+                      <template #reference>
+                        <span size="medium" transition="fade-in-linear">{{
+                          scope.row.lab
+                        }}</span>
+                      </template>
+                    </el-popover>
+                  </template>
+                </el-table-column>
+                <el-table-column min-width="40%">
+                  <template #default="scope">
+                    <el-popover trigger="hover" placement="bottom">
+                      <p>描述:{{ scope.row.describe }}</p>
+                      <p>参数:{{ scope.row.param }}</p>
+                      <template #reference>
+                        <span size="medium" transition="fade-in-linear">{{
+                          scope.row.name
+                        }}</span>
+                      </template>
+                    </el-popover>
+                  </template>
+                </el-table-column>
+              </el-table>
+            </el-tab-pane>
+            <el-tab-pane label="运算符">
+              <div class="operator">
+                <el-button
+                  v-for="item in operator"
+                  :key="item"
+                  size="mini"
+                  class="buttons"
+                  @click="elInputSplit(item)"
+                >
+                  {{ item }}
+                </el-button>
+              </div>
+            </el-tab-pane>
+          </el-tabs>
+        </el-col>
+      </el-row>
+      <el-row :gutter="24">
+        <el-col :span="24">
+          <el-form-item prop="description">
+            <el-tag size="small">规则描述</el-tag>
+            <el-input
+              type="textarea"
+              rows="4"
+              v-model="form.description"
+              id="descriptionInput"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <span class="footerButton">
+        <el-button round size="mini" @click="closeDialog">取 消</el-button>
+        <el-button round size="mini" @click="submitForm(ruleFormRef)"
+          >确 定</el-button
+        >
+      </span>
+    </template>
+  </el-dialog>
+</template>
+<script setup>
+import {
+  ref,
+  onMounted,
+  reactive,
+  computed,
+  watch,
+  nextTick,
+  defineEmits,
+  defineProps,
+} from "vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import {
+  custombj_postSave,
+  fetch_electrical_point_ai,
+  fetch_electrical_point_di,
+  getStationinfo,
+  fetchPointList,
+  fetchPointListByBootst,
+} from "@/api/zhbj/index.js";
+import { useStore } from "vuex";
+const store = useStore();
+const emits = defineEmits(["close"]);
+const props = defineProps({
+  //是否显示
+  isVisible: {
+    type: Boolean,
+    defaule: false,
+  },
+  //表单
+  dialogOptions: {
+    type: Object,
+  },
+  //型号接口数据
+  modelListAll: {
+    type: Object,
+  },
+  //所属部件及预警类型
+  fetchListAll: {
+    type: Array,
+  },
+  //报警级别
+  rankList: {
+    type: Array,
+  },
+});
+watch(
+  () => props.isVisible,
+  (val, old) => {
+    nextTick(() => {
+      isShow.value = val;
+    });
+  },
+  {
+    deep: true,
+  }
+);
+watch(
+  () => props.dialogOptions,
+  (val, old) => {
+    nextTick(() => {
+      form.value = val;
+      if (val.id && val.id != "") {
+        if (val?.stationId.includes("FDC") || val?.stationId.includes("GDC")) {
+          getfetchPointList();
+        } else {
+          getfetchPointListByBootst();
+        }
+      }
+    });
+  },
+  {
+    deep: true,
+  }
+);
+watch(
+  () => props.dialogOptions?.stationId,
+  (val, old) => {
+    if (val && val != "") {
+      nextTick(async () => {
+        state.modelList = props.modelListAll[val];
+        if (val.includes("FDC")) {
+          state.relatedPartsList = props.fetchListAll?.fjbj;
+        } else if (val.includes("GDC")) {
+          state.relatedPartsList = props.fetchListAll?.gfbj;
+        } else {
+          state.relatedPartsList = [];
+        }
+      });
+    } else {
+      state.relatedPartsList = [];
+      state.modelList = [];
+    }
+  }
+);
+
+const isShow = ref(false);
+
+const form = ref({
+  id: "",
+  category: "windturbine", //报警类别
+  description: "", //描述
+  name: "", //规则名称
+  enable: true, //是否启用
+  expression: "", //表达式
+  range: "", //预警类型
+  rank: "", //报警级别
+  relatedParts: "", //所属部件
+  stationId: "", //场站id
+  deviceId: "",
+  electricalId: "",
+  lineId: "",
+  modelId: "",
+  projectId: "",
+  tag: "",
+  uniformCode: "",
+});
+const getExtraCharacters = (str1, str2) => {
+  let result = "";
+  if (str1 && str2) {
+    if (str1.length < str2.length) {
+      result = str2.slice(str1.length);
+    } else if (str1.length > str2.length) {
+      result = str1.slice(str2.length);
+    }
+  }
+
+  return result;
+};
+const newVal = ref("");
+watch(
+  () => form.value.expression,
+  (val, old) => {
+    console.log(val, old);
+    let a = getExtraCharacters(val, old);
+    newVal.value = a;
+  },
+  {
+    immediate: true,
+  }
+);
+// 场站列表/升压站列表
+const stationList = computed(() => {
+  if (form.value.category == "windturbine") {
+    return store.state.stationListAll;
+  } else {
+    return store.state.booststationList;
+  }
+});
+const toEmits = () => {
+  emits("close"); // 向父组件传递数据
+};
+const state = reactive({
+  modelList: [], //型号列表
+  relatedPartsList: [], //部件列表
+  AIPointList: [],
+  DIPointList: [],
+  AIPointSearch: "",
+  DIPointSearch: "",
+});
+const operator = [
+  "+",
+  "-",
+  "*",
+  "/",
+  "(",
+  ")",
+  ">",
+  ">=",
+  "<",
+  "<=",
+  "==",
+  "!=",
+  "&&",
+  "||",
+  "!",
+  "%",
+  "true",
+  "false",
+  ".",
+];
+const func = [
+  {
+    lab: "MR",
+    name: "移动极差",
+    param: "测点名,时间(秒)",
+    describe: "是指两个或多个连续样本值中最大值与最小值之差",
+    scene: "测点的移动极差超限报警",
+  },
+  {
+    lab: "MAR",
+    name: "均值极差",
+    param: "测点名,时间(秒)",
+    describe: "",
+    scene: "测点的均值极差计算",
+  },
+  {
+    lab: "RiseExceed",
+    name: "上升趋势",
+    param: "测点名,时间(秒),阈值",
+    describe: "取测点在给定的时间范围内数据上升的量是否超过阈值",
+    scene: "测点值的上升速度过快等",
+  },
+  {
+    lab: "Sustain",
+    name: "持续时间",
+    param: "表达式,时间(秒)",
+    describe:
+      "判定状态(表达式成立)持续的时间是否超过给定的时间判断状态持续的时间",
+    scene: "",
+  },
+  {
+    lab: "LastUpdateTime",
+    name: "最近数据时间",
+    param: "测点名",
+    describe: "",
+    scene: "判定离线,状态持续时间等",
+  },
+  {
+    lab: "abs",
+    name: "取绝对值",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "acos",
+    name: "反余弦",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "asin",
+    name: "反正弦",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "atan",
+    name: "反正切",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "atan2",
+    name: "xy坐标转为极坐标",
+    param: "x,y",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "ceiling",
+    name: "向上取整",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "cos",
+    name: "余弦",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "cosh",
+    name: "双曲线余弦",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "exp",
+    name: "欧拉数 e 的 double 次幂的值",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "floor",
+    name: "向下取整",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "log",
+    name: "自然对数",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "log10",
+    name: "底数为 10 的对数",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "max",
+    name: "比较最大值",
+    param: "double a, double b",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "min",
+    name: "比较最小值",
+    param: "double a, double b",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "pow",
+    name: "返回第一个参数的第二个参数次幂的值",
+    param: "double a, double b",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "round",
+    name: "返回最接近参数的 long,或int",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "sign",
+    name: "负数返回-1.0,整数返回1.0,0返回0.0",
+    param: "float f/double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "sin",
+    name: "三角正弦值",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "sinh",
+    name: "双曲线正弦",
+    param: "double x",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "sqrt",
+    name: "正平方根",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "tan",
+    name: "正切",
+    param: "double a",
+    describe: "",
+    scene: "",
+  },
+  {
+    lab: "tanh",
+    name: "双曲线正切",
+    param: "double x",
+    describe: "",
+    scene: "",
+  },
+  { lab: "PI", name: "圆周率", param: "", describe: "", scene: "" },
+  { lab: "E", name: "自然对数", param: "", describe: "", scene: "" },
+];
+const faultList = computed(() => props.fetchListAll.yj); //预警类型列表
+const ruleFormRef = ref(null);
+const rules = reactive({
+  name: [{ required: true, message: "请输入规则名称", trigger: "blur" }],
+  category: [{ required: true, message: "请选择报警类别", trigger: "change" }],
+  rank: [{ required: true, message: "请选择报警级别", trigger: "change" }],
+  stationId: [{ required: true, message: "请选择风场", trigger: "change" }],
+  expression: [
+    { required: true, message: "表达式不能为空", trigger: ["change"] },
+  ],
+});
+
+//categorychanged
+const categorychanged = async (val) => {
+  form.value.stationId = "";
+  form.value.modelId = "";
+  form.value.expression = "";
+  form.value.relatedParts = "";
+  state.AIPointList = [];
+  state.DIPointList = [];
+};
+//stationChange
+const stationChange = async (val) => {
+  if (form.value.category == "windturbine") {
+    form.value.modelId = "";
+    state.modelList = props.modelListAll[val];
+  } else {
+    getfetchPointListByBootst();
+  }
+};
+//modelIdChange
+const modelIdChange = async () => {
+  getfetchPointList();
+};
+// 根据场站和型号查询测点数据
+const getfetchPointList = async () => {
+  const { data } = await fetchPointList(
+    form.value.stationId,
+    form.value.modelId
+  );
+  if (Object.keys(data).length) {
+    state.AIPointList = data.ai.sort(function (a, b) {
+      return a.uniformCode - b.uniformCode;
+    });
+    state.DIPointList = data.di.sort(function (a, b) {
+      return a.uniformCode - b.uniformCode;
+    });
+  }
+};
+
+// 根据升压站查询测点数据
+const getfetchPointListByBootst = async () => {
+  const { data } = await fetchPointListByBootst(form.value.stationId);
+  if (Object.keys(data).length) {
+    state.AIPointList = data.ai.sort(function (a, b) {
+      return a.uniformCode - b.uniformCode;
+    });
+    state.DIPointList = data.di.sort(function (a, b) {
+      return a.uniformCode - b.uniformCode;
+    });
+  }
+};
+//筛选AI测点
+const filterAIList = computed(() =>
+  state.AIPointList?.filter(
+    (data) =>
+      !state.AIPointSearch ||
+      data.uniformCode.includes(state.AIPointSearch) ||
+      data.name.includes(state.AIPointSearch)
+  )
+);
+//筛选DI测点
+const filterDIList = computed(() =>
+  state.DIPointList?.filter(
+    (data) =>
+      !state.DIPointSearch ||
+      data.uniformCode.includes(state.DIPointSearch) ||
+      data.name.includes(state.DIPointSearch)
+  )
+);
+
+// 函数点击事件
+const tabFuncRowClickHandle = (row) => {
+  let elInput = document.getElementById("expressionInput");
+  let startPos = elInput.selectionStart; //第0个字符到选中的字符
+  let endPos = elInput.selectionEnd; //选中字符到末尾字符
+  if (startPos === undefined || endPos === undefined) return;
+  let txt = elInput.value;
+  let func;
+  if (
+    row.lab === "MR" ||
+    row.lab === "MAR" ||
+    row.lab === "RiseExceed" ||
+    row.lab === "Sustain" ||
+    row.lab === "LastUpdateTime"
+  ) {
+    func = row.lab + "()";
+  } else if (row.lab === "PI" || row.lab === "E") {
+    func = "Math." + row.lab;
+  } else {
+    func = "Math." + row.lab + "()";
+  }
+  // 将插值添加到选中光标位置
+  let result = txt.substring(0, startPos) + func + txt.substring(endPos);
+  elInput.value = result;
+  // 重新定义光标位置
+  elInput.focus();
+  if (row.lab === "PI" || row.lab === "E") {
+    elInput.selectionStart = startPos + func.length;
+    elInput.selectionEnd = startPos + func.length;
+  } else {
+    elInput.selectionStart = startPos + func.length - 1;
+    elInput.selectionEnd = startPos + func.length - 1;
+  }
+  form.value.expression = result; // 赋值给表单中的的字段
+};
+//rowDbclick
+const rowDbclick = (row) => {
+  elInputSplit(row);
+};
+// 表达式字符串拼接
+const elInputSplit = async (val) => {
+  let elInput = document.getElementById("expressionInput");
+  let startPos = elInput.selectionStart;
+  let endPos = elInput.selectionEnd;
+  if (startPos === undefined || endPos === undefined) return;
+  let txt = elInput.value;
+  let txtSplit = val.uniformCode || val;
+  let result = txt.substring(0, startPos) + txtSplit + txt.substring(endPos);
+  elInput.value = result;
+  elInput.focus();
+  elInput.selectionStart = startPos + txtSplit.length;
+  elInput.selectionEnd = startPos + txtSplit.length;
+  form.value.expression = result;
+};
+const handle = (e) => {
+  if (e.ctrlKey && e.key == "z") {
+    form.value.expression = form.value.expression.replace(newVal.value, "");
+  }
+};
+//提交
+const submitForm = async (formEl) => {
+  if (!formEl) return;
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      save();
+    } else {
+      console.log("error submit!", fields);
+    }
+  });
+};
+//保存
+const save = async () => {
+  const res = await custombj_postSave(form.value);
+  console.warn(res);
+  if (res.code != 200) {
+    ElMessage.error(res.msg);
+  } else {
+    ElMessage.success(`保存成功!`);
+    closeDialog();
+  }
+};
+//reset
+const resetForm = (formEl) => {
+  formEl.resetFields();
+};
+//confirm关闭
+const handleClose = () => {
+  ElMessageBox.confirm("确认关闭?")
+    .then(() => {
+      closeDialog();
+    })
+    .catch(() => {
+      // catch error
+    });
+};
+//关闭触发事件
+const closeDialog = () => {
+  resetForm(ruleFormRef.value);
+  form.value = {};
+  state.AIPointList = [];
+  state.DIPointList = [];
+  toEmits();
+};
+</script>
+<style lang="less" scoped>
+.col-box {
+  display: flex;
+  flex-direction: column;
+}
+
+.select-mini {
+  width: 120px;
+}
+
+.el-tabs__content {
+  padding: 0 !important;
+}
+
+.el-tabs--border-card {
+  -webkit-box-shadow: none;
+  box-shadow: none;
+}
+
+.border {
+  border: solid red 1px;
+}
+
+.el-table--mini td {
+  padding: 3px 0;
+}
+
+.el-button-group .el-button--primary {
+  border: none;
+}
+
+.el-form--label-top .el-form-item__label {
+  padding: 0;
+}
+</style>

+ 577 - 0
src/views/IntegratedAlarm/alarmConfig/customConfig/index.vue

@@ -0,0 +1,577 @@
+<template>
+  <div class="custom-config">
+    <div class="form-wrapper">
+      <div class="btns" style="margin-bottom: 10px">
+        <el-button class="buttons" size="mini" round @click="handleInsert">
+          新增记录
+        </el-button>
+        <el-button class="buttons" size="mini" round @click="export2Excel">
+          批量导出</el-button
+        >
+        <el-button class="buttons" size="mini" round @click="outExe">
+          模板下载</el-button
+        >
+        <el-upload
+          style="display: inline; margin-left: 10px"
+          :action="url + 'alertrule/import'"
+          :show-file-list="false"
+          :on-success="handleSuccess"
+          :on-progress="handleProgress"
+          :on-error="handleError"
+        >
+          <el-button
+            class="buttons"
+            size="mini"
+            round
+            @click="exportShow = true"
+          >
+            批量导入</el-button
+          >
+        </el-upload>
+      </div>
+      <div class="search-wrapper">
+        <el-select
+          v-model="query.category"
+          size="mini"
+          class="search-item"
+          popper-class="select"
+          @change="categorychanged"
+        >
+          <el-option key="1" label="风机报警" value="windturbine"></el-option>
+          <!-- <el-option
+            key="2"
+            label="升压站报警"
+            value="booststation"
+          ></el-option> -->
+          <el-option key="3" label="光伏报警" value="inverter"></el-option>
+        </el-select>
+        <el-select
+          v-model="query.wpId"
+          clearable
+          size="mini"
+          class="search-item"
+          :placeholder="'全部场站'"
+          popper-class="select"
+          @change="changeStation"
+        >
+          <el-option
+            v-for="item in stationList"
+            :key="item.id"
+            :value="item.id"
+            :label="item.aname"
+          ></el-option>
+        </el-select>
+        <el-select
+          v-model="query.relatedparts"
+          clearable
+          size="mini"
+          class="search-item"
+          placeholder="所属部件"
+        >
+          <el-option
+            v-for="item in fetchList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.nemCode"
+          >
+          </el-option>
+        </el-select>
+        <el-select
+          v-model="query.enabled"
+          clearable
+          size="mini"
+          class="search-item"
+          placeholder="是否可用"
+        >
+          <el-option
+            v-for="item in state.isEnabled"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          >
+          </el-option>
+        </el-select>
+        <el-select
+          v-model="query.modelId"
+          clearable
+          size="mini"
+          class="search-item"
+          placeholder="全部机型"
+          popper-class="select"
+        >
+          <el-option
+            v-for="item in modelList"
+            :key="item.id"
+            :value="item.id"
+            :label="item.aname"
+          ></el-option>
+        </el-select>
+        <!-- <el-select
+          v-model="query.rank"
+          clearable
+          size="mini"
+          class="search-item"
+          placeholder="全部级别"
+          popper-class="select"
+        >
+          <el-option
+            v-for="item in state.rankList"
+            :key="item.id"
+            :value="item.id"
+            :label="item.name"
+          ></el-option>
+        </el-select> -->
+        <el-input
+          placeholder="请输入名称"
+          v-model="query.name"
+          size="mini"
+          class="search-item"
+          clearable
+        ></el-input>
+        <el-button class="buttons" round size="mini" @click="getData"
+          >搜索</el-button
+        >
+      </div>
+    </div>
+    <div class="table-wrapper">
+      <div class="leftContent">
+        <span>{{ pageTitle }}</span>
+      </div>
+      <el-table
+        :data="state.tableData"
+        stripe
+        height="calc(100% - 42px - 45px)"
+      >
+        <el-table-column
+          v-for="item in state.tableHeader"
+          :key="item.code"
+          :label="item.title"
+          align="center"
+          :prop="item.code"
+          :minWidth="item.width ? item.width : 60"
+          show-overflow-tooltip
+        >
+          <template #default="scope">
+            <span v-if="item.code == 'rank'">
+              {{ rankConvert(scope.row.rank) }}
+            </span>
+            <span v-else-if="item.code == 'category'">
+              {{ categoryConvert(scope.row.category) }}
+            </span>
+            <span v-else-if="item.code == 'enable'">
+              {{ enabledConvert(scope.row.enable) }}
+            </span>
+            <span v-else>
+              {{ scope.row[item.code] }}
+            </span>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" align="center" width="100">
+          <template #default="scope">
+            <el-button
+              type="text"
+              style="color: #05bb4c"
+              size="mini"
+              @click="handleEditClick(scope.row)"
+              >编辑</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <div class="pagination">
+        <el-pagination
+          layout="total, sizes, prev, pager, next"
+          :current-page="query.pageNum"
+          :page-size="query.pageSize"
+          :page-sizes="[22, 50, 100, 200, 500]"
+          :total="total"
+          @size-change="
+            (value) => {
+              query.pageSize = value;
+              query.pageNum = 1;
+              getData();
+            }
+          "
+          @current-change="handlePageChange"
+        ></el-pagination>
+      </div>
+    </div>
+    <customcomponents
+      @close="dialogclose"
+      :isVisible="state.visible"
+      :dialogOptions="state.form"
+      :rankList="state.rankList"
+      :modelListAll="state.modelListAll"
+      :fetchListAll="state.fetchListAll"
+    />
+  </div>
+</template>
+<script setup>
+import {
+  custombj_fetchTableData,
+  getStationinfo,
+  fetchRelatePartAndAlarmType,
+  fetchModel,
+  fetchBooststation,
+  custombj_importTemplate,
+  getWpList,
+} from "@/api/zhbj/index.js";
+import { outExportExcel } from "@/tools/excel/exportExcel.js"; //引入文件
+import {
+  ref,
+  onMounted,
+  provide,
+  computed,
+  reactive,
+  watch,
+  nextTick,
+} from "vue";
+import { useStore } from "vuex";
+import { useRouter } from "vue-router";
+import { ElMessageBox, ElMessage } from "element-plus";
+import customcomponents from "@/views/IntegratedAlarm/alarmConfig/components/custom_components.vue";
+const pageTitle = "预警配置";
+const store = useStore();
+const token = { token: store.state.user.authToken };
+const url = process.env.VUE_APP_ALARM;
+const router = useRouter();
+const query = reactive({
+  pageNum: 1,
+  pageSize: 22,
+  name: "",
+  wpId: "",
+  rank: "",
+  modelId: "",
+  category: "windturbine",
+  enabled: "",
+  relatedparts: "",
+});
+const state = reactive({
+  tableData: [],
+  fetchListAll: [], //部件及预警类型
+  modelListAll: {}, //型号所有列表
+  isEnabled: [
+    {
+      id: false,
+      name: "否",
+    },
+    {
+      id: true,
+      name: "是",
+    },
+  ],
+  rankList: [
+    {
+      id: 1,
+      name: "低",
+    },
+    {
+      id: 2,
+      name: "中低",
+    },
+    {
+      id: 3,
+      name: "中",
+    },
+    {
+      id: 4,
+      name: "中高",
+    },
+    {
+      id: 5,
+      name: "高",
+    },
+  ],
+  visible: false,
+  form: {},
+  tableHeader: [
+    { title: "编码", code: "id" },
+    { title: "场站", code: "stationName", width: "100" },
+    { title: "机型", code: "modelId" },
+    { title: "规则名称", code: "name", width: "150" },
+    { title: "表达式", code: "expression", width: "150" },
+    { title: "描述", code: "description", width: "150" },
+    { title: "所属部件", code: "relatedPartsName" },
+    // { title: "级别", code: "rank" },
+    { title: "类型", code: "category" },
+    { title: "是否启用", code: "enable" },
+  ],
+  tableHeader1: [
+    { title: "编码", code: "id" },
+    { title: "升压站", code: "stationName" },
+    { title: "规则名称", code: "name" },
+    { title: "表达式", code: "expression" },
+    { title: "描述", code: "description" },
+    // { title: "级别", code: "rank" },
+    { title: "类型", code: "category" },
+    { title: "是否启用", code: "enable" },
+  ],
+});
+//型号列表
+const modelList = computed(() => {
+  if (query.wpId == "") {
+    return [];
+  } else {
+    return state.modelListAll[query.wpId];
+  }
+});
+//部件列表
+const fetchList = computed(() => {
+  if (query.wpId == "") {
+    return [];
+  } else {
+    if (query.wpId.includes("FDC")) {
+      return state.fetchListAll?.fjbj;
+    } else {
+      return state.fetchListAll?.gfbj;
+    }
+  }
+});
+
+// 场站列表/升压站列表
+const stationList = ref([]);
+let total = ref(0);
+onMounted(() => {
+  getWpArray();
+  getfetchRelatePart();
+  getequipmentmodel_list();
+  getData();
+});
+
+const dialogclose = () => {
+  state.visible = false;
+  state.form = {};
+  getData();
+};
+const getWpArray = async () => {
+  const { data } = await getWpList(query.category);
+  stationList.value = data;
+};
+const getData = async () => {
+  const { data } = await custombj_fetchTableData(query);
+  state.tableData = data.records;
+  total.value = data.total;
+};
+//所属部件
+const getfetchRelatePart = async () => {
+  const { data } = await fetchRelatePartAndAlarmType();
+  state.fetchListAll = data;
+  //   if (router && router.currentRoute.value.query.name) {
+  //     let queryName = router.currentRoute.value.query.name;
+  //     query.relatedparts = queryName;
+  //   }
+  //   getData();
+};
+// 机型
+const getequipmentmodel_list = async () => {
+  const { data } = await fetchModel();
+  state.modelListAll = data;
+};
+const handleEditClick = (row) => {
+  let obj = Object.assign({}, row);
+  state.form = obj;
+  state.visible = true;
+};
+const handleInsert = () => {
+  state.form = {
+    category: "windturbine",
+    enable: true,
+    stationId: "",
+    expression: "",
+  };
+  state.visible = true;
+};
+
+//changeStation
+const changeStation = async () => {
+  query.modelId = "";
+  query.relatedparts = "";
+};
+//categorychanged
+const categorychanged = async () => {
+  getWpArray();
+  this.clean();
+  getData();
+};
+
+// 分页导航
+const handlePageChange = (val) => {
+  query.pageNum = val;
+  getData();
+};
+
+// 批量导出
+const export2Excel = async () => {
+  let tableHeader = [];
+  let tableKey = [];
+  const { data } = await custombj_fetchTableData({
+    pageNum: 1,
+    pageSize: total.value,
+    ...query,
+  });
+  ElMessage.success(`导出成功!`);
+  if (query.category == "windturbine") {
+    tableHeader = state.tableHeader.map((item) => item.title);
+    tableKey = state.tableHeader.map((item) => item.code);
+  } else if (query.category == "booststation") {
+    tableHeader = state.tableHeader1.map((item) => item.title);
+    tableKey = state.tableHeader1.map((item) => item.code);
+  }
+  outExportExcel(
+    tableHeader,
+    tableKey,
+    data.records,
+    "自定义预警配置导出excel"
+  );
+};
+// 模板下载
+const outExe = () => {
+  custombj_importTemplate().then((response) => {
+    const link = document.createElement("a");
+    const blob = new Blob([response], {
+      type: "application/vnd.ms-excel",
+    });
+    link.style.display = "none";
+    link.href = URL.createObjectURL(blob);
+    link.download = "自定义报警模板.xlsx";
+    document.body.appendChild(link);
+    link.click();
+    document.body.removeChild(link);
+  });
+};
+// 批量导入
+const handleSuccess = (response) => {
+  if (response.code == "200") {
+    ElMessage.success("导入成功!");
+    getData();
+  } else {
+    ElMessage.error(response.msg);
+  }
+};
+// 批量导入中
+const handleProgress = (response) => {};
+// 批量导入失败
+const handleError = (response) => {
+  ElMessage.success("导入失败!");
+};
+// 清空字段
+const clean = () => {
+  query.modelId = "";
+  query.relatedparts = "";
+  query.wpId = "";
+  query.enabled = "";
+  query.rank = "";
+  query.name = "";
+  query.pageNum = 1;
+  state.tableData = [];
+  total.value = 0;
+};
+
+const rankConvert = (val) => {
+  if (val == 1) {
+    return "低";
+  } else if (val == 2) {
+    return "中低";
+  } else if (val == 3) {
+    return "中";
+  } else if (val == 4) {
+    return "中高";
+  } else if (val == 5) {
+    return "高";
+  }
+};
+// 类型
+const categoryConvert = (val) => {
+  if (val === "windturbine") {
+    return "风机";
+  } else if (val === "inverter") {
+    return "光伏";
+  }
+};
+// 状态
+const enabledConvert = (val) => {
+  if (val === false) {
+    return "停用";
+  } else if (val === true) {
+    return "启用";
+  }
+};
+</script>
+<style scoped lang="less">
+.custom-config {
+  height: 100%;
+  width: 100%;
+  padding: 0 20px;
+  padding-bottom: 10px;
+  .form-wrapper {
+    display: flex;
+    padding-top: 10px;
+    justify-content: space-between;
+    .search-wrapper::v-deep {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      font-family: Microsoft YaHei;
+      font-weight: 400;
+      color: #b3b3b3;
+      margin-bottom: 10px;
+      .search-item {
+        margin-right: 10px;
+        max-width: 190px;
+      }
+    }
+
+    .btns {
+      display: flex;
+      justify-content: flex-end;
+      margin-right: 10px;
+      //   position: absolute;
+      //   right: 0;
+      //   top: 53px;
+      .el-button + .el-button {
+        margin-left: 10px;
+      }
+    }
+
+    .buttons {
+      background-color: rgba(5, 187, 76, 0.2);
+      border: 1px solid #3b6c53;
+      color: #b3b3b3;
+      font-size: 14px;
+      &:hover {
+        background-color: rgba(5, 187, 76, 0.5);
+        color: #ffffff;
+      }
+    }
+  }
+}
+.table-wrapper {
+  height: calc(100% - 43px);
+  width: 100%;
+  .leftContent {
+    width: 242px;
+    height: 41px;
+    display: flex;
+    align-items: center;
+    background: url("~@/assets/imgs/title_left_bg1.png") no-repeat;
+
+    span {
+      font-size: 16px;
+      font-family: Microsoft YaHei;
+      font-weight: 400;
+      color: #05bb4c;
+      margin-left: 25px;
+    }
+  }
+  .el-table::v-deep {
+    .el-table__body-wrapper {
+      height: calc(100% - 45px) !important;
+    }
+  }
+  .pagination-wrapper ::v-deep {
+    text-align: right;
+    margin-top: 10px;
+  }
+}
+</style>

+ 9 - 4
src/views/IntegratedAlarm/alarmConfig/historyConfig/index.vue

@@ -94,10 +94,8 @@
       </div>
       <el-table
         :data="state.tableData"
-        :highlight-current-row="true"
-        border
+        stripe
         height="calc(100% - 52px - 42px)"
-        width="100%"
       >
         <el-table-column
           v-for="item in query.alarmType == 'windturbine' ||
@@ -266,6 +264,7 @@ const modelList = computed(() => {
 const isStation = computed(() => store.getters.isStation);
 
 let total = ref(0);
+const hisTable = ref();
 const getData = async () => {
   const { data: res } = await windturbinebj_fetchTableData(query);
   res.records.forEach((ele) => {
@@ -273,6 +272,7 @@ const getData = async () => {
     ele.resetTableName = ele.resetTable ? "是" : "否";
   });
   state.tableData = res.records;
+  console.log(hisTable.value);
   total.value = res.total;
 };
 
@@ -621,7 +621,12 @@ const relatePartConvert = (val) => {
       margin-left: 25px;
     }
   }
-  .pagination-wrapper :deep {
+  .el-table::v-deep {
+    .el-table__body-wrapper {
+      height: calc(100% - 45px) !important;
+    }
+  }
+  .pagination-wrapper ::v-deep {
     text-align: right;
     margin-top: 10px;
   }

+ 241 - 0
src/views/IntegratedAlarm/alarmConfig/logs/index.vue

@@ -0,0 +1,241 @@
+<template>
+  <div class="logs">
+    <div class="form-wrapper">
+      <div class="search-wrapper">
+        <el-select
+          v-model="value"
+          style="margin-right: 10px"
+          clearable
+          size="mini"
+          placeholder="类型"
+        >
+          <el-option
+            v-for="item in options"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+            :disabled="item.disabled"
+          />
+        </el-select>
+        <el-input
+          v-model="input"
+          clearable
+          size="mini"
+          placeholder="名称检索"
+          style="width: 200px; margin-right: 10px"
+        />
+      </div>
+      <div class="btns">
+        <el-button round size="mini" class="buttons" @click="search"
+          >搜 索</el-button
+        >
+      </div>
+    </div>
+    <div class="table-wrapper">
+      <div class="leftContent">
+        <span>{{ pageTitle }}</span>
+      </div>
+      <el-table
+        :data="tableData"
+        height="calc(100% - 57px - 40px)"
+        stripe
+        style="width: 100%"
+      >
+        <el-table-column
+          prop="ruleName"
+          label="规则名称"
+          width="300"
+          align="center"
+        />
+        <el-table-column
+          prop="ruleType"
+          label="规则类型"
+          width="120"
+          align="center"
+        />
+        <el-table-column
+          prop="stationName"
+          label="场站名称"
+          width="140"
+          align="center"
+        />
+        <el-table-column
+          prop="modelId"
+          label="风机型号"
+          width="200"
+          align="center"
+        />
+        <el-table-column label="更改内容" show-overflow-tooltip>
+          <template #default="scope">
+            <div v-for="item in scope.row.infoList" :key="item">
+              {{ item.fieldName }} :更改前: {{ item.beforValue }}
+              <el-divider direction="vertical" /> 更改后:
+              {{ item.afterValue }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="updateTimeName"
+          label="操作时间"
+          width="150"
+          align="center"
+        />
+        <el-table-column
+          prop="updateType"
+          label="操作类型"
+          width="100"
+          align="center"
+        />
+        <el-table-column
+          prop="updateUser"
+          label="操作人"
+          width="100"
+          align="center"
+        />
+      </el-table>
+      <div class="pagination">
+        <el-pagination
+          layout="total,sizes, prev, pager, next"
+          hide-on-single-page
+          :current-page="query.page"
+          :page-size="query.limit"
+          :page-sizes="[22, 50, 100, 200, 500]"
+          :total="query.pageTotal"
+          @current-change="handlePageChange"
+          @size-change="
+            (value) => {
+              query.limit = value;
+              query.page = 1;
+              logsList();
+            }
+          "
+        ></el-pagination>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, reactive, nextTick, watch, computed } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { fetchruleventLogs } from "@/api/zhbj/index.js";
+import dayjs from "dayjs";
+onMounted(() => {
+  logsList();
+});
+const pageTitle = "规则修改日志";
+const query = reactive({
+  page: 1,
+  limit: 22,
+  pageTotal: null,
+});
+let tableData = ref(null);
+let input = ref("");
+let value = ref("");
+let options = ref([
+  {
+    label: "自定义报警",
+    value: "自定义报警",
+  },
+  {
+    label: "风机报警",
+    value: "风机报警",
+  },
+  {
+    label: "升压站报警",
+    value: "升压站报警",
+  },
+]);
+//
+const search = () => {
+  logsList();
+};
+const logsList = async () => {
+  const { data } = await fetchruleventLogs(
+    query.page,
+    query.limit,
+    input.value,
+    value.value
+  );
+  data.records.forEach((ele) => {
+    ele.updateTimeName = dayjs(ele.updateTime).format("YYYY-MM-DD:HH:mm:ss");
+  });
+  query.pageTotal = data.total;
+  tableData.value = data.records;
+};
+// 分页导航
+const handlePageChange = (val) => {
+  query.page = val;
+  logsList();
+};
+</script>
+<style lang="less" scoped>
+.logs {
+  height: 100%;
+  width: 100%;
+  padding: 0 20px;
+  padding-bottom: 10px;
+  .form-wrapper {
+    display: flex;
+    padding-top: 10px;
+    padding-bottom: 10px;
+    .search-wrapper::v-deep {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      font-family: Microsoft YaHei;
+      font-weight: 400;
+      color: #b3b3b3;
+
+      .search-item {
+        margin-right: 10px;
+        max-width: 190px;
+      }
+    }
+
+    .btns {
+      display: flex;
+      align-items: center;
+    }
+
+    .buttons {
+      background-color: rgba(5, 187, 76, 0.2);
+      border: 1px solid #3b6c53;
+      color: #b3b3b3;
+      font-size: 14px;
+      &:hover {
+        background-color: rgba(5, 187, 76, 0.5);
+        color: #ffffff;
+      }
+    }
+  }
+}
+.table-wrapper {
+  height: calc(100% - 43px);
+  width: 100%;
+  .leftContent {
+    width: 242px;
+    height: 41px;
+    display: flex;
+    align-items: center;
+    background: url("~@/assets/imgs/title_left_bg1.png") no-repeat;
+
+    span {
+      font-size: 16px;
+      font-family: Microsoft YaHei;
+      font-weight: 400;
+      color: #05bb4c;
+      margin-left: 25px;
+    }
+  }
+  .el-table::v-deep {
+    .el-table__body-wrapper {
+      height: calc(100% - 45px) !important;
+    }
+  }
+  .pagination-wrapper ::v-deep {
+    text-align: right;
+    margin-top: 10px;
+  }
+}
+</style>