|
|
@@ -1,6 +1,94 @@
|
|
|
<template>
|
|
|
- <div class="app-container">
|
|
|
- <el-row>
|
|
|
+ <div style="width: 98%;margin-left: 1%;">
|
|
|
+ <el-tabs
|
|
|
+ v-model="activeName"
|
|
|
+ type="card"
|
|
|
+ class="demo-tabs"
|
|
|
+
|
|
|
+ @tab-click="handleClick"
|
|
|
+ >
|
|
|
+ <el-tab-pane label="模型事件" name="first">
|
|
|
+ <el-form ref="formRef" :inline="true" :model="queryParams1">
|
|
|
+ <el-form-item label="运行事件">
|
|
|
+ <el-select
|
|
|
+ v-model="queryParams1.event"
|
|
|
+ placeholder=""
|
|
|
+ clearable
|
|
|
+ style="width: 150px"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="dict in model_run_log_event"
|
|
|
+ :key="dict.value"
|
|
|
+ :label="dict.label"
|
|
|
+ :value="dict.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="模型名称">
|
|
|
+ <el-input v-model="queryParams1.mdName" style="width: 150px"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="时间">
|
|
|
+ <el-date-picker v-model="queryParams1.time" type="daterange" range-separator="至"
|
|
|
+ start-placeholder="开始时间" end-placeholder="结束时间"
|
|
|
+ value-format="YYYY-MM-DD">
|
|
|
+ </el-date-picker>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="getList1" icon="Search">搜索</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div style="display: flex;width: 100%;">
|
|
|
+ <div style="width: 60%;">
|
|
|
+ <el-table
|
|
|
+ stripe
|
|
|
+ style="height: 68vh;margin-top: 1%;width: 100%;"
|
|
|
+ :data="tableDataJian"
|
|
|
+ :cell-style="{ padding: '5px' }"
|
|
|
+ :row-style="{ fontSize: '1rem', textAlign:'center' }"
|
|
|
+ border>
|
|
|
+ <el-table-column type="index" align="center" label="序号" width="60">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <div style="text-align: center;">{{ $index + 1 }}</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column show-overflow-tooltip header-align="center" align="left" prop="createBy" label="人员名称"/>
|
|
|
+ <el-table-column show-overflow-tooltip header-align="center" align="left" prop="createTime" label="创建时间"/>
|
|
|
+ <el-table-column show-overflow-tooltip header-align="center" align="left" prop="mdName" label="模型名称"/>
|
|
|
+ <el-table-column show-overflow-tooltip header-align="center" align="left" prop="event" label="运行事件"/>
|
|
|
+ </el-table>
|
|
|
+ <div style="display: flex;">
|
|
|
+ <el-pagination
|
|
|
+ small
|
|
|
+ background
|
|
|
+ style="margin-top: 1%;margin-left: auto;display: flex;margin-right: 1%;"
|
|
|
+ layout="prev, pager, next"
|
|
|
+ :total="totalJian"
|
|
|
+ v-model="pageNumJian"
|
|
|
+ @change="fanJian"
|
|
|
+ class="mt-4"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="width: 49%;margin-left: 1%;margin-top: 1%;">
|
|
|
+ <div>
|
|
|
+ 过去7天模型吞吐量
|
|
|
+ </div>
|
|
|
+ <div style="height: 30vh;width: 100%;background-color: ;margin-top: 1%;" id="right1">
|
|
|
+
|
|
|
+ </div>
|
|
|
+ <div style="margin-top: 5%;">
|
|
|
+ 过去7天模型错误率
|
|
|
+ </div>
|
|
|
+ <div style="height: 30vh;width: 100%;background-color: ;margin-top: 1%;" id="right2">
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="模型服务监控" name="second" style="height: 100vh;">
|
|
|
+ <el-row>
|
|
|
<!-- 筛选条件 -->
|
|
|
<el-form ref="formRef" :inline="true" :model="queryParams">
|
|
|
<el-form-item label="服务名称">
|
|
|
@@ -23,8 +111,8 @@
|
|
|
<el-row class="table_box">
|
|
|
<el-table
|
|
|
stripe
|
|
|
+ style="height: 68vh;"
|
|
|
:data="tableData"
|
|
|
- :height="tableHeight"
|
|
|
:cell-style="{ padding: '5px' }"
|
|
|
:row-style="{ fontSize: '1rem', textAlign:'center' }"
|
|
|
border>
|
|
|
@@ -67,22 +155,31 @@
|
|
|
:total="total"
|
|
|
v-model:page="queryParams.pageNum"
|
|
|
v-model:limit="queryParams.pageSize"
|
|
|
+ style="margin-top: 1%;"
|
|
|
@pagination="getList"
|
|
|
/>
|
|
|
|
|
|
<el-dialog v-model="open" title="服务日志详情" width="60%" append-to-body>
|
|
|
|
|
|
</el-dialog>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
</div>
|
|
|
</template>
|
|
|
<script setup>
|
|
|
+import * as echarts from 'echarts';
|
|
|
import {onMounted, reactive, ref} from "vue";
|
|
|
-import {getServiceLogList} from "@/api/service/log.js";
|
|
|
+import {getServiceLogList,getMdLogList,getMdStatusSum,getMdAllList } from "@/api/service/log.js";
|
|
|
import useClipboard from 'vue-clipboard3'
|
|
|
|
|
|
const {toClipboard} = useClipboard()
|
|
|
const {proxy} = getCurrentInstance();
|
|
|
-const tableHeight = ref(window.innerHeight - 280)
|
|
|
+const { model_run_log_event } = proxy.useDict("model_run_log_event");
|
|
|
+const tableHeight = ref(window.innerHeight - 400)
|
|
|
+const queryParams1 = ref({
|
|
|
+ time:[]
|
|
|
+})
|
|
|
const queryParams = reactive({
|
|
|
pageNum: 1,
|
|
|
pageSize: 20,
|
|
|
@@ -93,6 +190,10 @@ const queryParams = reactive({
|
|
|
sttm: "",
|
|
|
entm: "",
|
|
|
})
|
|
|
+const totalJian = ref(0)
|
|
|
+const tableDataJian = ref([])
|
|
|
+const pageNumJian = ref(1)
|
|
|
+const activeName = ref('first')
|
|
|
const tableData = ref([])
|
|
|
const total = ref(0)
|
|
|
const shortcuts = ref([
|
|
|
@@ -139,7 +240,191 @@ async function copyParams(message) {
|
|
|
proxy.$modal.msgSuccess("复制成功!");
|
|
|
})
|
|
|
}
|
|
|
+function fanJian(val){
|
|
|
+ pageNumJian.value = val
|
|
|
+ getList1()
|
|
|
+}
|
|
|
+function getList1(){
|
|
|
+ queryParams1.value.pageSize = 20
|
|
|
+ queryParams1.value.pageNum = pageNumJian.value
|
|
|
+ var par = {
|
|
|
+ pageNum:pageNumJian.value,
|
|
|
+ pageSize:20,
|
|
|
+ event:queryParams1.value.event,
|
|
|
+ mdName:queryParams1.value.mdName,
|
|
|
+ params:{
|
|
|
+ beginTime:queryParams1.value.time==[]?'':queryParams1.value.time[0],
|
|
|
+ endTime:queryParams1.value.time==[]?'':queryParams1.value.time[1],
|
|
|
+ }
|
|
|
+ }
|
|
|
+ getMdLogList(par).then(r=>{
|
|
|
+ tableDataJian.value = r.rows;
|
|
|
+ totalJian.value = r.total;
|
|
|
+ for(var i = 0;i<tableDataJian.value.length;i++){
|
|
|
+ for(var i1 = 0;i1<model_run_log_event.value.length;i1++){
|
|
|
+ if(tableDataJian.value[i].event===model_run_log_event.value[i1].value){
|
|
|
+ tableDataJian.value[i].event = model_run_log_event.value[i1].label
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+}
|
|
|
+function getLast7DaysRange() {
|
|
|
+ const end = new Date();
|
|
|
+ const start = new Date();
|
|
|
+ end.setDate(start.getDate() - 1);
|
|
|
+ start.setDate(end.getDate() - 7);
|
|
|
+ function formatDate(date) {
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(date.getDate()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ start: formatDate(start),
|
|
|
+ end: formatDate(end)
|
|
|
+ };
|
|
|
+}
|
|
|
+function mergeModelData(data) {
|
|
|
+ const mergeMap = {};
|
|
|
+
|
|
|
+ data.forEach(item => {
|
|
|
+ const mdName = item.mdName?.trim(); // 处理可能的空格
|
|
|
+ if (!mdName) return;
|
|
|
+
|
|
|
+ if (!mergeMap[mdName]) {
|
|
|
+ mergeMap[mdName] = {
|
|
|
+ mdName: mdName,
|
|
|
+ mdId: item.mdId,
|
|
|
+ statisNum: 0,
|
|
|
+ statisNumErr: 0
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ const current = mergeMap[mdName];
|
|
|
+ const statisNumValue = Number(item.statisNum) || 0;
|
|
|
+
|
|
|
+ if (item.statusCode === '200') {
|
|
|
+ // statusCode为200,保留statisNum
|
|
|
+ current.statisNum = statisNumValue;
|
|
|
+ } else if (item.statusCode) {
|
|
|
+ // 其他statusCode,累加到statisNumErr
|
|
|
+ current.statisNumErr += statisNumValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保留第一个非空的mdId
|
|
|
+ if (!current.mdId && item.mdId) {
|
|
|
+ current.mdId = item.mdId;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return Object.values(mergeMap);
|
|
|
+}
|
|
|
+async function drawRight2(){
|
|
|
+ var chartDom = document.getElementById('right2');
|
|
|
+ var myChart = echarts.init(chartDom);
|
|
|
+ var option;
|
|
|
+ const last7Days = getLast7DaysRange();
|
|
|
+ var par1 = {
|
|
|
+ params:{
|
|
|
+ beginTime:last7Days.start,
|
|
|
+ endTime:last7Days.end,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var uniqueMdNames
|
|
|
+ var mergedData
|
|
|
+ var success = []
|
|
|
+ var error = []
|
|
|
+ await getMdStatusSum(par1).then(res=>{
|
|
|
+ uniqueMdNames = [...new Map(res.data.map(item => [item.mdName, item.mdName])).values()].filter(Boolean);
|
|
|
+ mergedData = mergeModelData(res.data)
|
|
|
+ mergedData.forEach(item=>{
|
|
|
+ success.push(item.statisNum)
|
|
|
+ error.push(item.statisNumErr)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ option = {
|
|
|
+ grid: {
|
|
|
+ top: '15%', // 图表距离容器顶部的距离
|
|
|
+ right: '5%', // 图表距离容器右侧的距离
|
|
|
+ bottom: '15%', // 图表距离容器底部的距离
|
|
|
+ left: '10%', // 图表距离容器左侧的距离
|
|
|
+ containLabel: true // 确保坐标轴标签在 grid 区域内
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {},
|
|
|
+ color: ['#67C23A','#F56C6C',],
|
|
|
+ xAxis: [
|
|
|
+ {
|
|
|
+ type: 'category',
|
|
|
+ data: uniqueMdNames,
|
|
|
+ axisLabel: {
|
|
|
+ interval: 0, // 强制显示所有标签
|
|
|
+ rotate: -25, // 旋转角度,正值表示顺时针旋转,负值表示逆时针旋转
|
|
|
+ // 可以设置文字样式,如字体大小、颜色等
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 9,
|
|
|
+ color: '#333'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ type: 'value'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '成功',
|
|
|
+ type: 'bar',
|
|
|
+ stack: 'Ad',
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series'
|
|
|
+ },
|
|
|
+ data: success,
|
|
|
+
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '失败',
|
|
|
+ type: 'bar',
|
|
|
+ stack: 'Ad',
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series'
|
|
|
+ },
|
|
|
+ data: error,
|
|
|
+ label: {
|
|
|
+ show: true, // 显示标签
|
|
|
+ position: 'top', // 标签位置在柱子上方
|
|
|
+ // 自定义标签内容,formatter可以是字符串模板,也可以是回调函数
|
|
|
+ formatter: function(params) {
|
|
|
+ // 计算每个柱子占当天的总比例
|
|
|
+ const total = params.data + option.series[0].data[params.dataIndex];
|
|
|
+ const percent = ((params.data / total) * 100).toFixed(1);
|
|
|
+ if(params.data){
|
|
|
+ return params.data + `\n(${percent}%)`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 标签文本样式
|
|
|
+ textStyle: {
|
|
|
+ color: '#000', // 颜色
|
|
|
+ fontSize: 12
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ option && myChart.setOption(option);
|
|
|
|
|
|
+}
|
|
|
function getList() {
|
|
|
getServiceLogList(queryParams)
|
|
|
.then((r) => {
|
|
|
@@ -147,9 +432,163 @@ function getList() {
|
|
|
total.value = r.total;
|
|
|
});
|
|
|
}
|
|
|
+function processModelData(data) {
|
|
|
+ const resultMap = {};
|
|
|
+
|
|
|
+ data.forEach(item => {
|
|
|
+ const mdName = item.mdName?.trim();
|
|
|
+ if (!mdName) return;
|
|
|
+
|
|
|
+ const statisNum = Number(item.statisNum) || 0;
|
|
|
+
|
|
|
+ if (!resultMap[mdName]) {
|
|
|
+ // 初始化该模型的数据
|
|
|
+ resultMap[mdName] = {
|
|
|
+ mdName: mdName,
|
|
|
+ mdId: item.mdId || null,
|
|
|
+ totalStatisNum: statisNum,
|
|
|
+ recordCount: 1,
|
|
|
+ firstRecordDate: item.statisTm,
|
|
|
+ lastRecordDate: item.statisTm,
|
|
|
+ records: [item] // 可选:保留详细记录
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ // 更新现有模型数据
|
|
|
+ resultMap[mdName].totalStatisNum += statisNum;
|
|
|
+ resultMap[mdName].recordCount += 1;
|
|
|
+
|
|
|
+ // 更新最新记录日期
|
|
|
+ if (item.statisTm && (!resultMap[mdName].lastRecordDate || item.statisTm > resultMap[mdName].lastRecordDate)) {
|
|
|
+ resultMap[mdName].lastRecordDate = item.statisTm;
|
|
|
+ }
|
|
|
+
|
|
|
+ resultMap[mdName].records.push(item);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 转换为数组并排序(按统计数字降序)
|
|
|
+ return Object.values(resultMap)
|
|
|
+ .sort((a, b) => b.totalStatisNum - a.totalStatisNum);
|
|
|
+}
|
|
|
+function getDateRange(startStr, endStr) {
|
|
|
+ const startDate = new Date(startStr);
|
|
|
+ const endDate = new Date(endStr);
|
|
|
+ const dates = [];
|
|
|
+
|
|
|
+ // 验证日期有效性
|
|
|
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
|
+ throw new Error('无效的日期格式');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (startDate > endDate) {
|
|
|
+ throw new Error('开始日期不能晚于结束日期');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制开始日期对象,避免修改原日期
|
|
|
+ const currentDate = new Date(startDate);
|
|
|
+
|
|
|
+ // 循环直到当前日期超过结束日期
|
|
|
+ while (currentDate <= endDate) {
|
|
|
+ // 格式化为 YYYY-MM-DD
|
|
|
+ const year = currentDate.getFullYear();
|
|
|
+ const month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(currentDate.getDate()).padStart(2, '0');
|
|
|
+
|
|
|
+ dates.push(`${year}-${month}-${day}`);
|
|
|
+
|
|
|
+ // 日期加一天
|
|
|
+ currentDate.setDate(currentDate.getDate() + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return dates;
|
|
|
+}
|
|
|
+async function drawRight1(){
|
|
|
+ var chartDom = document.getElementById('right1');
|
|
|
+ var myChart = echarts.init(chartDom);
|
|
|
+ var option;
|
|
|
+ const last7Days = getLast7DaysRange();
|
|
|
+ var par = {
|
|
|
+ params:{
|
|
|
+ beginTime:last7Days.start,
|
|
|
+ endTime:last7Days.end,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var x = getDateRange(last7Days.start,last7Days.end)
|
|
|
+ var y = []
|
|
|
+ await getMdAllList(par).then(res=>{
|
|
|
+ res.data.forEach(item=>{
|
|
|
+ item.value = []
|
|
|
+ item.lineCharts.forEach(item1=>{
|
|
|
+ item.value.push(item1.num)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ res.data.forEach(item=>{
|
|
|
+ var par = {
|
|
|
+ name: item.mdName,
|
|
|
+ type: 'line',
|
|
|
+ data: item.value,
|
|
|
+ smooth: true
|
|
|
+ }
|
|
|
+ y.push(par)
|
|
|
+ })
|
|
|
+ console.log(res.data)
|
|
|
+ })
|
|
|
+ option = {
|
|
|
+ grid: {
|
|
|
+ top: '15%', // 图表距离容器顶部的距离
|
|
|
+ right: '5%', // 图表距离容器右侧的距离
|
|
|
+ bottom: '15%', // 图表距离容器底部的距离
|
|
|
+ left: '10%', // 图表距离容器左侧的距离
|
|
|
+ containLabel: true // 确保坐标轴标签在 grid 区域内
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ top:'90%'
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ splitLine: { show: false },
|
|
|
+ // type: 'category',
|
|
|
+ data: x,
|
|
|
+ axisLabel: {
|
|
|
+ interval: 0, // 强制显示所有标签
|
|
|
+ rotate: -25, // 旋转角度,正值表示顺时针旋转,负值表示逆时针旋转
|
|
|
+ // 可以设置文字样式,如字体大小、颜色等
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 9,
|
|
|
+ color: '#333'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ show: true,
|
|
|
+ splitLine: { show: false },
|
|
|
+ name: '单位:次',
|
|
|
+ minInterval: 1,
|
|
|
+ axisTick: {
|
|
|
+ show: true // 确保显示刻度线
|
|
|
+ },
|
|
|
+ axisLine: {
|
|
|
+ show: true, // 确保显示轴线
|
|
|
+ lineStyle: {
|
|
|
+ color: '#333', // 可以设置轴线的颜色,例如与文字颜色一致
|
|
|
+ // width: 1 // 可以设置轴线宽度,可选
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ series:y
|
|
|
+ };
|
|
|
+
|
|
|
+ option && myChart.setOption(option);
|
|
|
|
|
|
+}
|
|
|
onMounted(() => {
|
|
|
getList()
|
|
|
+ getList1()
|
|
|
+ drawRight2()
|
|
|
+ drawRight1()
|
|
|
});
|
|
|
|
|
|
watch(() => queryParams.time, value => {
|