Browse Source

fea: 增加策略功能

vera_min 5 months ago
parent
commit
81fc9fe196

+ 9 - 0
src/api/policy/index.ts

@@ -0,0 +1,9 @@
+import { get, post, del } from '/@/utils/request';
+export default {
+  getList: (data: any) => get('/policy/policy/list', data),
+  del: (id: number) => del(`/policy/policy/delete?id=${id}`),
+  add: (data: any) => post('/policy/policy/save', data),
+  edit: (data: any) => post('/policy/policy/save', data),
+  detail: (id: number) => get(`/policy/policy/detail?id=${id}`),
+  saveParam: (data: object) => post('/policy/policy/saveParam', data),
+} 

+ 297 - 0
src/views/policy/components/addItem.vue

@@ -0,0 +1,297 @@
+<template>
+  <el-dialog :title="ruleForm.id ? '修改控制策略' : '添加控制策略'" v-model="isShowDialog" width="650px">
+    <el-form
+      ref="ruleFormRef"
+      :model="ruleForm"
+      :rules="rules"
+      label-width="auto"
+      status-icon
+    >
+      <el-form-item label="标题" prop="title" style="width: 388px;">
+        <el-input clearable v-model="ruleForm.title" />
+      </el-form-item>
+      <el-form-item label="类型" prop="types">
+        <el-radio-group v-model="ruleForm.types">
+          <el-radio :label="1">手动</el-radio>
+          <el-radio :label="2">自动</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="说明" prop="description" style="width: 388px;">
+        <el-input  v-model="ruleForm.description" type="textarea" />
+      </el-form-item>
+      <el-form-item label="参数配置" prop="param">
+        <div style="width: 100%;">
+          <div class="param-wrap" v-for="(item, index) in ruleForm.param" :key="index">
+            <el-input  clearable placeholder="key" v-model="item.key" />
+            <el-input clearable placeholder="标题" v-model="item.title" />
+            <el-select v-model="item.types" placeholder="请选择数据类型">
+							<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
+								<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" />
+							</el-option-group>
+						</el-select>
+            <el-button v-if="index === ruleForm.param.length -1" @click="plusParam()">
+              <el-icon>
+                <ele-Plus />
+              </el-icon>
+            </el-button>
+            <el-button v-else @click="minusParam(index)">
+              <el-icon>
+                <ele-Minus />
+              </el-icon>
+            </el-button>
+          </div>
+        </div>
+      </el-form-item>
+    </el-form>
+    <div class="form-btn-wrap">
+      <el-button type="primary" @click="onSubmit(ruleFormRef)"> 提交 </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue'
+import api from '/@/api/device';
+import policyApi from '/@/api/policy';
+import axios from 'axios';
+import { ElMessage } from 'element-plus';
+import type { FormInstance } from 'element-plus'
+import { getToken } from "/@/utils/auth";
+import { v4 as uuid } from "uuid";
+
+interface RuleForm {
+  id: any
+  title: string
+  types: number
+  description: string,
+  param: any[],
+  flowId: string,
+}
+
+const emit = defineEmits(['getList']);
+
+const headers = {
+	Authorization: 'Bearer ' + getToken(),
+};
+const isShowDialog = ref(false);
+const ruleFormRef = ref<FormInstance>()
+const ruleForm = ref<RuleForm>({
+  id: null,
+  title: '',
+  types: 2,
+  description: '',
+  param: [ {
+    key: "",
+    title: "",
+    types: ""
+  }],
+  flowId: ''
+})
+
+// 数据类型下拉数据
+const typeData = ref([]);
+const validateParam = (rule: any, value: any, callback: any) => {
+  if (value === '') {
+    callback(new Error('请输入参数配置'))
+  } else if (value) {
+    verification(value).then(() => {
+      callback()
+    })
+    .catch((e:string) => {
+      callback(new Error(e))
+    })
+  } else {
+    callback()
+  }
+}
+
+const verification = (data:any) => {
+  return new Promise(function(resolve, reject) {
+    try {
+      data.forEach((item:any, indexs:number) => {
+        if (!item.key && !item.title && !item.types) {
+          throw '参数配置未完善,请进行完善'
+        }
+        if (!item.key) {
+          throw '第' + (indexs + 1) + '个参数配置的key未完善,请进行完善'
+        } else if (!item.title) {
+          throw '第' + (indexs + 1) + '个参数配置的标题未完善,请进行完善'
+        } else if (!item.types) {
+          throw '第' + (indexs + 1) + '个参数配置的数据类型未完善,请进行完善'
+        }
+
+      })
+    } catch (e) {
+      return reject(e)
+    }
+    resolve('成功')
+  })
+}
+
+const rules = ref({
+  title: [
+    { required: true, message: '请输入标题', trigger: 'blur' },
+  ],
+  types: [
+    {
+      required: true,
+      message: '请输入说明',
+      trigger: 'change',
+    },
+  ],
+  description: [
+    { required: true, message: '请输入描述', trigger: 'blur' },
+  ],
+  param: [{ required: true, validator: validateParam, trigger: 'change' }],
+})
+
+const onSubmit = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate();
+  if (!ruleForm.value.id) {
+      const id = uuid();
+      await axios.post(
+        import.meta.env.VITE_RULE_SERVER_URL + "/api/v1/rules/" + id,
+        {
+          ruleChain: {
+            id: id,
+            name: ruleForm.value.title,
+            root: true,
+            additionalInfo: {
+              description: ruleForm.value.description,
+              layoutX: "130",
+              layoutY: "220",
+            },
+          },
+          metadata: {
+            nodes: [{
+              "id": "node_2",
+              "additionalInfo": {
+                "layoutX": 606,
+                "layoutY": 424
+              },
+              "type": "log",
+              "name": "日志",
+              "debugMode": true,
+              "configuration": {
+                "jsScript": "return 'Incoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
+              }
+            }],
+            endpoints: [],
+            connections: [],
+          },
+        },
+        { headers }
+      );
+      ruleForm.value.flowId = id;
+	} else {
+    // 找到规则
+    const { data } = (await axios.get(import.meta.env.VITE_RULE_SERVER_URL + "/api/v1/rules/" + ruleForm.value.flowId, { headers }).catch(() => {
+      ElMessage.error("规则不存在");
+    })) as any;
+
+    // 修改名称和说明
+    data.ruleChain.name = ruleForm.value.title;
+    data.ruleChain.additionalInfo.description = ruleForm.value.description;
+
+    // 保存
+    await axios.post(import.meta.env.VITE_RULE_SERVER_URL + "/api/v1/rules/" + ruleForm.value.flowId, data, { headers });
+  }
+  const theApi = ruleForm.value.id ? policyApi.edit : policyApi.add;
+
+	await theApi(ruleForm.value);
+
+	ElMessage.success('操作成功');
+	resetForm();
+	isShowDialog.value = false;
+	emit('getList');
+}
+
+const resetForm = () => {
+  ruleFormRef.value && ruleFormRef.value.resetFields();
+
+  ruleForm.value.param = [{
+    key: "",
+    title: "",
+    types: ""
+  }]
+}
+
+const plusParam = () => {
+  ruleForm.value.param.push({
+    key: '',
+    title: '',
+    types: ''
+  })
+}
+
+const minusParam = (index:number) => {
+  ruleForm.value.param.splice(index, 1);
+}
+
+// 打开弹窗
+const openDialog = (row?: any) => {
+  resetForm();
+  if (row) {
+    policyApi.detail(row.id).then((res: any) => {
+      const { id, title, types, description, paramData, flowId } = res.data;
+      ruleForm.value = {
+        id: id,
+        title: title,
+        types: types,
+        description: description,
+        param: paramData,
+        flowId: flowId
+      }
+    });
+
+
+  }
+  api.product.getDataType({ status: -1 }).then((res: any) => {
+    const data:any = Object.values(res.dataType)
+    data.forEach((item:any, index:number) => {
+      if (index == 0) {
+        data[index]['label'] = '基础类型';
+        data[index]['options'] = item;
+      } else {
+        data[index]['label'] = '扩展类型';
+        data[index]['options'] = item.filter((i:any) => ['enum', 'array', 'object'].indexOf(i.type) === -1);
+        
+      }
+    });
+    typeData.value = data || [];
+  });
+  isShowDialog.value = true;
+
+};
+
+defineExpose({ openDialog })
+</script>
+
+<style lang="scss" scoped>
+  .param-wrap {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 10px;
+    .el-input,
+    .el-select {
+      width: 150px;
+    }
+  }
+  .el-icon {
+    margin-right: 0!important;
+  }
+  ::v-deep .el-dialog__body {
+    position: relative;
+  }
+  .form-btn-wrap {
+    position: absolute;
+    bottom: 20px;
+    right: 20px;
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    width: 100%;
+  }
+</style>

+ 132 - 0
src/views/policy/components/execute.vue

@@ -0,0 +1,132 @@
+<template>
+  <el-dialog title="执行" v-model="isShowDialog" width="600px">
+    <el-form>
+      <el-form-item>
+        <p>标题:{{currentPolicy.title}}</p>
+        <p class="ml30">执行类型:{{typesText[currentPolicy.types]}}</p>
+      </el-form-item>
+      <el-form-item >
+        <div style="width: 100%;">
+          <p>参数配置</p>
+          <div class="param-wrap" v-for="(item, index) in currentPolicy.paramData" :key="index">
+            <p class="mr6 label">{{item.title}}:</p>
+            <!-- int、long、float、double、string、boolean、date、timestamp -->
+            <el-input v-if="item.types === 'string'" clearable :placeholder="`请输入${item.title}`" v-model="item.value" />
+            <el-input-number v-if="['int',  'long'].indexOf(item.types) > -1" :placeholder="`请输入${item.title}`" v-model="item.value" :step="1" step-strictly />
+            <el-input-number v-if="['float',  'double'].indexOf(item.types) > -1" :placeholder="`请输入${item.title}`" v-model="item.value" :precision="2" />
+            <el-radio-group v-if="item.types === 'boolean'" v-model="item.value">
+              <el-radio :label="0">否</el-radio>
+              <el-radio :label="1">是</el-radio>
+            </el-radio-group>
+            <el-date-picker
+              v-if="item.types === 'date'"
+              v-model="item.value"
+              type="datetime"
+              :placeholder="`请选择${item.title}`"
+              format="YYYY/MM/DD hh:mm:ss"
+              value-format="YYYY-MM-DD h:m:s"
+            />
+            <el-date-picker
+              v-if="item.types === 'timestamp'"
+              v-model="item.value"
+              type="datetime"
+              :placeholder="`请选择${item.title}`"
+              format="YYYY/MM/DD hh:mm:ss"
+              value-format="x"
+            />
+          </div>
+        </div>
+      </el-form-item>
+    </el-form>
+    <div class="form-btn-wrap">
+      <el-button @click="onSubmit('handle')">手动执行 </el-button>
+      <el-button type="primary" @click="onSubmit('auto')"> 开始执行 </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue'
+import { ElMessage } from "element-plus";
+import api from '/@/api/policy';
+import axios from 'axios';
+import { getToken } from "/@/utils/auth";
+
+const isShowDialog = ref(false);
+const typesText = {
+  1: '手动',
+  2: '自动'
+};
+const headers = {
+  Authorization: "Bearer " + getToken(),
+};
+const currentPolicy = ref<any>({});
+
+const emit = defineEmits(['getList']);
+
+// 手动执行/开始执行
+const onSubmit = async () => {
+  const params:any = {};
+  currentPolicy.value.paramData.forEach((item:any) => {
+    params[item.key] = item.value
+  })
+  await api.saveParam({
+    id: currentPolicy.value.id,
+    param: {
+      ...params
+    }
+  })
+  await axios.post(`${import.meta.env.VITE_RULE_SERVER_URL}/api/v1/rules/${currentPolicy.value.flowId}/execute/${currentPolicy.value.types}`, params, { headers }).catch(() => {
+    ElMessage.error("规则不存在");
+  }) as any;
+
+	ElMessage.success('操作成功');
+	isShowDialog.value = false;
+	emit('getList');
+}
+
+// 打开弹窗
+const openDialog = (row?: any) => {
+  if (row) {
+    api.detail(row.id).then((res: any) => {
+      currentPolicy.value = res.data;
+    });
+  }
+  isShowDialog.value = true;
+};
+
+defineExpose({ openDialog });
+</script>
+
+<style lang="scss" scoped>
+  .param-wrap {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    margin-bottom: 10px;
+    .label {
+      width: 120px;
+      text-align: right;
+    }
+  }
+  .el-input-number,
+  .el-input {
+    width: 260px!important;
+  }
+  ::v-deep   .el-date-editor.el-input {
+    width: 260px!important;
+  }
+
+  ::v-deep .el-dialog__body {
+    position: relative;
+  }
+  .form-btn-wrap {
+    position: absolute;
+    bottom: 20px;
+    right: 20px;
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    width: 100%;
+  }
+</style>

+ 117 - 0
src/views/policy/index.vue

@@ -0,0 +1,117 @@
+<template>
+  <el-card shadow="nover" class="page page-wrapper">
+    <el-form :model="tableData.param" ref="queryRef" inline>
+      <el-form-item>
+        <el-button @click="onOpenEdit(queryRef)">
+          <el-icon>
+            <ele-Plus />
+          </el-icon>
+          添加
+        </el-button>
+        <!-- <el-button disabled type="primary" class="ml10" @click="onOpenAdd" v-auth="'add'">
+          <el-icon>
+            <ele-FolderAdd />
+          </el-icon>
+          批量执行
+        </el-button> -->
+      </el-form-item>
+    </el-form>
+    <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" v-loading="tableData.loading">
+      <!-- <el-table-column type="selection" width="55" align="center" /> -->
+      <el-table-column label="序号" align="center" prop="id" width="100" />
+      <el-table-column label="标题" prop="title" show-overflow-tooltip />
+      <el-table-column label="说明" prop="description" show-overflow-tooltip />
+      <el-table-column prop="status" label="状态" width="80" align="center" >
+        <template #default="scope">
+          <el-tag type="success" size="small" v-if="scope.row.status == 1">启用</el-tag>
+          <el-tag type="info" size="small" v-else>禁用</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="180" align="center" fixed="right">
+        <template #default="scope">
+          <el-button size="small" text type="primary" @click="onOpenRecord(scope.row)">执行</el-button>
+          <el-button size="small" text type="primary" @click="onOpenPolicyRecord(scope.row)">策略</el-button>
+          <el-button size="small" text type="warning" @click="onOpenEdit(scope.row)">修改</el-button>
+          <el-button size="small" text type="info" @click="onRowDel(scope.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="typeList" />
+    <Execute @getList="updeteList()" ref="executeRef" />
+    <AddPolicy @getList="updeteList()" ref="addPolicyRef" />
+  </el-card>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, defineAsyncComponent } from 'vue';
+import api from '/@/api/policy';
+import { ElMessage, ElMessageBox } from "element-plus";
+import { getToken } from "/@/utils/auth";
+
+const Execute = defineAsyncComponent(() => import("/@/views/policy/components/execute.vue"));
+const AddPolicy = defineAsyncComponent(() => import("/@/views/policy/components/addItem.vue"));
+
+const  tableData =  ref({
+  data: [],
+  total: 0,
+  loading: false,
+  param: {
+    pageNum: 1,
+    pageSize: 20,
+  }
+})
+const executeRef = ref();
+const addPolicyRef = ref();
+
+const onOpenEdit = (row:any) => {
+  addPolicyRef.value.openDialog(row);
+};
+const onOpenRecord = (row: any) => {
+  executeRef.value.openDialog(row);
+};
+
+const onRowDel = (row: any) => {
+  let msg = '你确定要删除所选数据?';
+  ElMessageBox.confirm(msg, '提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    api.del(row.id).then(() => {
+      ElMessage.success('删除成功');
+      updeteList();
+    });
+  }).catch(() => { });
+};
+
+const onOpenPolicyRecord = async (row: any) => {
+  localStorage.setItem("auth-tokens", `{"access_token":"${getToken()}"}`);
+  const params:any = {};
+  row.paramData.forEach((item: any) => {
+    params[item.key] = item.value || '';
+  });
+  const url = `/plugin/rule/index.html?${(new URLSearchParams(params)).toString()}#${row.flowId}`;
+  window.open(url);
+};
+
+
+const updeteList = ( ) => {
+  if(tableData.value.param.pageNum !== 1) tableData.value.param.pageNum = 1;
+  getList();
+}
+
+const getList = () => {
+  tableData.value.loading = true;
+  api.getList(tableData.value.param).then((res: any) => {
+    tableData.value.data = res.list;
+    tableData.value.total = res.Total;
+  }).finally(() => {
+    tableData.value.loading = false;
+  });
+};
+
+// 页面加载时
+onMounted(() => {
+  getList();
+});
+</script>