Sfoglia il codice sorgente

feat: 对接指标管理的查询,发布,详情,数据接口及页面等

yanglzh 1 mese fa
parent
commit
62ba3d08c8

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

@@ -122,6 +122,7 @@ export default {
   indicator: {
     getList: (params: object) => get('/indicator/list', params),
     data: (params: object) => get('/indicator/data', params),
+    getData: (params: object) => get('/indicator/getData', params),
     detail: (code: string) => get('/indicator/detail', { code }),
     add: (data: object) => post('/indicator/add', data),
     del: (code: string) => del('/indicator/del', { code }),

+ 172 - 0
src/views/system/datahub/indicator/component/data.vue

@@ -0,0 +1,172 @@
+<template>
+  <el-dialog v-model="visible" :title="`指标数据 - ${title}`" width="1100px" :close-on-click-modal="false" destroy-on-close>
+    <div v-if="visible">
+      <el-form :inline="true" class="toolbar">
+        <el-form-item>
+          <el-input v-model="query.searchValue" placeholder="输入指标值或原始值" clearable style="width: 160px" />
+        </el-form-item>
+        <el-form-item label="时间范围">
+          <el-date-picker v-model="query.dateRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 380px" />
+        </el-form-item>
+        <el-form-item label="维度筛选">
+          <el-select v-model="dimensionSelectedText" style="width: 120px" placeholder="全部维度" />
+        </el-form-item>
+        <el-form-item label="">
+          <el-button type="primary" :icon="Filter" @click="fetchList(1)">筛选</el-button>
+          <!-- <el-button @click="exportData">导出</el-button> -->
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="list" style="width: 100%; margin-top: 8px" v-loading="loading">
+        <el-table-column label="时间" align="left" min-width="160">
+          <template #default="scope">{{ scope.row.time || scope.row.createdAt || scope.row.createTime || "-" }}</template>
+        </el-table-column>
+        <el-table-column :label="`指标值${detail.unit ? ' (' + detail.unit + ')' : ''}`" min-width="140" align="left">
+          <template #default="scope">
+            <el-link type="primary" :underline="false">{{ scope.row.value ?? scope.row.indicatorValue ?? "-" }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="原始值" width="120" align="left">
+          <template #default="scope">{{ scope.row.rawValue ?? scope.row.originValue ?? "-" }}</template>
+        </el-table-column>
+        <el-table-column label="监测点" prop="monitorPoint" width="120" align="left"> </el-table-column>
+        <el-table-column label="深度" prop="depth" width="120" align="left"> </el-table-column>
+        <el-table-column label="设备" prop="device" width="120" align="left"> </el-table-column>
+        <el-table-column v-for="k in dimKeys" :key="k" :label="dimNameMap[k] || k" min-width="120" show-overflow-tooltip>
+          <template #default="scope">{{ (scope.row.dimensions && scope.row.dimensions[k]) ?? scope.row[k] ?? "-" }}</template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pager">
+        <el-pagination background layout="prev, pager, next, ->, total, sizes" :page-sizes="[20, 50, 100, 200]" :total="total" :page-size="query.pageSize" :current-page="query.pageNum" @current-change="(p:number)=>fetchList(p)" @size-change="(s:number)=>{query.pageSize=s;fetchList(1)}" />
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref, computed } from "vue";
+import { ElMessage } from "element-plus";
+import { Filter } from "@element-plus/icons-vue";
+import api from "/@/api/datahub";
+
+const visible = ref(false);
+const loading = ref(false);
+const code = ref("");
+const title = ref("");
+const detail = reactive<any>({}); // 详情含 unit/维度等
+const list = ref<any[]>([]);
+const total = ref(0);
+
+// 查询参数(对应后端文档)
+const query = reactive({
+  searchValue: "",
+  keyWord: "",
+  year: "",
+  accurate: "h",
+  dateRange: [],
+  pageNum: 1,
+  pageSize: 20,
+});
+
+const dimensionFilters = reactive<Record<string, any>>({});
+
+const dimKeys = computed(() => {
+  const set = new Set<string>();
+  list.value.forEach((row: any) => {
+    if (row?.dimensions && typeof row.dimensions === "object") {
+      Object.keys(row.dimensions).forEach((k) => set.add(k));
+    }
+  });
+  return Array.from(set);
+});
+const dimNameMap = computed<Record<string, string>>(() => {
+  const map: Record<string, string> = {};
+  (detail.dimensions || []).forEach((d: any) => {
+    map[d.key || d.name] = d.name || d.key;
+  });
+  return map;
+});
+const dimensionSelectedText = computed(() => {
+  const entries = Object.entries(dimensionFilters).filter(([, v]) => v !== "" && v !== undefined && v !== null);
+  if (!entries.length) return "全部维度";
+  return entries.map(([k, v]) => `${dimNameMap.value[k] || k}:${v}`).join(";");
+});
+
+function buildParams() {
+  const params: any = {
+    ...query,
+    code: code.value,
+  };
+  // 维度 JSON 字符串
+  const dims: Record<string, any> = {};
+  Object.entries(dimensionFilters).forEach(([k, v]) => {
+    if (v !== "" && v !== undefined && v !== null) dims[k] = v;
+  });
+  if (Object.keys(dims).length) params.dimensions = JSON.stringify(dims);
+  return params;
+}
+
+function fetchDetail() {
+  const getDetail = (api.indicator as any).detail || (api.indicator as any).data;
+  return getDetail(code.value).then((res: any) => {
+    const data = res?.data || res?.Info || res || {};
+    Object.assign(detail, data);
+  });
+}
+
+function fetchList(p?: number) {
+  if (typeof p === "number") query.pageNum = p;
+  loading.value = true;
+  const params = buildParams();
+  api.indicator
+    .getData(params)
+    .then((res: any) => {
+      // 兼容不同返回格式
+      const data = res?.data ?? res?.Info ?? res ?? {};
+      const rows = data?.list ?? data?.rows ?? data?.records ?? data ?? [];
+      list.value = Array.isArray(rows) ? rows : [];
+      total.value = res?.total ?? data?.total ?? data?.count ?? list.value.length ?? 0;
+    })
+    .finally(() => (loading.value = false));
+}
+
+function open(row: any) {
+  code.value = row?.code || "";
+  title.value = `${row?.name || "-"} (${code.value})`;
+  visible.value = true;
+  // 清空筛选
+  Object.assign(query, { searchValue: "", keyWord: "", year: "", startTime: "", endTime: "", accurate: "", accurateRanges: "", orderBy: "", pageNum: 1, pageSize: 20 });
+  Object.keys(dimensionFilters).forEach((k) => delete (dimensionFilters as any)[k]);
+  fetchDetail().then(() => fetchList(1));
+}
+
+function exportData() {
+  ElMessage.info("导出功能请在接口确定后接入");
+}
+
+defineExpose({ open });
+</script>
+
+<style lang="scss" scoped>
+.sub-title {
+  color: var(--el-text-color-secondary);
+}
+.toolbar {
+  margin-top: 10px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  align-items: center;
+}
+.pager {
+  margin-top: 12px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.empty-tip {
+  color: var(--el-text-color-secondary);
+  text-align: center;
+}
+</style>

+ 61 - 0
src/views/system/datahub/indicator/component/detail.vue

@@ -0,0 +1,61 @@
+<template>
+  <el-dialog v-model="visible" :title="`指标详情 - ${detail.name || '-'} (${detail.code || code})`" width="720px" :close-on-click-modal="false">
+    <div v-if="visible">
+      <el-descriptions :column="1" border class="mt16">
+        <el-descriptions-item label="指标名称">{{ detail.name || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="指标描述">{{ detail.description || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="指标类型">
+          <el-tag size="small">{{ detail.type || "-" }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="单位">{{ detail.unit || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="计算公式">
+          <span class="mono">{{ detail.formula || "-" }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="维度数">{{ (detail.dimensions && detail.dimensions.length) || detail.dimensionCount || 0 }} 个</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="detail.status == '1' ? 'success' : 'info'">{{ detail.status == "1" ? "已发布" : "未发布" }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="指标描述">{{ detail.description || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ detail.createdAt || detail.createTime || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="创建人">{{ detail.createdBy || "-" }}</el-descriptions-item>
+      </el-descriptions>
+    </div>
+    <template #footer>
+      <el-button @click="visible = false">关闭</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref } from "vue";
+import api from "/@/api/datahub";
+
+const visible = ref(false);
+const code = ref("");
+const detail = reactive<any>({});
+
+function open(row: any) {
+  code.value = row?.code || "";
+  Object.keys(detail).forEach((k) => delete (detail as any)[k]);
+  visible.value = true;
+  if (!code.value) return;
+  api.indicator.detail(code.value).then((res: any) => {
+    const data = res?.data || res?.Info || res || {};
+    Object.assign(detail, data);
+  });
+}
+
+defineExpose({ open });
+</script>
+
+<style lang="scss" scoped>
+.sub-title {
+  color: var(--el-text-color-secondary);
+}
+.mt16 {
+  margin-top: 16px;
+}
+.mono {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+</style>

+ 26 - 15
src/views/system/datahub/indicator/component/edit.vue

@@ -58,7 +58,7 @@
           <el-tab-pane label="计算公式" name="formula">
             <div class="section-title">计算公式</div>
             <el-input v-model="form.formula" type="textarea" :rows="4" placeholder="请输入计算公式,例如:COD = (V1 - V2) × C × 8 × 1000 / V" />
-            <div class="mt10 text-gray">支持 + - × ÷ () 函数 等;参数请在下方维护。</div>
+            <div class="mt10 text-gray">支持数学运算符(+、-、×、÷)、括号、函数等,系统会自动识别公式中的参数</div>
 
             <div class="section-title mt20 flex-between">
               <span>公式参数</span>
@@ -67,11 +67,11 @@
 
             <el-table :data="form.formulaParams" border style="width: 100%">
               <el-table-column type="index" label="序号" width="60" align="center" />
-              <el-table-column label="参数名称" prop="name" />
-              <el-table-column label="参数编码" prop="code" />
-              <el-table-column label="参数值" prop="values" width="200" />
-              <el-table-column label="参数描述" prop="description" />
-              <el-table-column label="操作" width="160" align="center">
+              <el-table-column label="参数名称" prop="name" align="center" />
+              <el-table-column label="参数编码" prop="code" align="center"/>
+              <el-table-column label="参数值" prop="values" align="center"/>
+              <el-table-column label="参数描述" prop="description" align="center"/>
+              <el-table-column label="操作" width="120" align="center">
                 <template #default="scope">
                   <el-button size="small" text type="primary" @click="openParamDialog(scope.row, scope.$index)">编辑</el-button>
                   <el-button size="small" text type="danger" @click="removeParam(scope.$index)">删除</el-button>
@@ -89,13 +89,14 @@
 
             <el-table :data="form.dimensions" border style="width: 100%">
               <el-table-column type="index" label="序号" width="60" align="center" />
-              <el-table-column label="维度名称" prop="name" />
-              <el-table-column label="维度标识" prop="code" width="180" />
-              <el-table-column label="值类型" prop="valueType" width="140">
+              <el-table-column label="维度名称" prop="name" align="center"/>
+              <el-table-column label="维度标识" prop="code" align="center"/>
+              <el-table-column label="维度值类型" prop="valueType" align="center">
                 <template #default="scope">{{ formatValueType(scope.row.valueType) }}</template>
               </el-table-column>
-              <el-table-column label="维度描述" prop="description" />
-              <el-table-column label="操作" width="160" align="center">
+              <el-table-column label="维度值" prop="values" align="center"/>
+              <el-table-column label="维度描述" prop="description" align="center"/>
+              <el-table-column label="操作" width="120" align="center">
                 <template #default="scope">
                   <el-button size="small" text type="primary" @click="openDimDialog(scope.row, scope.$index)">编辑</el-button>
                   <el-button size="small" text type="danger" @click="removeDim(scope.$index)">删除</el-button>
@@ -139,14 +140,14 @@
 
     <!-- 添加/编辑 维度 -->
     <el-dialog v-model="dimDialog.visible" title="添加维度" width="520px" :close-on-click-modal="false">
-      <el-form :model="dimDialog.form" ref="dimFormRef" :rules="dimRules" label-width="90px">
+      <el-form :model="dimDialog.form" ref="dimFormRef" :rules="dimRules" label-width="100px">
         <el-form-item label="维度名称" prop="name">
           <el-input v-model.trim="dimDialog.form.name" placeholder="请输入维度名称" />
         </el-form-item>
         <el-form-item label="维度标识" prop="code">
           <el-input v-model.trim="dimDialog.form.code" placeholder="请输入维度标识,如 time, location" />
         </el-form-item>
-        <el-form-item label="值类型" prop="valueType">
+        <el-form-item label="维度值类型" prop="valueType">
           <el-select v-model="dimDialog.form.valueType" placeholder="请选择值类型" style="width: 100%">
             <el-option label="字符串" value="string" />
             <el-option label="数值" value="number" />
@@ -154,6 +155,15 @@
             <el-option label="时间" value="datetime" />
           </el-select>
         </el-form-item>
+        <el-form-item label="维度值" prop="values">
+          <el-input v-if="dimDialog.form.valueType === 'string'" v-model.trim="dimDialog.form.values" placeholder="请输入维度值" />
+          <el-input v-else-if="dimDialog.form.valueType === 'number'" type="number" v-model.trim="dimDialog.form.values" placeholder="请输入维度值" />
+          <el-radio-group v-else-if="dimDialog.form.valueType === 'boolean'" v-model="dimDialog.form.values">
+            <el-radio :label="true">是</el-radio>
+            <el-radio :label="false">否</el-radio>
+          </el-radio-group>
+          <el-date-picker v-else-if="dimDialog.form.valueType === 'datetime'" type="datetime" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" v-model="dimDialog.form.values" placeholder="请选择维度值" />
+        </el-form-item>
         <el-form-item label="维度描述" prop="description">
           <el-input v-model.trim="dimDialog.form.description" type="textarea" placeholder="请输入维度描述" />
         </el-form-item>
@@ -179,7 +189,7 @@ const isEdit = ref(false);
 const activeTab = ref<"base" | "formula" | "dimension">("base");
 
 type FormulaParam = { name: string; code: string; values: string; description?: string };
-type Dimension = { name: string; code: string; valueType: "string" | "number" | "boolean" | "datetime"; description?: string };
+type Dimension = { name: string; code: string; valueType: "string" | "number" | "boolean" | "datetime"; values: string; description?: string };
 
 const formRef = ref();
 const form = reactive({
@@ -230,7 +240,7 @@ const paramRules = {
 const dimDialog = reactive({
   visible: false,
   index: -1,
-  form: { name: "", code: "", valueType: "string", description: "" } as Dimension,
+  form: { name: "", code: "", valueType: "string", values: "", description: "" } as Dimension,
 });
 const dimFormRef = ref();
 const dimRules = {
@@ -332,6 +342,7 @@ function openDimDialog(row?: Dimension, index?: number) {
     name: row?.name || "",
     code: row?.code || "",
     valueType: (row?.valueType as any) || "string",
+    values: row?.values || "",
     description: row?.description || "",
   };
   dimDialog.visible = true;

+ 34 - 5
src/views/system/datahub/indicator/index.vue

@@ -16,9 +16,8 @@
         <el-form-item>
           <el-select v-model="params.status" placeholder="全部状态" clearable style="width: 160px">
             <el-option :value="''" label="全部状态" />
-            <el-option value="enabled" label="启用" />
-            <el-option value="draft" label="草稿" />
-            <el-option value="disabled" label="停用" />
+            <el-option value="0" label="未发布" />
+            <el-option value="1" label="已发布" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -53,12 +52,16 @@
         </el-table-column>
         <el-table-column label="状态" prop="status" width="100" align="center">
           <template #default="scope">
-            <el-tag size="small" :type="statusTagType(scope.row.status)">{{ formatStatus(scope.row.status) }}</el-tag>
+            <el-tag size="small" :type="scope.row.status == '1' ? 'success' : 'danger'">{{ scope.row.status == "1" ? "已发布" : "未发布" }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" align="center" width="180" />
-        <el-table-column label="操作" align="center" width="180" fixed="right">
+        <el-table-column label="操作" align="center" width="200" fixed="right">
           <template #default="scope">
+            <el-button size="small" text type="primary" v-if="scope.row.status == '0'" @click="publish(scope.row)">发布</el-button>
+            <el-button size="small" text type="warning" v-else @click="unpublish(scope.row)">取消发布</el-button>
+            <el-button size="small" text type="primary" @click="openDetail(scope.row)">详情</el-button>
+            <el-button size="small" text type="success" @click="openData(scope.row)">数据</el-button>
             <el-button size="small" text type="primary" @click="addOrEdit(scope.row)">编辑</el-button>
             <el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
           </template>
@@ -69,6 +72,8 @@
     </el-card>
 
     <EditIndicator ref="editFormRef" :typeOptions="typeOptions" @update="getList" />
+    <DetailDialog ref="detailRef" />
+    <DataDialog ref="dataRef" />
   </div>
 </template>
 
@@ -78,9 +83,13 @@ import { ElMessageBox, ElMessage } from "element-plus";
 import api from "/@/api/datahub";
 import { useSearch } from "/@/hooks/useCommon";
 import EditIndicator from "./component/edit.vue";
+import DetailDialog from "./component/detail.vue";
+import DataDialog from "./component/data.vue";
 import apiSystem from "/@/api/system";
 
 const editFormRef = ref();
+const detailRef = ref();
+const dataRef = ref();
 const queryRef = ref();
 
 const typeOptions = ref([]);
@@ -100,9 +109,29 @@ const { params, tableData, getList, loading } = useSearch(api.indicator.getList,
 
 getList();
 
+const publish = (row?: any) => {
+  api.indicator.publish(row.code).then(() => {
+    ElMessage.success("发布成功");
+    getList();
+  });
+};
+
+const unpublish = (row?: any) => {
+  api.indicator.unpublish(row.code).then(() => {
+    ElMessage.success("取消发布成功");
+    getList();
+  });
+};
+
 const addOrEdit = (row?: any) => {
   editFormRef.value.openDialog(row);
 };
+const openDetail = (row: any) => {
+  detailRef.value.open(row);
+};
+const openData = (row: any) => {
+  dataRef.value.open(row);
+};
 
 const onRowDel = (row: any) => {
   ElMessageBox.confirm(`此操作将永久删除指标:${row.name}(${row.code}),是否继续?`, "提示", {