wujingjing
2024-12-30 c4004648da4014c8777e4f4bd887ca28cb7afd97
src/views/project/yw/systemManage/metricMgr/MetricDetail.vue
@@ -1,121 +1,99 @@
<template>
   <div class="flex flex-col h100">
      <el-row class="w100 h100">
         <el-col :span="17">
         <el-col :span="17" class="h-full">
            <div class="h100 relative">
               <div class="ml-6 h100">
                  <el-tabs v-model="state.activeMetricName" @tab-change="handleClick" class="h100">
                     <el-tab-pane label="基础信息" name="basicInformation">
                        <div class="h100">
                           <el-descriptions title="指标信息" :column="2">
                              <el-descriptions-item label="指标ID">kooriookami</el-descriptions-item>
                              <el-descriptions-item label="中文名">18100000000</el-descriptions-item>
                              <el-descriptions-item label="英文名">Suzhou</el-descriptions-item>
                              <el-descriptions-item label="别名"> </el-descriptions-item>
                              <el-descriptions-item label="分类">
                                 <el-tag size="small">School</el-tag>
                           <el-descriptions title="指标信息">
                              <el-descriptions-item label="指标名称:" :span="2">{{
                                 state.metricBasicInfo.descriptionQuotaItems.title ? state.metricBasicInfo.descriptionQuotaItems.title : '-'
                              }}</el-descriptions-item>
                              <el-descriptions-item label="指标全称:" :span="2">{{
                                 state.metricBasicInfo.descriptionQuotaItems.full_name
                                    ? state.metricBasicInfo.descriptionQuotaItems.full_name
                                    : '-'
                              }}</el-descriptions-item>
                              <el-descriptions-item label="指标定义:" :span="3">{{
                                 state.metricBasicInfo.descriptionQuotaItems.metrics_define
                                    ? state.metricBasicInfo.descriptionQuotaItems.metrics_define
                                    : '-'
                              }}</el-descriptions-item>
                              <el-descriptions-item label="类别:" :span="2">
                                 {{
                                    state.metricBasicInfo.descriptionQuotaItems.metrics_type
                                       ? state.metricBasicInfo.descriptionQuotaItems.metrics_type
                                       : '-'
                                 }}</el-descriptions-item
                              >
                              <el-descriptions-item label="重要性:" :span="2">
                                 {{ eMetrics_Ops[state.metricBasicInfo.descriptionQuotaItems.metrics_important] }}
                              </el-descriptions-item>
                              <el-descriptions-item label="计算方法:" :span="3">
                                 {{
                                    state.metricBasicInfo.descriptionQuotaItems.calcu_method
                                       ? state.metricBasicInfo.descriptionQuotaItems.calcu_method
                                       : '-'
                                 }}
                              </el-descriptions-item>
                           </el-descriptions>
                           <el-divider />
                           <el-descriptions title="模型信息" :column="2">
                              <el-descriptions-item label="模型名">kooriookami</el-descriptions-item>
                              <el-descriptions-item label="模型ID">18100000000</el-descriptions-item>
                              <el-descriptions-item label="模型英文名">Suzhou</el-descriptions-item>
                           </el-descriptions>
                           <el-divider />
                           <el-descriptions title="定义信息" :column="2">
                              <el-descriptions-item label="定义类型">kooriookami</el-descriptions-item>
                              <el-descriptions-item label="表达式">18100000000</el-descriptions-item>
                              <el-descriptions-item label="度量列表">
                                 <template #default>
                                    <el-table :data="state.measurementList" style="width: 100%">
                                       <el-table-column prop="date" label="度量名称" width="180" />
                                       <el-table-column prop="name" label="限定条件" width="180" />
                                       <el-table-column prop="address" label="聚合函数" />
                                    </el-table>
                                 </template>
                              </el-descriptions-item>
                           </el-descriptions>
                        </div>
                     </el-tab-pane>
                     <el-tab-pane label="指标探索" name="indicatorExploration">
                        <div class="h100">
                           <div class="set_explore">
                              <div class="set_explore_pad">
                                 <el-form ref="exploreFormRef" :model="state.exploreForm" label-width="100px">
                                    <el-form-item label="日期区间:">
                                       <div style="width: 300px">
                                          <el-date-picker
                                             v-model="state.exploreForm.dateRangeExplore"
                                             type="daterange"
                                             range-separator="~"
                                             :shortcuts="shortcuts"
                     <el-tab-pane label="指标探索" name="indicatorExploration" class="h-full">
                        <div class="h-full flex-column">
                           <el-form :model="dialogFormValue" class="flex-0" ref="dialogFormRef" :rules="dialogFormRules">
                              <el-form-item label="日期区间:" prop="rangValue" v-if="currentMetrics?.is_time_values ?? true">
                                 <TimeRange v-model="dialogFormValue.rangValue" @change="query(false)" />
                              </el-form-item>
                              <el-row :gutter="35">
                                 <el-col :span="12" class="mb20">
                                    <el-form-item label="分组维度:" prop="rangValue">
                                       <el-select v-model="dialogFormValue.groupDimList" multiple @change="query(false)">
                                          <el-option
                                             v-for="item in currentMetrics?.dimensions ?? []"
                                             :key="item.id"
                                             :label="item.title"
                                             :value="item.id"
                                          />
                                       </div>
                                    </el-form-item>
                                    <el-form-item label="维度下钻:">
                                       <el-select v-model="state.exploreForm.dimensionDrilling" autocomplete="off" style="width: 226.4px" clearable>
                                          <el-option
                                             v-for="item in Object.keys(eDrilling_Ops)"
                                             :key="item"
                                             :value="parseInt(item)"
                                             :label="eDrilling_Ops[item]"
                                          >
                                          </el-option>
                                       </el-select>
                                    </el-form-item>
                                    <el-form-item label="维度筛选:">
                                       <el-select
                                          v-model="state.exploreForm.dimensionFiltering.user"
                                          autocomplete="off"
                                          style="width: 186.4px"
                                          clearable
                                       >
                                          <el-option
                                             v-for="item in Object.keys(eDrilling_Ops)"
                                             :key="item"
                                             :value="parseInt(item)"
                                             :label="eDrilling_Ops[item]"
                                          >
                                          </el-option>
                                       </el-select>
                                       <el-select
                                          v-model="state.exploreForm.dimensionFiltering.unit"
                                          autocomplete="off"
                                          style="width: 126.4px; margin: 0 5px"
                                          clearable
                                       >
                                          <el-option
                                             v-for="item in Object.keys(eDimensionFilter_Ops)"
                                             :key="item"
                                             :value="parseInt(item)"
                                             :label="eDimensionFilter_Ops[item]"
                                          >
                                          </el-option>
                                       </el-select>
                                       <el-select v-model="state.resultList" autocomplete="off" style="width: 126.4px; margin-right: 5px" clearable>
                                          <el-option
                                             v-for="(item, index) in state.resultList"
                                             :key="index"
                                             :value="item.department"
                                             :label="item.department"
                                          >
                                          </el-option>
                                       </el-select>
                                       <el-button type="primary" @click="handleExplore" icon="ele-Search">查询</el-button>
                                 </el-col>
                                 <el-col :span="12" class="mb20" v-for="item in filterDimList" :key="item.id"
                                    ><el-form-item :label="`${item.title}:`" :prop="`${item.id}`">
                                       <el-input v-model="dialogFormValue[item.id]" @input="(val) => filterDimInput(val, item)"> </el-input>
                                    </el-form-item>
                                 </el-form>
                              </div>
                              <div class="w100 h-[550px]">
                                 <div ref="chartExplorationRef" class="w100 h100"></div>
                              </div>
                                 </el-col>
                              </el-row>
                           </el-form>
                           <div class="flex-auto mt-4">
                              <SummaryCom
                                 v-loading="queryLoading"
                                 ref="summaryComRef"
                                 :data="querySummaryData"
                                 :origin-data="{
                                    content: {
                                       origin: {
                                          summary: querySummaryData,
                                       },
                                    },
                                 }"
                              />
                              <!-- <RecordSet v-bind="recordSetProps" class="h-full" /> -->
                              <!-- <RecordSetTable v-bind="recordSetTableProps" class="h-full"/> -->
                           </div>
                        </div>
                     </el-tab-pane>
                     <el-tab-pane label="指标图谱" name="metricGraph" class="h-full">
                        <div class="h-full">
                           <TreeGraph v-if="graphData" :data="graphData" class="h-full" :maxCount="maxCount" /></div
                     ></el-tab-pane>
                  </el-tabs>
               </div>
               <div class="list_btn">
                  <el-button style="width: 40px" link @click="handleExitFlow">
               <div class="list_btn" @click="handleExitFlow">
                  <el-button style="width: 40px" link>
                     <el-icon style="font-size: 16px !important; color: #1677ff">
                        <ArrowLeft />
                     </el-icon>
@@ -135,10 +113,28 @@
               <div class="set-detail">
                  <el-divider />
                  <div class="section___vePzi">
                     <div class="item___txXyB" v-for="(item, index) in state.metricBasicInfo.visitNumData" :key="index">
                        <div class="item_name">{{ item.name }}:</div>
                     <div class="item___txXyB">
                        <div class="item_name">重要性:</div>
                        <div class="item_value">{{ eMetrics_Ops[state.metricBasicInfo.descriptionQuotaItems.metrics_important] }}</div>
                     </div>
                     <div class="item___txXyB">
                        <div class="item_name">所属领域:</div>
                        <div class="item_value">
                           {{ item.value }}
                           {{
                              state.metricBasicInfo.descriptionQuotaItems.metrics_type
                                 ? state.metricBasicInfo.descriptionQuotaItems.metrics_type
                                 : '-'
                           }}
                        </div>
                     </div>
                     <div class="item___txXyB">
                        <div class="item_name">描述:</div>
                        <div class="item_value">
                           {{
                              state.metricBasicInfo.descriptionQuotaItems.metrics_define
                                 ? state.metricBasicInfo.descriptionQuotaItems.metrics_define
                                 : '-'
                           }}
                        </div>
                     </div>
                  </div>
@@ -148,10 +144,24 @@
                        <i class="ywifont ywicon-gongzuozongjie w-[18px] h-[18px] !text-[18px]"></i>
                        <span>创建信息</span>
                     </div>
                     <div class="item___txXyB" v-for="(item, index) in state.metricBasicInfo.createInformation" :key="index">
                        <div class="item_name">{{ item.name }}:</div>
                     <div class="item___txXyB">
                        <div class="item_name">创建人:</div>
                        <div class="item_value">
                           {{ item.value }}
                           {{
                              state.metricBasicInfo.descriptionQuotaItems.create_user
                                 ? state.metricBasicInfo.descriptionQuotaItems.create_user
                                 : '-'
                           }}
                        </div>
                     </div>
                     <div class="item___txXyB">
                        <div class="item_name">创建时间:</div>
                        <div class="item_value">
                           {{
                              state.metricBasicInfo.descriptionQuotaItems.create_time
                                 ? state.metricBasicInfo.descriptionQuotaItems.create_time
                                 : '-'
                           }}
                        </div>
                     </div>
                  </div>
@@ -161,10 +171,20 @@
                        <i class="ywifont ywicon-jiankong w-[18px] h-[18px] !text-[20px]"></i>
                        <span>应用信息 </span>
                     </div>
                     <div class="item___txXyB" v-for="(item, index) in state.metricBasicInfo.applicationInformation" :key="index">
                        <div class="item_name">{{ item.name }}:</div>
                        <div class="item_value">
                           {{ item.value }}
                     <div class="text-[#344767]">
                        <div class="item___bg">
                           <div class="block px-[16px] py-[4px]">
                              <div class="subTitle___zya5g">下钻维度</div>
                              <div class="ml-0 mt-[20px]">
                                 <div class="flex flex-wrap gap-[5px] items-center">
                                    <div v-for="item in currentMetrics?.dimensions ?? []" :key="item.id">
                                       <el-tag :style="{ backgroundColor: 'e6f4ff', color: '#0958d9', borderColor: '#91caff' }">{{
                                          item.title
                                       }}</el-tag>
                                    </div>
                                 </div>
                              </div>
                           </div>
                        </div>
                     </div>
                  </div>
@@ -177,58 +197,37 @@
<script setup lang="ts">
import * as echarts from 'echarts';
import { onMounted, reactive, ref, nextTick } from 'vue';
import { nextTick, onMounted, reactive, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { formatTime } from '/@/utils/istation/common.js';
import { eDimensionFilter_Ops, eDrilling_Ops } from '/@/views/types/metrics/index';
import * as metricApi from '/@/api/metrics';
import TimeRange from '/@/components/chat/chatComponents/summaryCom/components/recordSet/components/TimeRange.vue';
import { formatTime, getDefaultPeriod } from '/@/utils/istation/common.js';
import { eMetrics_Ops } from '/@/views/types/metrics';
import TreeGraph from '/@/components/graph/treeGraph/TreeGraph.vue';
import { FormRules } from 'element-plus/es/components/form/src/types';
import _, { debounce } from 'lodash';
import { computed } from 'vue';
import SummaryCom from './components/SummaryCom.vue';
import { chatMetricsJsonByPost } from '/@/api/metrics';
import { useCompRef } from '/@/utils/types';
import { OrgTreeItem } from '../agentGraph/types';
const defaultTime = ref<[Date, Date]>([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]);
const router = useRouter();
const route = useRoute();
// 定义变量内容
const state = reactive({
   metricInfo: {
      name: '',
      id: '',
   } as any,
   activeMetricName: 'basicInformation',
   measurementList: [],
   metricBasicInfo: {
      visitNumData: [
         {
            name: '敏感度',
            value: '普通',
         },
         {
            name: '所属模型',
            value: 'PVUV统计',
         },
         {
            name: '描述',
            value: '一段时间内用户的访问次数',
         },
      ],
      createInformation: [
         {
            name: '创建人',
            value: 'admin',
         },
         {
            name: '创建时间',
            value: '2024-10-08 10:03:39',
         },
         {
            name: '更新时间',
            value: '2024-10-08 14:12:11',
         },
      ],
      applicationInformation: [
         {
            name: '分类',
            value: '核心指标',
         },
      ],
   },
      descriptionQuotaItems: {},
   } as any,
   exploreForm: {
      dateRangeExplore: [] as any,
      dateRangeExplore: '' as any,
      dimensionDrilling: 0,
      dimensionFiltering: {
         user: '',
@@ -238,49 +237,24 @@
   },
   resultList: [
      {
         id: 1,
         department: 'HR',
      },
      {
         id: 2,
         department: 'marketing',
      },
      {
         id: 3,
         department: 'sales',
      },
      {
         id: 4,
         department: 'strategy',
      },
   ],
   chartStatus: false,
});
const shortcuts = [
   {
      text: '最近一周',
      value: () => {
         const end = new Date();
         const start = new Date();
         start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
         return [start, end];
      },
   },
   {
      text: '最近一月',
      value: () => {
         const end = new Date();
         const start = new Date();
         start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
         return [start, end];
      },
   },
   {
      text: '最近三月',
      value: () => {
         const end = new Date();
         const start = new Date();
         start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
         return [start, end];
      },
   },
];
//返回到知识库列表
const handleExitFlow = () => {
@@ -289,7 +263,36 @@
};
//#endregion
//#region ====================== 查询 ======================
//#region ====================== 基础信息 ======================
const currentMetrics = ref(null);
const tableData = ref([]);
const getTableData = async (id) => {
   const res = await metricApi.getMetricNameListByPost();
   const data = res?.values ?? [];
   const filterData = data.filter((item) => item.id == id);
   state.metricBasicInfo.descriptionQuotaItems = filterData[0]; //左侧描述内容
   currentMetrics.value = filterData[0];
   filterDimList.value.forEach((item) => {
      if (!dialogFormValue.value) {
         dialogFormValue.value = {} as any;
      }
      dialogFormValue.value[item.id] = '';
   });
};
//#endregion
//#region ====================== 指标图谱查询 ======================
// 时间限制
const disablesDate = (time) => {
   return time.getTime() > new Date().getTime();
};
//获取日期的时间
const getDateTime = (val) => {
   state.exploreForm.dateRangeExplore[0] = val[0];
   state.exploreForm.dateRangeExplore[1] = val[1];
};
const handleDimensionFilteringChange = (val) => {
   state.exploreForm.dimensionFiltering.user = val;
};
const handleExplore = () => {
   console.log(state.exploreForm);
};
@@ -398,6 +401,9 @@
   chartInstance.resize();
};
const handleClick = (val) => {
   const { startDate, endDate } = getDefaultPeriod('7Days');
   const dateRange = [startDate.format('YYYY-MM-DD HH:mm:ss'), endDate.format('YYYY-MM-DD HH:mm:ss')];
   state.exploreForm.dateRangeExplore = dateRange;
   if (val == 'indicatorExploration') {
      nextTick(() => {
         if (state.chartStatus) {
@@ -409,12 +415,129 @@
      });
   }
   state.activeMetricName = val;
   if (val === 'metricGraph') {
      if (!graphData.value) {
         getGraphTreeData();
      }
   }
};
//#endregion
//#region ====================== 指标图谱 ======================
const filterDimList = computed(
   () => currentMetrics?.value?.dimensions ?? [].filter((item) => item.filter_type === 'str_eq' && item.type === '字符串')
);
const dialogFormRules = ref<FormRules>({
   title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
   prompt: [{ required: true, message: '请输入提示词', trigger: 'blur' }],
});
const summaryComRef = useCompRef(SummaryCom);
const dialogFormValue = ref({
   rangValue: ['2024-10-21 00:00:00', '2024-10-23 23:59:59'],
   groupDimList: [],
});
const currentMetricsId = computed(() => router.currentRoute.value.query.id as string);
const querySummaryData = ref([]);
const queryLoading = ref(false);
/**
 * 按查询条件查找
 */
const query = async (isFirst: boolean) => {
   if (!currentMetricsId.value) return;
   const groupDimStr = dialogFormValue.value.groupDimList.join(',');
   const sendGroupDims = groupDimStr || undefined;
   queryLoading.value = true;
   const res = await chatMetricsJsonByPost(
      {
         metrics_id: currentMetricsId.value,
         start_time: dialogFormValue.value.rangValue[0],
         end_time: dialogFormValue.value.rangValue[1],
         group_dims: sendGroupDims,
         filter_dims: JSON.stringify(_.omit(dialogFormValue.value, ['groupDimList', 'rangValue'])),
      },
      {
         loading: false,
      }
   ).finally(() => {
      queryLoading.value = false;
   });
   querySummaryData.value = res?.summary ?? [];
   if (!isFirst) {
      nextTick(() => {
         nextTick(() => {
            summaryComRef.value.updateSummary();
         });
      });
   }
};
const debounceQuery = debounce(query, 600);
const filterDimInput = (val, dim) => {
   debounceQuery(false);
};
//#endregion
//#region ====================== 指标图谱 ======================
const maxCount = ref(null);
const graphData = ref(null);
const convertOrgTreeToTreeNode = (orgTreeData: OrgTreeItem) => {
   const treeData = {
      id: orgTreeData.treeId,
      label: orgTreeData.label,
      data: orgTreeData,
      children: orgTreeData.children?.length > 0 ? orgTreeData.children.map((item) => convertOrgTreeToTreeNode(item)) : [],
   };
   return treeData;
};
const getGraphTreeData = () => {
   if (!currentMetricsId.value) return;
   /** @description 维度数量 */
   let dimensionCount = 0;
   /** @description 指标数量 */
   let metricsCount = 1;
   const metricsTreeId = `metrics-${currentMetricsId.value}`;
   const dimensionList = currentMetrics.value.dimensions ?? [];
   dimensionCount = dimensionList.length;
   let logicTree: OrgTreeItem = {
      treeId: metricsTreeId,
      logicId: currentMetricsId.value,
      type: 'metrics',
      model: currentMetrics.value,
      get label() {
         return this.model.title;
      },
      level: 0,
      children: dimensionList.map((item) => {
         const dimensionTreeId = `${metricsTreeId}-dimension-${item.id}`;
         return {
            treeId: dimensionTreeId,
            logicId: item.id,
            type: 'dimension',
            model: item,
            get label() {
               return this.model.title;
            },
            level: 1,
         };
      }),
   };
   const resData = logicTree;
   maxCount.value = Math.max(dimensionCount, metricsCount);
   graphData.value = convertOrgTreeToTreeNode(resData);
};
//#endregion
onMounted(() => {
   const { id, name } = route.query;
   state.metricInfo.name = name;
   state.metricInfo.id = id;
   const { id } = route.query;
   query(true);
   getTableData(id);
});
</script>
<style scoped lang="scss">
@@ -516,6 +639,25 @@
               opacity: 1;
            }
         }
         .item___bg {
            position: relative;
            margin: 0;
            padding: 8px 0;
            list-style: none;
            background-color: #f9fafb;
            .subTitle___zya5g {
               margin: 0;
               color: #7b809a;
               font-weight: 700;
               font-size: 14px;
               line-height: 1.25;
               letter-spacing: 0.03333em;
               text-transform: uppercase;
               text-decoration: none;
               vertical-align: unset;
               opacity: 1;
            }
         }
      }
   }
}