Prechádzať zdrojové kódy

feat: 风险预测功能开发

zhangwenya 8 mesiacov pred
rodič
commit
c1953235fd

+ 7 - 0
src/api/risk/r5.js

@@ -42,3 +42,10 @@ export function deleteConfig(hcIds){
         method: 'delete'
     })
 }
+
+export function listNetwork(modelId,metricsId){
+    return request({
+        url: '/risk/other/network/'+modelId+'/'+metricsId,
+        method: 'get',
+    })
+}

+ 7 - 0
src/api/risk/r7.js

@@ -0,0 +1,7 @@
+import request from '@/utils/request';
+export function otherServer() {
+    return request({
+        url: '/risk/other/server',
+        method: 'get',
+    })
+}

+ 1 - 1
src/views/index/widget/healthTendency.vue

@@ -94,7 +94,7 @@ function initChart(res) {
       return {
         name: p.name,
         type: 'line',
-        stack: 'Total',
+        // stack: 'Total',
         smooth: true,
         symbolSize: 0,
         data: p.score

+ 0 - 1
src/views/risk/r5/components/alarmsTop.vue

@@ -18,7 +18,6 @@
           <div class="time" :style="`width:${item.value/maxNum*100}%`">{{item.value}}</div>
         </div>
       </div>
-
     </div>
   </div>
 </template>

+ 54 - 1
src/views/risk/r5/css/style.scss

@@ -11,8 +11,9 @@
     @include flexBetween();
     width: 100%;
     flex-wrap: wrap;
+    align-items: flex-start;
     .t-c-row{
-      width: 48%;
+      width: 30%;
       margin-bottom: 20px;
       .top-title{
         font-weight: bolder;
@@ -22,6 +23,41 @@
         font-size: 14px;
       }
     }
+    .tab-chart{
+      width: calc(100% - 150px);
+    }
+    .config-bar{
+      width: 150px;
+      .c-row{
+        border-bottom:1px solid #f9f9f9;
+        font-size: 14px;
+        color:#444;
+        line-height:32px;
+        cursor: pointer;
+        transition: all .3s;
+        @include flexStart;
+        .s-span{
+          display: none;
+          margin-right:5px;
+        }
+        &:hover{
+          .s-span{
+            display: inline-block;
+          }
+          color: #409EFF;
+          padding-left:5px;
+          font-weight: bolder;
+        }
+      }
+      .active{
+        .s-span{
+          display: inline-block;
+        }
+        color: #409EFF;
+        padding-left:5px;
+        font-weight: bolder;
+      }
+    }
   }
   .sub-tip{
     text-align: center;
@@ -101,6 +137,23 @@
         }
       }
     }
+  }
 
+  .r7-row{
+    border:1px solid #F1F1F1;
+    margin-bottom: 10px;
+    &-title{
+      line-height:36px;
+      background-image: linear-gradient(to right top,#f9f9f9,#F4F4F4);
+      color:#444;
+      padding:0 10px;
+      border-bottom:1px solid #F1F1F1;
+      font-size: 16px;
+    }
+    &-content{
+      padding:0 15px 20px 15px;
+      color:#444;
+    }
   }
+
 }

+ 110 - 2
src/views/risk/r6/components/indexMapping.vue

@@ -1,7 +1,115 @@
 <template>
-
+  <el-button type="primary" size="small" @click="addIndex">新增指标</el-button>
+  <el-table :data="configData" border stripe style="width: 100%;margin-top:10px;" size="small">
+    <el-table-column label="显示名称">
+      <template #default="scope">
+        {{ scope.row.viewName }}
+      </template>
+    </el-table-column>
+    <el-table-column label="指标映射">
+      <template #default="scope">
+        <el-select v-model="scope.row.metricsName" size="small" style="width: 100%" @change="changeMetrics(scope.row)">
+          <el-option v-for="item in mappingData" :key="item.id" :label="item.metricsName" :value="item.metricsName"/>
+        </el-select>
+      </template>
+    </el-table-column>
+    <el-table-column label="操作" width="80" align="center">
+      <template #default="{row}">
+        <el-popconfirm title="确认要删除该条信息吗?" @confirm="handleDelete(row)" width="200">
+          <template #reference>
+            <el-button type="danger" link>删除</el-button>
+          </template>
+        </el-popconfirm>
+      </template>
+    </el-table-column>
+  </el-table>
+  <el-dialog v-model="visible" title="新增指标" width="400" append-to-body>
+    <el-form ref="objRef" :model="form" :rules="rules" label-width="100px" label-suffix=":" size="small">
+      <el-form-item label="显示名称" prop="viewName">
+        <el-input v-model="form.viewName" placeholder="请输入显示名称"/>
+      </el-form-item>
+      <el-form-item label="指标映射" prop="metricsName">
+        <el-select v-model="form.metricsName" style="width: 100%">
+          <el-option v-for="item in mappingData" :key="item.id" :label="item.metricsName" :value="item.metricsName"/>
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <div class="btn-row">
+      <el-button type="primary" @click="handleSubmit" size="small">确定</el-button>
+      <el-button @click="visible=false" size="small">取消</el-button>
+    </div>
+  </el-dialog>
 </template>
 <script setup lang="ts">
+import {listConfig} from "@/api/risk/r5"
+import {delConfig, updateConfig,addConfig} from "@/api/risk/config"
+
+const {proxy} = getCurrentInstance()
+const props = defineProps(['configData'])
+const mappingData = ref([])
+const visible = ref(false)
+
+const defaultForm = {
+  viewName: '',
+  metricsId: '',
+  metricsName: ''
+}
+const rules = {
+  viewName: [
+    {required: true, message: '请输入显示名称', trigger: 'blur'}
+  ],
+  metricsName: [
+    {required: true, message: '请选择指标映射', trigger: 'change'}
+  ]
+}
+
+const form = ref({
+  ...defaultForm
+})
+
+onMounted(async () => {
+  const res = await listConfig({configType: "host", pageNum: 1, pageSize: 20})
+  mappingData.value = res.rows
+})
+
+function addIndex() {
+  visible.value = true
+  form.value = {...defaultForm}
+  proxy.resetForm("objRef");
+}
+
+async function changeMetrics(row) {
+  const data = {...row}
+  data.metricsId = mappingData.value.find(item => item.metricsName === row.metricsName).metricsId
+  const res = await updateConfig(data)
+  handleSuccess(res)
+}
+
+async function handleDelete(row) {
+  const res = await delConfig(row.hcId)
+  handleSuccess(res)
+}
+
+function handleSuccess(res) {
+  proxy.$message.success(res.msg)
+  proxy.$emit('refresh')
+}
+
+function handleSubmit() {
+  proxy.$refs.objRef.validate(async valid => {
+    if (valid) {
+      const data = {...form.value}
+      data.metricsId = mappingData.value.find(item => item.metricsName === form.value.metricsName).metricsId
+      const res = await addConfig(data)
+      handleSuccess(res)
+    }
+  })
+}
 </script>
-<style scoped lang="scss">
+<style lang="scss" scoped>
+.btn-row{
+  display: flex;
+  justify-content: center;
+  margin-top:30px;
+}
 </style>

+ 144 - 1
src/views/risk/r6/index.vue

@@ -1,7 +1,150 @@
 <template>
+  <div class="app-container host-analysis">
+    <div class="top-right-btn">
+      <el-button type="primary" @click="handleIMap">指标映射</el-button>
+    </div>
+    <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick" type="border-card" v-if="modelData.length">
+      <el-tab-pane :label="item.modelName" :name="item.modelName" v-for="item in modelData" :key="item.modelId"/>
+      <div class="tab-content">
+        <div class="config-bar">
+          <div v-for="item in configData" :key="item.hcId" :class="['c-row',{'active':item.metricsId === activeConfig}]"
+               @click="changeConfig(item)">
+            <span class="s-span"> > </span>
+            {{ item.viewName }}
+          </div>
+        </div>
+        <div class="tab-chart">
+          <div ref="chartSort" style="height: 400px"/>
+        </div>
+      </div>
+    </el-tabs>
+    <el-empty description="暂无数据" v-else/>
+  </div>
 
+  <el-dialog v-model="visible" title="指标映射" width="500">
+    <index-mapping :configData="configData" @refresh="initConfig"/>
+  </el-dialog>
 </template>
 <script setup lang="ts">
+import {listConfig, listModel, listNetwork} from "@/api/risk/r5"
+import {parseTime} from "@/utils/ruoyi"
+import {onMounted} from "vue";
+import * as echarts from "echarts";
+import indexMapping from "./components/indexMapping.vue"
+const modelData = ref([])
+const activeName = ref('')
+const configData = ref([])
+const activeConfig = ref(null)
+const chartSort = ref(null)
+const visible = ref(false)
+
+onMounted(() => {
+  initConfig()
+})
+
+async function initConfig(){
+  await getConfig()
+  await getModelList()
+}
+
+function handleIMap(){
+  visible.value = true
+}
+
+async function getModelList() {
+  const res = await listModel()
+  modelData.value = res.data
+  activeName.value = modelData.value[0].modelName
+  activeConfig.value = configData.value[0].metricsId
+  await getNetwork(modelData.value[0].modelId, configData.value[0].metricsId)
+}
+
+async function getConfig() {
+  const res = await listConfig({configType: 'network', pageNum: 1, pageSize: 100})
+  configData.value = res.rows
+}
+
+function handleClick(tab) {
+  activeConfig.value = configData.value[0].metricsId
+  getNetwork(modelData.value[tab.index].modelId, configData.value[0].metricsId)
+}
+
+function changeConfig(item) {
+  activeConfig.value = item.metricsId
+  const modelId = modelData.value.find(p => p.modelName === activeName.value).modelId
+  getNetwork(modelId, activeConfig.value)
+}
+
+async function getNetwork(modelId, metricsId) {
+  const res = await listNetwork(modelId, metricsId)
+  initChart(res.data)
+}
+
+function initChart(res){
+  const myChart = echarts.init(chartSort.value);
+  const option = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: res.map(p=>p.name),
+      textStyle: {
+        color: "#849ac4"
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: res[0].data.map(p=>parseTime(p.time,'{y}-{m}-{d}')),
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#849ac4",
+          width: 0,
+        }
+      },
+      axisTick: {
+        show: false,
+      },
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#849ac4",
+          width: 0,
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: "#849ac480",
+          type: 'dashed',
+        }
+      },
+    },
+    series:res.map(p=>{
+      return {
+        name: p.name,
+        type: 'line',
+        smooth: true,
+        symbolSize: 0,
+        data: p.data.map(j=>j.value)
+      }
+    })
+  };
+  myChart.setOption(option)
+}
+
+
 </script>
-<style scoped lang="scss">
+<style lang="scss">
+@import "../r5/css/style.scss";
 </style>

+ 106 - 2
src/views/risk/r7/index.vue

@@ -1,7 +1,111 @@
 <template>
-
+  <div class="app-container host-analysis">
+    <div class="r7-row">
+      <div class="r7-row-title">健康月度曲线</div>
+      <div ref="chartMonth" style="height: 300px"/>
+    </div>
+    <div class="r7-row">
+      <div class="r7-row-title">当日健康度排名</div>
+      <div class="r7-row-content">
+        <div v-for="(item,index) in serverTop" :key="index" class="top-style">
+          <span class="index" :style="`background:${colors[index]||'#A1B3D2'}`">
+            {{ index + 1 }}
+          </span>
+          <div class="progress-row">
+            {{ item.name }}
+            <div class="progress-bg">
+              <div class="progress-bar"
+                   :style="`width:${item.score/maxNum*100}%;background:${colors[index]||'#A1B3D2'}`"/>
+              {{ item.score }}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 </template>
 <script setup lang="ts">
+import {otherServer} from "@/api/risk/r7"
+import {onMounted} from "vue"
+import * as echarts from "echarts"
+
+const chartMonth = ref(null)
+const colors = ['#f56c6c', '#e6a23c', '#409EFF']
+const serverTop = ref([])
+const maxNum = ref(0)
+onMounted(() => {
+  getServerInfo()
+})
+
+async function getServerInfo() {
+  const res = await otherServer()
+  serverTop.value = res.data.top
+  maxNum.value = Math.max(...serverTop.value.map(p => p.score))
+  initChartMonth(res.data.trend)
+}
+
+function initChartMonth(res) {
+  const myChart = echarts.init(chartMonth.value)
+  const option = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: res.map(p => p.modelName),
+      textStyle: {
+        color: "#849ac4"
+      }
+    },
+    grid: {
+      left: '1%',
+      right: '1%',
+      bottom: '3%',
+      top: '10%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: res[0].xdata,
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#849ac4",
+          width: 0,
+        }
+      },
+      axisTick: {
+        show: false,
+      },
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#849ac4",
+          width: 0,
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: "#849ac480",
+          type: 'dashed',
+        }
+      },
+    },
+    series: res.map(p => ({
+      name: p.modelName,
+      type: 'line',
+      smooth: true,
+      symbolSize: 0,
+      data: p.scores
+    }))
+  };
+  myChart.setOption(option)
+}
+
 </script>
-<style scoped lang="scss">
+<style lang="scss">
+@import "../r5/css/style.scss";
 </style>