소스 검색

feat: 风险预测试开发

zhangwenya 8 달 전
부모
커밋
8033f9acc4

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

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+export function listModel(){
+    return request({
+        url: '/risk/other/model/list',
+        method: 'get',
+    })
+}
+
+export function listHost(modelId){
+    return request({
+        url: '/risk/other/host/'+modelId,
+        method: 'get',
+    })
+}
+
+export function listConfig(query){
+    return request({
+        url: '/risk/config/list',
+        method: 'get',
+        params: query
+    })
+}
+
+export function updateConfig(data){
+    return request({
+        url: '/risk/config',
+        method: 'put',
+        data
+    })
+}
+export function addConfig(data){
+    return request({
+        url: '/risk/config',
+        method: 'post',
+        data
+    })
+}
+
+export function deleteConfig(hcIds){
+    return request({
+        url: '/risk/config/'+hcIds,
+        method: 'delete'
+    })
+}

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

@@ -16,7 +16,7 @@
           </div>
         </div>
       </div>
-      <div class="chart-sort">
+      <div class="chart-sort" v-if="trendData[active]">
         {{trendData[active].objName || "-"}}
         <div ref="chartSort" style="height: 218px;" />
       </div>

+ 1 - 1
src/views/risk/r2/components/riskDetail.vue

@@ -68,7 +68,7 @@ async function initChart(item){
     xAxis: {
       type: 'category',
       boundaryGap: false,
-      data: data.times.map(item => parseTime(item, '{y}-{m}-{d} {h}:{i}:{s}')),
+      data: data.times.map(item => parseTime(item, '{y}-{m}-{d}')),
       axisLine: {
         show: true,
         lineStyle: {

+ 6 - 6
src/views/risk/r2/css/style.scss

@@ -46,10 +46,10 @@
 
     }
   }
-  .top-title{
-    font-weight: bolder;
-    margin-bottom: 10px;
-    border-bottom: 1px solid #EEE;
-    padding-bottom: 10px;
-  }
+}
+.top-title{
+  font-weight: bolder;
+  margin-bottom: 10px;
+  border-bottom: 1px solid #EEE;
+  padding-bottom: 10px;
 }

+ 10 - 4
src/views/risk/r3/components/addHz.vue

@@ -64,7 +64,7 @@
         <el-col :span="24" v-if="form.objType === '2'">
           <el-form-item label="">
             <div class="tree-row" >
-              <el-tree style="max-width: 600px" :props="props" :data="modelTreeData" show-checkbox ref="treeRef"
+              <el-tree style="max-width: 600px" :props="propsNode" :data="modelTreeData" show-checkbox ref="treeRef"
                        node-key="objId"
                        @check-change="handleCheckChange"/>
             </div>
@@ -81,8 +81,14 @@
 <script setup lang="ts">
 import {onMounted, reactive} from "vue";
 import {listModelMs, listModelObj} from "@/api/risk/r2";
-import {addModel, updateModel} from "@/api/risk/model"
+import {addModel, updateModel} from "@/api/risk/model";
 
+const props = defineProps({
+  riskType:{
+    type:String,
+    default:"1"
+  }
+})
 const objRef = ref(null)
 const {proxy} = getCurrentInstance()
 const {
@@ -98,7 +104,7 @@ const treeIndex = ref([])
 const data = reactive({
   form: {
     metricsName: "",
-    riskType: "1",
+    riskType: props.riskType,
     upDown:"",
     metricsId: null,
     objType: "1",
@@ -116,7 +122,7 @@ const metricsData = ref([])
 const modelTreeData = ref([])
 const {form, rules} = toRefs(data)
 
-const props = {
+const propsNode = {
   label: 'objName',
 }
 

+ 8 - 2
src/views/risk/r3/components/historyAnalyse.vue

@@ -35,7 +35,7 @@
   />
 
   <el-dialog v-model="visible" title="分析结果" width="800">
-    <now-analyse :rows="rows"/>
+    <now-analyse :rows="rows" v-if="visible"/>
   </el-dialog>
 
 </template>
@@ -43,8 +43,14 @@
 import {listAnalysis} from "@/api/risk/r3"
 import {onMounted} from "vue";
 import nowAnalyse from "./nowAnalyse.vue"
+const props = defineProps({
+  riskType:{
+    type:String,
+    default:"1"
+  }
+})
 const params = ref({
-  riskType:1,
+  riskType:props.riskType,
   metricsName:"",
   objName:"",
   pageNum:1,

+ 9 - 2
src/views/risk/r3/components/hzAnalyse.vue

@@ -60,7 +60,7 @@
 
   <el-dialog v-model="visible" :title="dialogInfo[active].title" width="50%" :close-on-click-modal="false">
     <component :is="dialogInfo[active].component" @success="getModelList" v-if="visible" @cancel="visible=false"
-               ref="acRef" :rows="rows"/>
+               ref="acRef" :rows="rows" :riskType="riskType"/>
   </el-dialog>
 </template>
 <script setup lang="ts">
@@ -69,16 +69,23 @@ import {delModel} from "@/api/risk/model"
 import {onMounted} from "vue";
 import addHz from "./addHz.vue"
 import {analysisAtOnce} from "@/api/risk/r2"
+const props = defineProps({
+  riskType:{
+    type:String,
+    default:"1"
+  }
+})
 
 const {proxy} = getCurrentInstance()
 const emit = defineEmits(["success"])
 const {analy_result, risk_up, sys_yes_no} = proxy.useDict('analy_result', 'risk_up', 'sys_yes_no');
 const params = ref({
-  riskType: 1,
+  riskType: props.riskType,
   metricsName: "",
   pageNum: 1,
   pageSize: 10
 })
+
 const visible = ref(false)
 const dialogInfo = [
   {title: "新增指标", component: addHz},

+ 25 - 32
src/views/risk/r3/components/nowAnalyse.vue

@@ -5,17 +5,21 @@
         <el-form-item label="数据有效性">
           <el-row :gutter="20">
             <el-col :span="24">
-              <el-tag effect="light" :type="rows.normalValid === '通过'?'success':'info'">正态检测: {{ rows.normalValid }}</el-tag>
+              <el-tag effect="light" :type="rows.normalValid === '通过'?'success':'info'">正态检测: {{
+                  rows.normalValid
+                }}
+              </el-tag>
             </el-col>
             <el-col :span="24">
-              <el-tag effect="light" :type="rows.varianceValid === '通过'?'success':'info'">方差齐性检测: {{ rows.varianceValid }}
+              <el-tag effect="light" :type="rows.varianceValid === '通过'?'success':'info'">方差齐性检测:
+                {{ rows.varianceValid }}
               </el-tag>
             </el-col>
           </el-row>
         </el-form-item>
         <el-form-item label="结果">
-          <el-tag v-if="rows.result=='趋势恶化'" type="danger">{{rows.result}}</el-tag>
-          <el-tag v-else >{{rows.result}}</el-tag>
+          <el-tag v-if="rows.result=='趋势恶化'" type="danger">{{ rows.result }}</el-tag>
+          <el-tag v-else>{{ rows.result }}</el-tag>
         </el-form-item>
         <el-form-item label="置信区间">
           {{ rows.confidenceLevel }}
@@ -26,39 +30,26 @@
       </el-form>
       <div>
         <div class="top-title">均值比较</div>
-        <el-image style="width: 400px" :src="rows.imgPath" fit="scale-down"/>
+        <div ref="chartTop" style="height: 200px;width: 400px"/>
       </div>
     </div>
   </div>
-  <div class="chart-row" v-if="activeTop">
-    <div ref="chartTop" style="height: 300px;"/>
+  <div class="chart-row">
+    <div class="top-title">2周数据图</div>
+    <el-image style="width: 300px" :src="rows.imgPath" fit="scale-down"/>
   </div>
 </template>
 <script setup lang="ts">
-import {parseTime} from "@/utils/ruoyi"
-import {analysisAtOnce, analysisTypeTrend} from "@/api/risk/r2"
-import {watchEffect} from "vue";
+import {onMounted} from "vue";
 import * as echarts from "echarts";
 
 const chartTop = ref(null)
-const maxNum = ref(0)
 const props = defineProps(['rows'])
-const analysisData = ref({})
-const colors = ['#f56c6c', '#e6a23c', '#409EFF']
-const activeTop = ref(null)
-watchEffect(() => {
-  // getAnalysisData()
+onMounted(()=>{
+  initChart(props.rows)
 })
 
-async function getAnalysisData() {
-  const res = await analysisAtOnce(props.rows.riskId)
-  analysisData.value = res.data
-  maxNum.value = Math.max(...analysisData.value.top.map(item => item.num)) + 20
-}
-
 async function initChart(item) {
-  activeTop.value = item
-  const {data} = await analysisTypeTrend(item.id)
   const myChart = echarts.init(chartTop.value);
   const option = {
     tooltip: {
@@ -68,13 +59,12 @@ async function initChart(item) {
       left: '0%',
       right: '0%',
       bottom: '3%',
-      top: '15%',
+      top: '5%',
       containLabel: true
     },
     xAxis: {
       type: 'category',
-      boundaryGap: false,
-      data: data.times.map(item => parseTime(item, '{y}-{m}-{d} {h}:{i}:{s}')),
+      data: ['本周', '上周'],
       axisLine: {
         show: true,
         lineStyle: {
@@ -103,11 +93,14 @@ async function initChart(item) {
       },
     },
     series: {
-      type: 'line',
-      stack: 'Total',
-      smooth: true,
-      symbolSize: 0,
-      data: data.values
+      type: 'bar',
+      data: item.avgValue ? item.avgValue.split(",") : [],
+      barMaxWidth: 25,
+      itemStyle: {
+        normal: {
+          barBorderRadius:[10, 10 , 0, 0]
+        }
+      }
     }
   };
   myChart.setOption(option)

+ 23 - 0
src/views/risk/r4/index.vue

@@ -0,0 +1,23 @@
+<template>
+  <div class="app-container state-err">
+    <el-card class="state-row">
+      <template #header>成史分析</template>
+      <history-analyse ref="haRef" riskType="2"/>
+    </el-card>
+    <el-card class="state-row">
+      <template #header>数值分析指标</template>
+      <hz-analyse @success="handleReload" riskType="2"/>
+    </el-card>
+  </div>
+</template>
+<script setup lang="ts">
+import historyAnalyse from "../r3/components/historyAnalyse.vue"
+import hzAnalyse from "../r3/components/hzAnalyse.vue"
+const haRef = ref(null)
+function handleReload(){
+  haRef.value.getAnalysis()
+}
+</script>
+<style lang="scss">
+@import "../r3/css/style.scss";
+</style>

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

@@ -0,0 +1,48 @@
+<template>
+  <div class="top-title">{{title}}</div>
+  <div class="sub-tip">
+    <span class="tip-alarm"/>告警次数
+    <span class="tip-title"/>持续时间
+  </div>
+  <div v-for="(item,index) in data" :key="index" class="top-style">
+      <span class="index" :style="`background:${colors[index]||'#A1B3D2'}`">
+        {{ index + 1 }}
+      </span>
+    <div class="progress-row">
+      {{ item.objName }}
+      <div class="row-flex">
+        <div class="progress-alarm">
+          <div class="alarms" :style="`width:${item.alarms/maxAlarms*100}%`">{{item.alarms}}</div>
+        </div>
+        <div class="progress-alarm">
+          <div class="time" :style="`width:${item.value/maxNum*100}%`">{{item.value}}</div>
+        </div>
+      </div>
+
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+const props = defineProps({
+  data: {
+    type: Array,
+    default: () => []
+  },
+  title:{
+    type: String,
+    default: ''
+  },
+  colors:{
+    type: Array,
+    default: () => []
+  }
+})
+const maxNum = computed(() => {
+  return Math.max(...props.data.map(item => item.value))+10
+})
+const maxAlarms = computed(() => {
+  return Math.max(...props.data.map(item => item.alarms))+10
+})
+</script>
+<style scoped lang="scss">
+</style>

+ 35 - 0
src/views/risk/r5/components/cpuTop.vue

@@ -0,0 +1,35 @@
+<template>
+    <div class="top-title">{{title}}</div>
+    <div v-for="(item,index) in data" :key="index" class="top-style">
+      <span class="index" :style="`background:${colors[index]||'#A1B3D2'}`">
+        {{ index + 1 }}
+      </span>
+      <div class="progress-row">
+        {{ item.objName }}
+        <div class="progress-bg">
+          <div class="progress-bar"
+               :style="`width:${item.value/maxNum*100}%;background:${colors[index]||'#A1B3D2'}`"/>
+          {{ item.value }}
+        </div>
+      </div>
+    </div>
+</template>
+<script setup lang="ts">
+const props = defineProps({
+  data: {
+    type: Array,
+    default: () => []
+  },
+  title:{
+    type: String,
+    default: ''
+  },
+  colors:{
+    type: Array,
+    default: () => []
+  }
+})
+const maxNum = computed(() => {
+  return Math.max(...props.data.map(item => item.value))+10
+})
+</script>

+ 144 - 0
src/views/risk/r5/components/hostSetting.vue

@@ -0,0 +1,144 @@
+<template>
+  <el-button type="primary" @click="editConfig(null)">新增配置</el-button>
+  <el-table :data="configData" border style="width: 100%;margin-top:10px;" size="small">
+    <el-table-column prop="metricsName" label="指标名称" />
+    <el-table-column prop="viewName" label="图表标题" />
+    <el-table-column label="排名依据">
+        <template #default="{row}">
+          <dict-tag :options="ranking_based" :value="row.rankingBased"/>
+        </template>
+    </el-table-column>
+    <el-table-column label="排名依据" width="130">
+      <template #default="{row}">
+        <el-button type="primary" link @click="editConfig(row)">编辑</el-button>
+        <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>
+  <pagination
+      style="margin-top:10px"
+      v-show="total>0"
+      :total="total"
+      v-model:page="query.pageNum"
+      v-model:limit="query.pageSize"
+      @pagination="getConfigList"
+  />
+  <el-dialog v-model="visible" :title="title" width="500">
+    <el-form ref="objRef" :model="form" :rules="rules" label-width="100px" label-suffix=":">
+      <el-form-item label="指标" prop="metricsId">
+        <el-select v-model="form.metricsId" filterable placeholder="请选择..." style="width: 80%" @change="changeMetricsId">
+          <el-option v-for="item in metricsData" :key="item.metricsId" :label="item.metricsName" :value="item.metricsId"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="图表标题" prop="viewName">
+        <el-input v-model="form.viewName" placeholder="请输入图表标题" style="width: 80%" />
+      </el-form-item>
+      <el-form-item label="排名依据" prop="rankingBased">
+        <el-select v-model="form.rankingBased" placeholder="请选择..." style="width: 80%">
+          <el-option v-for="item in ranking_based" :key="item.value" :label="item.label" :value="item.value"/>
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <div class="btn-row">
+      <el-button type="primary" @click="handleSubmit">确定</el-button>
+      <el-button @click="visible=false">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import {listConfig,updateConfig,deleteConfig,addConfig} from "@/api/risk/r5"
+import {listModelMs} from "@/api/risk/r2";
+import {onMounted, ref} from "vue";
+const {proxy} = getCurrentInstance()
+const {ranking_based} = proxy.useDict("ranking_based")
+
+const query = ref({
+  configType:"host",
+  pageNum:1,
+  pageSize:10
+})
+const initForm={
+  metricsId:"",
+  metricsName:"",
+  viewName:"",
+  rankingBased:"",
+  configType : "host"
+}
+const form = ref({
+  ...initForm
+})
+const configData = ref([])
+const visible = ref(false)
+const title = ref("")
+const total = ref(0)
+const rules={
+  metricsId: [{required: true, message: "请选择指标"}],
+  viewName: [{required: true, message: "请输入图表标题"}],
+  rankingBased: [{required: true, message: "请选择排名依据"}]
+}
+const metricsData=ref([])
+onMounted(()=>{
+  getConfigList()
+})
+
+async function handleDelete(row){
+  const res = await deleteConfig(row.hcId)
+  updateData(res)
+}
+
+async function getConfigList(){
+  visible.value = false
+  const res = await listConfig(query.value)
+  configData.value = res.rows
+  total.value = res.total
+}
+
+function changeMetricsId(id) {
+  form.value.metricsName = metricsData.value.find(item => item.metricsId === id).metricsName
+}
+
+async function getMetricsList(){
+  const res = await listModelMs()
+  metricsData.value = res.data
+}
+
+function updateData(res){
+  proxy.$message.success(res.msg)
+  getConfigList()
+  proxy.$emit("update")
+}
+
+function handleSubmit(){
+  proxy.$refs.objRef.validate(async valid => {
+    if(valid){
+      const res = form.value.hcId != undefined ? await updateConfig(form.value) : await addConfig(form.value)
+      updateData(res)
+    }
+  })
+}
+
+async function editConfig(row){
+  visible.value = true
+  if(!row){
+    title.value = "新增配置"
+    form.value = {...initForm}
+  }else{
+    title.value = "编辑配置"
+    form.value = {...row}
+  }
+  proxy.resetForm("objRef");
+  !metricsData.value.length && await getMetricsList()
+}
+
+</script>
+<style lang="scss" scoped>
+.btn-row{
+  display: flex;
+  justify-content: center;
+  margin-top:30px;
+}
+</style>

+ 106 - 0
src/views/risk/r5/css/style.scss

@@ -0,0 +1,106 @@
+@import "@/assets/styles/common.scss";
+.host-analysis{
+  margin: -10px;
+  .top-right-btn{
+    position: absolute;
+    z-index: 111;
+    right:15px;
+    top:14px;
+  }
+  .tab-content{
+    @include flexBetween();
+    width: 100%;
+    flex-wrap: wrap;
+    .t-c-row{
+      width: 48%;
+      margin-bottom: 20px;
+      .top-title{
+        font-weight: bolder;
+        margin-bottom:10px;
+        padding:10px ;
+        color:#666;
+        font-size: 14px;
+      }
+    }
+  }
+  .sub-tip{
+    text-align: center;
+    font-size: 12px;
+    color:#888;
+    @include flexCenter;
+    .tip-alarm{
+      width: 20px;
+      height:10px;
+      background: #409EFF;
+      display: inline-block;
+      margin:0 5px;
+      border-radius: 5px;
+    }
+    .tip-title{
+      @extend .tip-alarm;
+      background: #e6a23c;
+    }
+  }
+
+  .top-style{
+    margin:10px 0;
+    @include flexStart();
+    cursor: pointer;
+    &:last-child{
+      margin-bottom:0;
+    }
+    .index{
+      width: 20px;
+      font-size: 12px;
+      text-align: center;
+      line-height: 18px;
+      display: inline-block;
+      background: #A1B3D2;
+      border-radius: 100%;
+      color:white;
+      margin-right:10px;
+    }
+    .progress-row{
+      width: 100%;
+      color: #6f7988;
+      font-size: 12px;
+      .progress-bg{
+        background: #A1B3D280;
+        height:12px;
+        width: 100%;
+        border-radius: 6px;
+        margin-top:5px;
+        @include flexStart;
+        font-size: 12px;
+        .progress-bar{
+          height:12px;
+          border-radius: 6px;
+          background: #A1B3D2;
+          margin-right:5px;
+          transition: all .3s;
+        }
+      }
+      .row-flex{
+        @include flexStart;
+        margin-top:5px;
+      }
+      .progress-alarm{
+        width: 48%;
+        .alarms{
+          background: #409EFF;
+          line-height:12px;
+          font-size: 12px;
+          color:white;
+          border-radius: 6px;
+          transition: all .3s;
+          @include flexCenter;
+        }
+        .time{
+          background: #e6a23c;
+          @extend .alarms;
+        }
+      }
+    }
+
+  }
+}

+ 62 - 0
src/views/risk/r5/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <div class="app-container host-analysis">
+    <div class="top-right-btn">
+      <el-button type="primary" @click="handleSet">主机指标配置</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 v-for="(item,index) in hostData" :key="index" class="t-c-row">
+          <cpu-top  v-if="item.data[0].alarms === undefined" :data="item.data" :title="item.title" :colors="colors" />
+          <alarms-top v-if="item.data[0].alarms" :data="item.data" :title="item.title" :colors="colors" />
+        </div>
+      </div>
+    </el-tabs>
+    <el-empty description="暂无数据" v-else/>
+  </div>
+
+  <el-dialog title="主机指标配置" v-model="visible" width="800">
+    <host-setting v-if="visible" @close="visible = false" @update="getModelList"/>
+  </el-dialog>
+
+</template>
+<script setup lang="ts">
+import {listHost, listModel} from "@/api/risk/r5"
+import {onMounted} from "vue";
+import cpuTop from "./components/cpuTop.vue"
+import alarmsTop from "./components/alarmsTop.vue"
+import hostSetting from "./components/hostSetting.vue"
+
+const colors = ['#f56c6c', '#e6a23c', '#409EFF']
+const modelData = ref([])
+const activeName = ref('')
+const hostData = ref([])
+const visible = ref(false)
+onMounted(() => {
+  getModelList()
+})
+
+async function getModelList() {
+  const res = await listModel()
+  modelData.value = res.data
+  activeName.value = modelData.value[0].modelName
+  await getHostInfo(modelData.value[0].modelId)
+}
+
+function handleSet(){
+  visible.value = true
+}
+
+function handleClick(tab) {
+  getHostInfo(modelData.value[tab.index].modelId)
+}
+
+async function getHostInfo(modelId) {
+  const res = await listHost(modelId)
+  hostData.value = res.data
+}
+
+</script>
+<style lang="scss">
+@import "./css/style.scss";
+</style>