Quellcode durchsuchen

fea: 数据中心增加指标管理功能

vera_min vor 5 Monaten
Ursprung
Commit
e010e6b581

+ 17 - 1
src/api/datahub/index.ts

@@ -1,3 +1,11 @@
+/*
+ * @Author: vera_min vera_min@163.com
+ * @Date: 2025-04-14 16:36:08
+ * @LastEditors: vera_min vera_min@163.com
+ * @LastEditTime: 2025-05-07 20:56:56
+ * @FilePath: /sagoo-admin-ui/src/api/datahub/index.ts
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
 import { get, post, del, put, file } from '/@/utils/request';
 
 export default {
@@ -94,5 +102,13 @@ export default {
       deviceAlarmLevelCount: (dateType: 'year' | 'month' | 'day', date: string) => get('/analysis/deviceAlarmLevelCount', { dateType, date }),
       // 产品数量统计
       productCount: () => get('/analysis/productCount'),
-   }
+   },
+   // 计算指标管理
+   calculationIndicator: {
+      getList: (params: object) => get('/compute/list', params),
+      add: (data: object) => post('/compute/add', data),
+      delete: (id: number) => del('/compute/del', { id }),
+      edit: (data: object) => put('/compute/edit', data),
+      deploy: (data: object) => put('/compute/publish', data),
+   },
 }

+ 275 - 0
src/views/system/datahub/modeling/calculationIndicator.vue

@@ -0,0 +1,275 @@
+<!-- 计算指标管理 -->
+<template>
+  <div class="page page-full model-detail-wrap">
+    <div class="content">
+      <div class="cont_box">
+        <div class="title">模型标识:{{ detail.key }}</div>
+        <div class="title" style="margin-left: 20px">模型表名:{{ detail.name }}</div>
+        <div class="pro-status"><span :class="developer_status == 1 ? 'on' : 'off'"></span>{{ developer_status == 1 ? '已发布' : '未发布' }}</div>
+        <!-- <div class="pro-option" v-auth="'startOrStop'" @click="CkOption">{{ developer_status == 1 ? '停用' : '发布' }}</div> -->
+      </div>
+    </div>
+
+    <div class="content-box page page-full-part">
+      <div class="wu-box">
+        <div class="system-user-search mb15">
+          <el-form :model="tableData.param" ref="queryRef" inline @submit.prevent @keyup.enter="typeList">
+            <el-form-item label="指标标识" prop="name">
+              <el-input v-model="tableData.param.keyWord" placeholder="请输入指标标识" clearable style="width: 220px" />
+            </el-form-item>
+
+            <el-form-item>
+              <el-button type="primary" class="ml10" @click="typeList">
+                <el-icon>
+                  <ele-Search />
+                </el-icon>
+                查询
+              </el-button>
+              <el-button @click="resetQuery()">
+                <el-icon>
+                  <ele-Refresh />
+                </el-icon>
+                重置
+              </el-button>
+              <el-button type="primary" class="ml10" @click="onOpenAdd" v-auth="'add'" v-if="developer_status == 0">
+                <el-icon>
+                  <ele-FolderAdd />
+                </el-icon>
+                新增计算指标模型
+              </el-button>
+              <el-button type="success" class="ml10" @click="onPublish()">
+              <el-icon>
+                <ele-Upload />
+              </el-icon>
+              发布
+            </el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+
+        <el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
+          <el-table-column label="ID" align="center" prop="id" width="100" v-col="'id'" />
+          <el-table-column label="指标名称" prop="name" width="140" show-overflow-tooltip v-col="'key'" />
+          <el-table-column label="指标标识" prop="key" width="140" show-overflow-tooltip v-col="'key'" />
+          <el-table-column label="计算公式" prop="formula" show-overflow-tooltip v-col="'dataType'" />
+          <el-table-column label="数据类型" prop="types" width="85" show-overflow-tooltip v-col="'dataType'" />
+          <el-table-column label="指标说明" prop="description" width="140" show-overflow-tooltip v-col="'name'" />
+          <el-table-column prop="createdAt" label="创建时间" align="center" width="160" v-col="'createdAt'"></el-table-column>
+          <el-table-column label="操作" width="100" align="center" fixed="right">
+            <template #default="scope">
+              <el-button size="small" text type="warning" @click="onOpenEdit(scope.row)" v-if="developer_status == 0" v-auth="'edit'">修改</el-button>
+              <el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-if="developer_status == 0" v-auth="'del'">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination
+					v-show="tableData.data.length > 0"
+					:total="tableData.total"
+					v-model:page="tableData.param.pageNum"
+					v-model:limit="tableData.param.pageSize"
+					@pagination="typeList"
+				/>
+      </div>
+    </div>
+    <AddOrEditIndictor ref="editDicRef" @typeList="updateList" />
+  </div>
+</template>
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+import { ElMessageBox, ElMessage } from 'element-plus';
+import { useRoute } from 'vue-router';
+import AddOrEditIndictor from './component/addOrEditIndictor.vue';
+import api from '/@/api/datahub';
+
+const editDicRef = ref();
+const route = useRoute();
+const detail = ref<any>({});
+const developer_status = ref(0);
+const tableData = ref(
+  {
+    data: [],
+    total: 0,
+    loading: false,
+    param: {
+      pageNum: 1,
+      pageSize: 20,
+      tid: route.params.id as string,
+      keyWord: '',
+    },
+  }
+)
+
+const typeList = () => {
+  tableData.value.loading = true;
+  api.calculationIndicator.getList(tableData.value.param).then((res: any) => {
+    tableData.value.data = res.list;
+    tableData.value.total = res.Total;
+  }).finally(() => (tableData.value.loading = false));
+};
+
+const resetQuery = () => {
+  tableData.value.param.keyWord = ''
+  typeList();
+};
+
+const onRowDel = (row: any) => {
+  let msg = '你确定要删除所选数据?';
+  let ids: number[] = [];
+  if (row) {
+    msg = `此操作将永久删除数据节点:“${row.name}”,是否继续?`;
+    ids = row.id;
+  } else {
+    // ids = ids;
+  }
+  if (ids.length === 0) {
+    ElMessage.error('请选择要删除的数据。');
+    return;
+  }
+  ElMessageBox.confirm(msg, '提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning',
+  })
+    .then(() => {
+      api.calculationIndicator.delete(ids).then(() => {
+        ElMessage.success('删除成功');
+        typeList();
+      });
+    })
+    .catch(() => { });
+};
+
+const onPublish = () => {
+  ElMessageBox.confirm('是否发布?', '提示', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    api.calculationIndicator.deploy({ dataTemplateKey: detail.value.key }).then((res: any) => {
+      ElMessage.success('操作成功');
+      typeList();
+    });
+  })
+  .catch(() => { });
+
+};
+
+// 打开修改数据源弹窗
+const onOpenEdit = (row: any) => {
+  editDicRef.value.openDialog(row);
+};
+
+const onOpenAdd = () => {
+  editDicRef.value.openDialog(null, detail.value.key);
+};
+
+// const CkOption = () => {
+//   //检测是否需要设置关联
+//   api.template.relation_check(route.params.id).then((res: any) => {
+//     if (res.yes && developer_status.value == 0) {
+//       let ids = {
+//         id: route.params.id,
+//       }
+//       relationRef.value.openDialog(ids);
+//     } else {
+//       if (developer_status.value == 1) {
+//         api.tnode.undeploy({ id: route.params.id }).then((res: any) => {
+//           ElMessage.success('操作成功');
+//           developer_status.value = 0;
+//         });
+//       } else {
+//         api.tnode.deploy({ id: route.params.id }).then((res: any) => {
+//           ElMessage.success('操作成功');
+//           developer_status.value = 1;
+//         });
+//       }
+//     }
+//   });
+// };
+
+const updateList = () => {
+  tableData.value.param.pageNum = 1;
+  tableData.value.param.keyWord = '';
+  typeList();
+}
+
+onMounted(() => {
+  const ids = route.params?.modelId as string;
+  api.template.detail(ids).then((res: any) => {
+    detail.value = res.data;
+    developer_status.value = res.data.status
+  });
+
+  typeList();
+});
+</script>
+
+<style lang="scss" scoped>
+.content {
+  background: #fff;
+  width: 100%;
+  padding: 20px;
+}
+
+.content-box {
+  background: #fff;
+  width: 100%;
+  padding: 20px;
+  margin-top: 15px;
+}
+
+.cont_box {
+  display: flex;
+}
+
+.cont_box .title {
+  font-size: 18px;
+}
+
+.cont_box .pro-status {
+  line-height: 30px;
+  margin-left: 30px;
+}
+
+.cont_box .pro-status .on {
+  background: #52c41a;
+}
+
+.cont_box .pro-status .off {
+  background: #c41a1a;
+}
+
+.cont_box .pro-status span {
+  position: relative;
+  top: -1px;
+  display: inline-block;
+  width: 6px;
+  height: 6px;
+  vertical-align: middle;
+  border-radius: 50%;
+  margin-right: 5px;
+}
+
+.cont_box .pro-option {
+  line-height: 30px;
+  margin-left: 10px;
+  color: #1890ff;
+  cursor: pointer;
+}
+
+.content-box .pro-box {
+  display: flex;
+  padding: 10px;
+}
+
+.content-box .pro-box .protitle {
+  font-size: 18px;
+  font-weight: bold;
+  line-height: 35px;
+}
+
+.content-box .pro-box .buttonedit {
+  border: 0px;
+  color: #1890ff;
+}
+</style>

+ 285 - 0
src/views/system/datahub/modeling/component/addOrEditIndictor.vue

@@ -0,0 +1,285 @@
+<template>
+	<div class="system-edit-dic-container">
+		<el-dialog :title="(ruleForm.id ? '修改' : '新增') + '计算指标模型'" v-model="isShowDialog" width="700px">
+			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="110px">
+				<el-form-item label="选字段" prop="key">
+					<el-select style="width: 520px;" v-model="ruleForm.key" filterable placeholder="请选择数据类型">
+						<el-option v-for="item in nodeData" :key="item.key" :label="item.name" :value="item.key" />
+					</el-select>
+				</el-form-item>
+
+        <el-form-item label="指标名称" prop="name">
+					<el-input style="width: 520px;"  v-model="ruleForm.name" placeholder="请输入指标名称" />
+				</el-form-item>
+
+        <el-form-item label="指标说明" prop="description">
+					<el-input style="width: 520px;"  v-model="ruleForm.description" type="textarea" placeholder="请输入内容"></el-input>
+				</el-form-item>
+
+        <el-form-item label="计算公式" prop="formula">
+          <el-row class="flex" style="width: 100%;">
+            <el-input style="width: 430px;" disabled v-model="ruleForm.formula" placeholder="请输入计算公式"></el-input>
+            <el-button @click="showformulaFormDialog" type="primary" class="ml10">
+              <el-icon>
+                <ele-Edit />
+              </el-icon>
+              编辑
+            </el-button>
+          </el-row>
+
+				</el-form-item>
+
+				<el-form-item label="数据类型" prop="types">
+					<el-select style="width: 520px;" v-model="ruleForm.types" filterable placeholder="请选择数据类型">
+						<el-option v-for="item in tabData" :key="item.value" :label="item.label" :value="item.value" />
+					</el-select>
+				</el-form-item>
+
+        <el-form-item label="精度" prop="accuracy" v-if="ruleForm.types == 'float' || ruleForm.types == 'double'">
+          <el-input style="width: 520px;" v-model="ruleForm.accuracy" placeholder="请输入精度" />
+        </el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button @click="onCancel">取 消</el-button>
+					<el-button type="primary" @click="onSubmit">{{ ruleForm.id  ? '修 改' : '添 加' }}</el-button>
+				</span>
+			</template>
+		</el-dialog>
+
+    <el-dialog title="编辑计算公式" v-model="isShowFormulaDialog" width="600px">
+			<el-form :model="formulaForm" ref="formulaFormRef" :rules="formulaFormRules" label-width="110px">
+
+        <el-form-item label="运算符号">
+          <div class="symbol-list-wrap">
+            <div @click="assembleItem(item)" class="symbol-item-wrap" v-for="(item, index) in symbolList" :key="index">{{ item }}</div>
+          </div>
+				</el-form-item>
+
+        <el-form-item label="设备属性" prop="key">
+					<el-select @change="handleSelect" style="width: 520px;" v-model="formulaForm.key" filterable placeholder="请选择数据类型">
+						<el-option v-for="item in nodeData" :key="item.key" :label="item.name" :value="item.key" />
+					</el-select>
+				</el-form-item>
+
+        <el-form-item label="当前公式" prop="formula">
+          <el-input ref="formulaRef" @blur="handleBlur" style="width: 520px;" v-model="formulaForm.formula" placeholder="请输入公式" />
+        </el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button type="primary" @click="onSubmitFormulaForm">确定</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+wa1
+<script lang="ts" setup>
+import { ref, unref } from 'vue';
+import { useRoute } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import api from '/@/api/datahub';
+
+const emit = defineEmits(['typeList']);
+
+const validateAccuracy = (rule: any, value: any, callback: any) => {
+  if (['float', 'double'].indexOf(ruleForm.value.types) > -1  && !value) {
+    callback(new Error('请输入精度'))
+  } else {
+    callback()
+  }
+}
+
+const route = useRoute();
+const symbolList = ref([
+  '+', '-', '*', '/', '(', ')', '&', '^', '|', '~', '<<', '>>'
+])
+
+
+const cursorIndex = ref(0);
+const formulaRef = ref(null);
+const formRef = ref<HTMLElement | null>(null);
+const formulaFormRef = ref<HTMLElement | null>(null);
+const isShowDialog = ref(false);
+const isShowFormulaDialog = ref(false);
+const tabData = ref([
+  {
+    label: 'int(整数型)',
+    value: 'int',
+  },
+  {
+    label: 'long(长整数型)',
+    value: 'long',
+  },
+  {
+    label: 'float(单精度浮点型)',
+    value: 'float',
+  },
+  {
+    label: 'double(双精度浮点型)',
+    value: 'double',
+  }
+])
+
+const nodeData = ref([]);
+const formulaForm = ref({
+  key: '',
+  formula: '',
+  })
+
+const ruleForm = ref({
+  id: 0,
+  key: '',
+  name: '',
+  description: '',
+  formula: '',
+  types: '',
+  accuracy: null,
+  dataTemplateKey: ''
+})
+const rules = ref({
+  key: [{ required: true, message: '选字段不能为空', trigger: 'blur' }],
+  name: [{ required: true, message: '指标名称不能为空', trigger: 'blur' }],
+  description: [{ required: true, message: '指标说明不能为空', trigger: 'blur' }],
+  formula: [{ required: true, message: '计算公式不能为空', trigger: 'blur' }],
+  types: [{ required: true, message: '数据类型不能为空', trigger: 'blur' }],
+  accuracy: [{ required: true, validator: validateAccuracy, trigger: 'change' }],
+});
+
+const formulaFormRules = ref({
+  key: [{ required: true, message: '选字段不能为空', trigger: 'blur' }],
+  formula: [{ required: true, message: '计算公式不能为空', trigger: 'blur' }],
+});
+
+const handleBlur = () => {
+  if (!formulaRef.value) return;
+  cursorIndex.value = formulaRef.value.$el.querySelector('input').selectionStart;
+}
+
+const assembleItem = (item: any) => {
+  if (cursorIndex.value === 0 && !formulaForm.value.formula) {
+    formulaForm.value.formula += item;
+  } else {
+    formulaForm.value.formula = formulaForm.value.formula.substring(0, cursorIndex.value) + item + formulaForm.value.formula.substring(cursorIndex.value);
+  }
+}
+
+const handleSelect = (value: any) => {
+  formulaForm.value.formula += value;
+}
+
+const showformulaFormDialog = () => {
+  formulaForm.value.key = ruleForm.value.key;
+  if(ruleForm.value.formula) {
+    formulaForm.value.formula = ruleForm.value.formula;
+
+  } else {
+    formulaForm.value.formula = ruleForm.value.key;
+    
+  }
+  isShowFormulaDialog.value = true;
+}
+
+// 打开弹窗
+const openDialog = (row: any, dataTemplateKey:string) => {
+  resetForm();
+  if (row && row.id) {
+    ruleForm.value = row;
+  }
+  if (dataTemplateKey) {
+    ruleForm.value.dataTemplateKey = dataTemplateKey;
+  }
+  getNodeList();
+
+  isShowDialog.value = true;
+
+};
+
+
+// 获取数据模型节点列表
+const getNodeList = () => {
+  api.tnode.getList({
+    tid: route.params.modelId
+  }).then((res: any) => {
+    nodeData.value = res.list;
+  });
+};
+const resetForm = () => {
+  ruleForm.value = {
+    id: 0,
+    key: '',
+    name: '',
+    description: '',
+    formula: '',
+    types: '',
+    accuracy: null,
+    dataTemplateKey: ''
+  };
+};
+// 关闭弹窗
+const closeDialog = () => {
+  isShowDialog.value = false;
+};
+// 取消
+const onCancel = () => {
+  closeDialog();
+};
+
+// 计算公式编辑弹窗
+const onSubmitFormulaForm = () => {
+  const formWrap = unref(formulaFormRef) as any;
+  if (!formWrap) return;
+  formWrap.validate((valid: boolean) => {
+    if (valid) {
+      ruleForm.value.formula = formulaForm.value.formula;
+      ruleForm.value.key = formulaForm.value.key;
+      isShowFormulaDialog.value = false;
+    }
+  });
+};
+const onSubmit = () => {
+  const formWrap = unref(formRef) as any;
+  if (!formWrap) return;
+  formWrap.validate((valid: boolean) => {
+    if (valid) {
+      if (ruleForm.value.id !== 0) {
+        //修改
+        api.calculationIndicator.edit(ruleForm.value).then(() => {
+          ElMessage.success('计算指标模型修改成功');
+          closeDialog(); // 关闭弹窗
+          emit('typeList');
+        });
+      } else {
+        //添加
+        api.calculationIndicator.add(ruleForm.value).then(() => {
+          ElMessage.success('计算指标模型添加成功');
+          closeDialog(); // 关闭弹窗
+          emit('typeList');
+        });
+      }
+    }
+  });
+};
+
+defineExpose({ openDialog })
+</script>
+
+<style lang="scss" scoped>
+.symbol-list-wrap {
+  display: flex;
+  flex-wrap: wrap;
+  .symbol-item-wrap {
+    width: 100px;
+    height: 40px;
+    line-height: 40px;
+    text-align: center;
+    font-size: 16px;
+    
+    border: 1px solid #f1f1f1;
+    margin-bottom: 12px;
+    margin-right: 12px;
+  }
+}
+</style>
+

+ 24 - 4
src/views/system/datahub/modeling/index.vue

@@ -56,11 +56,12 @@
 				</el-table-column>
 				<el-table-column prop="createdAt" label="创建时间" width="200" align="center" v-col="'createdAt'"></el-table-column>
 
-				<el-table-column label="操作" width="280" align="center" fixed="right">
+				<el-table-column label="操作" width="310" align="center" fixed="right">
 					<template #default="scope">
-						<router-link :to="'/datahub/modeling/' + scope.row.id" class="link-type" style="padding-right: 12px; font-size: 12px; color: #409eff" v-auth="'detail'">
-							<span>字段管理</span>
-						</router-link>
+						<el-button size="small" text type="success" @click="onOpenCalculationIndicators(scope.row)">计算指标</el-button>
+						<el-button size="small" text type="primary" @click="manageField(scope.row)" v-auth="'detail'">
+								<span>字段管理</span> 
+						</el-button>
 						<el-button size="small" text type="success" @click="onOpenRecord(scope.row)" v-if="scope.row.status == 1" v-auth="'record'">数据记录</el-button>
 						<el-button size="small" text type="info" :disabled="scope.row.status" @click="onOpenJuhe(scope.row)" v-auth="'juhe'">聚合设置</el-button>
 						<el-button size="small" text type="warning" @click="onOpenEdit(scope.row)" v-if="scope.row.status == 0" v-auth="'edit'">修改</el-button>
@@ -80,6 +81,8 @@
 <script lang="ts">
 import { toRefs, reactive, onMounted, ref, defineComponent, getCurrentInstance } from 'vue';
 import { ElMessageBox, ElMessage, FormInstance } from 'element-plus';
+import { useRoute, useRouter } from 'vue-router';
+
 import EditDic from './component/edit.vue';
 import Detail from './component/detail.vue';
 import Juhe from './component/juhe.vue';
@@ -118,6 +121,8 @@ export default defineComponent({
 		const juheRef = ref();
 		const detailRef = ref();
 		const queryRef = ref();
+		const route = useRoute();
+		const router = useRouter();
 		const { proxy } = getCurrentInstance() as any;
 
 		const { datahub_model_type } = proxy.useDict('datahub_model_type');
@@ -139,6 +144,19 @@ export default defineComponent({
 		const initTableData = () => {
 			typeList();
 		};
+
+		const manageField = (row: TableDataRow) => {
+			router.push({
+				path: '/datahub/modeling/' + row.id,
+			});
+		};
+
+		const onOpenCalculationIndicators = (row: TableDataRow) => {
+			router.push({
+				path: '/datahub/calculationIndicator/' + row.id,
+			});
+		};
+
 		const typeList = () => {
 			state.tableData.loading = true;
 			api.template
@@ -229,6 +247,8 @@ export default defineComponent({
 			detailRef,
 			juheRef,
 			queryRef,
+			manageField,
+			onOpenCalculationIndicators,
 			onOpenRecord,
 			onOpenJuhe,
 			datahub_model_type,