Bladeren bron

feat: 首页开发

zhangwenya 8 maanden geleden
bovenliggende
commit
76c18702b6

+ 86 - 0
src/api/index/hl.js

@@ -0,0 +1,86 @@
+import request from '@/utils/request'
+
+export function hlCheck(){
+    return request({
+        url: '/index/hl/check',
+        method: 'get'
+    })
+}
+
+export function riskList(status){
+    return request({
+        url: '/index/risk?status='+status,
+        method: 'get'
+    })
+}
+
+export function hlCurr(){
+    return request({
+        url: '/index/hl/curr',
+        method: 'get'
+    })
+}
+
+export function bizAccess(){
+    return request({
+        url: '/index/biz/access',
+        method: 'get'
+    })
+}
+
+export function objAlarm(){
+    return request({
+        url: '/index/obj/alarm',
+        method: 'get'
+    })
+}
+export function msTrend(){
+    return request({
+        url: '/index/ms/trend',
+        method: 'get'
+    })
+}
+
+export function msTrendDetail(objMetricsId){
+    return request({
+        url: '/index/ms/trend/'+objMetricsId,
+        method: 'get'
+    })
+}
+
+export function hlMonthDay(type,date){
+    return request({
+        url: '/index/hl/'+type+'/'+date,
+        method: 'get'
+    })
+}
+
+export function msConfigList(){
+    return request({
+        url: '/index/ms/config/list',
+        method: 'get'
+    })
+}
+
+export function msConfigDel(ids){
+    return request({
+        url: '/index/ms/config/del/'+ids,
+        method: 'delete'
+    })
+}
+
+export function msConfigSelect(query){
+    return request({
+        url: '/index/ms/config/select',
+        method: 'get',
+        params: query
+    })
+}
+
+export function msConfigAdd(query){
+    return request({
+        url: '/index/ms/config/add',
+        method: 'get',
+        params: query
+    })
+}

BIN
src/assets/font/BebasNeue-1.otf


BIN
src/assets/font/SourceHanSansSC-Medium-2.woff


BIN
src/assets/font/YouSheBiaoTiHei-2.woff


BIN
src/assets/images/background.png


BIN
src/assets/images/health-mini.png


BIN
src/assets/images/health-small.png


BIN
src/assets/images/icon-green.png


BIN
src/assets/images/icon-red.png


BIN
src/assets/images/icon-yellow.png


BIN
src/assets/images/scroll-title.png


BIN
src/assets/images/title-icon.png


+ 13 - 0
src/assets/styles/index.scss

@@ -15,6 +15,19 @@ body {
   font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
 }
 
+@font-face {
+  font-family: "YouSheBiaoTiHei-2";
+  src: url("../font/YouSheBiaoTiHei-2.woff") format('woff');
+  font-weight: normal;
+  font-style: normal;
+}
+@font-face {
+  font-family: "BebasNeue-1";
+  src: url("../font/BebasNeue-1.otf") format('opentype');
+  font-weight: normal;
+  font-style: normal;
+}
+
 label {
   font-weight: 700;
 }

+ 26 - 3
src/views/index.vue

@@ -1,6 +1,29 @@
 <template>
-  <div class="app-container home">
-    <img src="@/assets/images/page.png" style="width: 100%;height: 900px;"/>
+  <div class="home-page">
+    <top-scroll-bar />
+    <div class="content-top">
+        <run-result />
+        <app-health />
+        <div class="column-row">
+          <access-error />
+          <alarm-ranking />
+        </div>
+    </div>
+    <div class="content-chart">
+      <health-tendency />
+      <run-tendency />
+    </div>
   </div>
 </template>
-
+<script setup>
+import topScrollBar from "./index/widget/topScrollBar.vue"
+import runResult from "./index/widget/runResult.vue"
+import appHealth from "./index/widget/appHealth.vue"
+import accessError from "./index/widget/accessError.vue"
+import alarmRanking from "./index/widget/alarmRanking.vue"
+import runTendency from "./index/widget/runTendency.vue"
+import healthTendency from "./index/widget/healthTendency.vue"
+</script>
+<style lang="scss">
+@import "./index/css/style.scss";
+</style>

+ 445 - 0
src/views/index/css/style.scss

@@ -0,0 +1,445 @@
+@import "../../../assets/styles/common.scss";
+.home-page{
+  background: url("@/assets/images/background.png") top center no-repeat;
+  height:calc(100vh - 84px);
+  background-size: cover;
+  overflow: auto;
+  padding:20px;
+  color:white;
+  @include borderBox;
+  .top-scroll-bar{
+    background-image: linear-gradient(to right top,#0E4690,#11386B);
+    box-shadow: inset 0 0 19px 3px #72D5FF66;
+    border:1px solid #72D5FF;
+    padding:10px;
+    @include borderBox;
+    @include flexBetween;
+    //border-radius: 16px 0 16px 0;
+    &-left{
+      @include flexStart;
+      .left-title{
+        background-image: linear-gradient(to bottom,#078BCB,#0B4389);
+        border:1px solid #6FCFFB;
+        border-radius: 25px 0 25px 0;
+        font-weight: bold;
+        @include flexCenter;
+      }
+      .left-content{
+        @include flexStart;
+        padding-left:20px;
+        font-size: 14px;
+        .s-time{
+          font-family: "BebasNeue-1", serif;
+          font-size: 18px;
+        }
+        .status-item{
+          background-image: linear-gradient(to bottom,#10427e,#002576);
+          border:1px solid #6FCFFB;
+          color:#72D5FF;
+          margin:0 10px;
+          font-size: 14px;
+          padding:2px 6px;
+        }
+        .result-text{
+          border-left:1px solid #3c6d88;
+          padding-left:10px;
+          @include flexStart;
+          .text-a{
+            @include flexStart;
+            color:#3AC4FF;
+            text-decoration: underline;
+            margin-left:10px;
+          }
+        }
+      }
+    }
+    .right-btn{
+      background-image: linear-gradient(to bottom,#25B1FF,#1E4683);
+      padding:4px 10px;
+      font-weight: bold;
+      font-size: 14px;
+      border:1px solid #95DFFF;
+      cursor: pointer;
+    }
+  }
+  .column-row-title{
+    background-image: linear-gradient(to right top,#21589e,#11386B);
+    border:1px solid #72D5FF;
+    border-bottom-width: 0;
+    padding:0 8px 0 0;
+    line-height:40px;
+    @include flexBetween;
+    .title-text{
+      font-family: "YouSheBiaoTiHei-2", Microsoft YaHei, serif;
+      @include flexStart;
+      font-size: 16px;
+      .s-span{
+        margin-left:5px;
+        font-size: 14px;
+        font-style: italic;
+        color: #A8CAF9;
+      }
+      &>img{
+        margin-right:10px;
+      }
+    }
+    .yellow-txt{
+      color: #FFB342;
+      font-size: 20px;
+      font-weight: 400;
+      margin-left:4px;
+    }
+    .el-checkbox{
+      color:white;
+      font-size: 16px;
+    }
+    .el-checkbox__inner{
+      background: transparent;
+      border:1px solid #72D5FF80;
+    }
+    .el-checkbox__label{
+      padding-left: 4px;
+    }
+    .el-checkbox__input.is-checked+.el-checkbox__label{
+      color: #72D5FF;
+    }
+    .el-checkbox__input.is-checked .el-checkbox__inner::after{
+      border-color: #72D5FF;
+    }
+  }
+
+  .content-top{
+    @include flexBetween;
+    align-items: flex-start;
+    margin-top:16px;
+    .column-row{
+      width: 375px;
+    }
+  }
+
+  .app-health{
+    height:calc(70vh - 138px);
+    position: relative;
+    width: calc(100% - 750px);
+    margin:0 10px;
+    .h-img{
+      width: 100px;
+    }
+    .a-h-item{
+      position: absolute;
+      top:10%;
+      left:10%;
+      text-align: center;
+    }
+    .item-cq{
+      top: 48%;
+      left: 15%;
+    }
+    .item-js{
+      top: 70%;
+      left: 50%;
+      margin-left:-60px;
+    }
+    .item-hg{
+      top: 48%;
+      left: 67%;
+    }
+    .item-fb{
+      left: calc(80% - 40px);
+    }
+    .item-jcd{
+      left:50%;
+      margin-left:-140px;
+      top:1%;
+    }
+    .i-label{
+      font-family: "YouSheBiaoTiHei-2", Microsoft YaHei, serif;
+      font-weight: 400;
+      font-size: 20px;
+      color: #CEE8FF;
+      text-shadow: 1px 1px 0 #138EFF;
+      font-style: normal;
+      text-transform: none;
+    }
+    .i-value{
+      font-family: "YouSheBiaoTiHei-2", Microsoft YaHei, serif;
+      font-weight: 400;
+      font-size: 34px;
+      letter-spacing: 2px;
+      font-style: normal;
+      text-transform: none;
+      text-align: center;
+      position: absolute;
+      top:-16px;
+      width: 100%;
+    }
+    .i-normal{
+      color:#6FFF7D;
+      text-shadow: 2px 0 0 #239E2F;
+      background: url("@/assets/images/icon-green.png") no-repeat center bottom;
+      background-size: 60%;
+    }
+    .i-waring{
+      color:#F0D90B;
+      text-shadow: 2px 0 0 #9E9223;
+      background: url("@/assets/images/icon-yellow.png") no-repeat center bottom;
+      background-size: 60%;
+    }
+    .i-risk{
+      color:#F00B0B;
+      text-shadow: 2px 0 0 #9E2323;
+      background: url("@/assets/images/icon-red.png") no-repeat center bottom;
+      background-size: 60%;
+    }
+  }
+
+  .column-content{
+    background-image: linear-gradient(to right top,#0E4690B0,#11386BB0);
+    box-shadow: inset 0 0 19px 3px #72D5FF33;
+    border:1px solid #72D5FF;
+    height:calc(70vh - 180px);
+    padding:10px;
+    @include borderBox;
+    overflow: auto;
+    .c-row{
+      font-size: 14px;
+      @include flexBetween;
+      border:1px solid #8DB2E4;
+      box-shadow: inset 0 0 19px 3px #72D5FF33;
+      padding:8px 12px;
+      margin-top:4px;
+      &:first-child{
+        margin-top:0;
+      }
+      .c-item{
+        width: 40%;
+        text-align: left;
+      }
+      .statue-risk{
+        width: 60px;
+        text-align: center;
+        padding:1px 0;
+        border:1px solid #FFB342;
+        background-image: linear-gradient(to bottom,#754E13,#CA8A29);
+        color:#FFF;
+        font-size: 14px;
+      }
+      .statue-normal{
+        @extend .statue-risk;
+        background-image: linear-gradient(to bottom,#203C6C,#435D8A);
+        border-color: #669FFF;
+        color: #7DADFF;
+      }
+    }
+  }
+  .column-content-half{
+    height:calc(((70vh - 180px)/2) - 26px);
+    font-size: 12px;
+  }
+
+  .tab-block{
+    @include flexCenter;
+    color:#9FB9E7;
+    margin-bottom:10px;
+    .tab-blue{
+      background: #3D74FF;
+      width: 20px;
+      height: 10px;
+      display: inline-block;
+      border-right: 1px;
+      margin:0 5px 0 10px;
+
+    }
+    .tab-yellow{
+      @extend .tab-blue;
+      background: #FFB342;
+    }
+  }
+
+  .access-row{
+    @include flexStart;
+    .a-r-label{
+      width: 120px;
+      text-align: right;
+    }
+    .a-r-progress{
+      width: calc(100% - 110px);
+      margin-left:10px;
+      .p-total{
+        @include flexStart;
+        width: 100%;
+        color:#9FB9E7;
+        .progress{
+          transition: all .3s;
+          background-image: linear-gradient(to right,#1D47B0,#3D74FF);
+          height: 10px;
+          margin-right:5px;
+          border-radius: 0 5px 5px 0;
+        }
+      }
+      .p-error{
+        .progress{
+          background-image: linear-gradient(to right,#A36F22,#FFB342);
+        }
+      }
+    }
+  }
+
+  .alarm-ranking{
+    margin-top:11px;
+  }
+  .alarm-row{
+    .ranking-title{
+      margin-bottom:5px;
+      @include flexBetween;
+      .no1{
+        color: #FFB342;
+        font-size: 16px;
+        font-family: "YouSheBiaoTiHei-2", Microsoft YaHei, serif;
+      }
+      .no2{
+        @extend .no1;
+        color: #72D5FF;
+      }
+      .no3{
+        @extend .no1;
+        color: #56D4B3;
+      }
+      .no-other{
+        @extend .no1;
+        color: #A1B3D2;
+      }
+      .alarm-num{
+        color:#9FB9E7;
+      }
+    }
+  }
+  .alarm-progress{
+    margin-bottom: 5px;
+    width: 100%;
+    height:10px;
+    background: #205194;
+    border-radius: 0 8px 8px 0;
+    .progress{
+      transition: all .3s;
+      background-image: linear-gradient(to right,#1D47B0,#3D74FF);
+      height: 10px;
+      margin-right:5px;
+      border-radius: 0 8px 8px 0;
+    }
+  }
+
+  .content-chart{
+    @include flexBetween;
+    margin-top:16px;
+    &-row{
+      width: calc(50% - 8px);
+      margin-left:16px;
+      &:first-child{
+        margin-left:0;
+      }
+    }
+    .btn-span{
+      background-image: linear-gradient(to bottom,#25B1FF,#1E4683);
+      font-size: 14px;
+      border:1px solid #95DFFF;
+      cursor: pointer;
+      line-height: 30px;
+      padding:0 10px;
+      font-weight: bold;
+    }
+    .chart-content{
+      height:260px;
+    }
+    .run-tendency{
+      @include flexBetween;
+      align-items: flex-start;
+      .run-title{
+        width: 200px;
+        height:238px;
+        overflow: auto;
+        .r-t-row{
+          @include flexStart;
+          padding:6px 8px;
+          cursor: pointer;
+          .r-value{
+            width: 66px;
+            line-height: 44px;
+            background: #16418B;
+            box-shadow: inset 0 0 5px 0 #3885DB;
+            border-radius: 2px 2px 2px 2px;
+            border: 1px solid #72D5FF;
+            text-align: center;
+            font-size: 14px;
+            color:#C9EFFF;
+            margin-right:5px;
+          }
+          .r-obj-name{
+            font-size: 14px;
+          }
+          .r-obj-metrics-name{
+            font-size: 12px;
+            color:#9FB9E7;
+            margin-top:5px;
+          }
+          &:hover{
+            background: #17468D;
+          }
+        }
+        .active{
+          background: #17468D;
+        }
+      }
+      .chart-sort{
+        width: calc(100% - 210px);
+        font-size: 14px;
+      }
+    }
+    .health-t{
+      .el-input__wrapper{
+        background-image: linear-gradient(to bottom,#2F5BA7,#123269);
+        box-shadow:0 0 0 1px #7EC1FF;
+      }
+      .el-select .el-input .el-select__caret{
+        color:white;
+      }
+      .el-input__inner{
+        color:white;
+      }
+      .el-input__prefix{
+        color:white;
+      }
+    }
+
+    .food-list-footer{
+      @include flexCenter;
+      padding-top: 20px;
+    }
+  }
+
+  // 滚动条
+  ::-webkit-scrollbar {
+    width: 4px;
+  }
+
+  ::-webkit-scrollbar-track {
+    // width: 6px;
+    background: rgba(#0E4690, .2);
+    // -webkit-border-radius: 2em;
+    // -moz-border-radius: 2em;
+    // border-radius: 2em;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    background-color: rgba(#72D5FF, .7);
+    background-clip: padding-box;
+    min-height: 28px;
+    // -webkit-border-radius: 2em;
+    // -moz-border-radius: 2em;
+    // border-radius: 2em;
+  }
+
+  ::-webkit-scrollbar-thumb:hover {
+    background: #72D5FF;
+  }
+}

+ 39 - 0
src/views/index/widget/accessError.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="column-row-title">
+    <span class="title-text"><img src="@/assets/images/title-icon.png" alt=""/>业务访问报错排名
+      <span class="s-span">(当日)</span>
+    </span>
+  </div>
+  <div class="column-content column-content-half">
+    <div class="tab-block">
+      <span><span class="tab-blue"/>总次数</span>
+      <span><span class="tab-yellow"/>访问错误次数</span>
+    </div>
+    <div class="access-row" v-for="item in accessData">
+      <div class="a-r-label">{{item.name}}</div>
+      <div class="a-r-progress">
+        <div class="p-total">
+          <span class="progress" :style="`width: calc(${(item.total/maxValue)*100}% - 40px)`"/>
+          {{item.total}}
+        </div>
+        <div class="p-total p-error">
+          <span class="progress" :style="`width: ${(item.error/item.total)*100}%`"/>
+          {{item.error}}
+        </div>
+<!--        <el-progress :percentage="item.total" :format="(percentage) => (percentage)"/>-->
+<!--        <el-progress :percentage="item.error" :format="(percentage) => (percentage)"/>-->
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import {bizAccess} from "@/api/index/hl"
+import {onMounted} from "vue";
+const accessData = ref([])
+const maxValue = ref(0)
+onMounted(async ()=>{
+  const res = await bizAccess()
+  accessData.value = res.data
+  maxValue.value = Math.max(...accessData.value.map(item=>item.total))+20
+})
+</script>

+ 39 - 0
src/views/index/widget/alarmRanking.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="column-row-title alarm-ranking">
+    <span class="title-text"><img src="@/assets/images/title-icon.png" alt=""/>组件告警数排名
+      <span class="s-span">(周)</span>
+    </span>
+<!--    <div><el-checkbox v-model="alarm" label="当前告警数" size="large" @change="getAlarm"/><span class="yellow-txt">8</span></div>-->
+  </div>
+  <div class="column-content column-content-half">
+    <div v-for="(item,index) in alarmData" class="alarm-row">
+      <div class="ranking-title">
+        <div>
+          <span :class="index===0 ? 'no1' : index ===1 ? 'no2':index ===2 ? 'no3':'no-other'">
+            NO.{{index+1}}
+          </span>
+          {{item.name}}
+        </div>
+        <span class="alarm-num">{{item.alarm}}</span>
+      </div>
+      <div class="alarm-progress">
+        <div class="progress" :style="`width:${(item.alarm/maxValue)*100}%`" />
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import {objAlarm} from "@/api/index/hl"
+import {onMounted} from "vue";
+const alarm = ref(false)
+const alarmData = ref([])
+const maxValue = ref(0)
+onMounted(()=>{
+  getAlarm()
+})
+async function getAlarm(){
+  const res = await objAlarm()
+  alarmData.value = res.data.data.sort((a,b)=>b.alarm - a.alarm)
+  maxValue.value = Math.max(...alarmData.value.map(item=>item.alarm))+20
+}
+</script>

+ 29 - 0
src/views/index/widget/appHealth.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="app-health">
+    <div :class="`a-h-item ${itemClass[index]}`" v-for="(item,index) in currData">
+        <div :class="`i-value ${ item.score > 90 ? 'i-normal' : item.score > 80 && item.score < 90 ? 'i-waring' : 'i-risk' } `">
+          {{item.score}}
+        </div>
+        <img :src="itemImg" alt="" class="h-img"/>
+        <div class="i-label">{{item.name}}</div>
+    </div>
+    <div class="a-h-item item-jcd">
+      <img src="@/assets/images/health-small.png" alt="" />
+      <div class="i-label">应用健康度</div>
+    </div>
+
+  </div>
+</template>
+<script setup lang="ts">
+import itemImg from "@/assets/images/health-mini.png"
+import {hlCurr} from "@/api/index/hl"
+import {onMounted} from "vue";
+const currData= ref([])
+const itemClass = ["","item-cq","item-js","item-hg","item-fb"]
+onMounted(async ()=>{
+  const res = await hlCurr()
+  currData.value = res.data
+})
+</script>
+<style scoped lang="scss">
+</style>

+ 106 - 0
src/views/index/widget/healthTendency.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="content-chart-row health-t">
+    <div class="column-row-title">
+      <span class="title-text">
+        <img src="@/assets/images/title-icon.png" alt=""/>应用健康趋势
+      </span>
+      <div>
+        <el-select v-model="dateType" size="small" style="width: 60px" @change="changeDate">
+          <el-option v-for="item in monthDay"  :label="item" :value="item" />
+        </el-select>
+        <el-date-picker v-model="dateValue"  size="small" style="width: 120px;margin-left:10px;" @change="changeDate"/>
+      </div>
+    </div>
+    <div class="column-content chart-content">
+      <div ref="chartSort" style="height: 238px" />
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import * as echarts from "echarts";
+import {parseTime} from "@/utils/ruoyi"
+import {hlMonthDay} from "@/api/index/hl"
+import {onMounted} from "vue";
+const monthDay = ["月度","每日"]
+const dateType = ref("月度")
+const dateValue = ref(new Date())
+const chartSort = ref(null)
+onMounted(()=>{
+  changeDate()
+})
+async function changeDate(){
+  if(dateType.value=="月度"){
+    const d=parseTime(dateValue.value,"{y}-{m}")
+    const res = await hlMonthDay("month",d)
+    initChart(res.data)
+  }else{
+    const d=parseTime(dateValue.value,"{y}-{m}-{d}")
+    const res = await hlMonthDay("day",d)
+    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: "#9FB9E7"
+      }
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: res[0].time,
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#9FB9E7",
+          width: 0,
+        }
+      },
+      axisTick: {
+        show: false,
+      },
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#9FB9E7",
+          width: 0,
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: "#6B8AC080",
+          type: 'dashed',
+        }
+      },
+    },
+    series:res.map(p=>{
+      return {
+        name: p.name,
+        type: 'line',
+        stack: 'Total',
+        smooth: true,
+        symbolSize: 0,
+        data: p.score
+      }
+    })
+  };
+  myChart.setOption(option)
+}
+</script>

+ 85 - 0
src/views/index/widget/indexConfig.vue

@@ -0,0 +1,85 @@
+<template>
+  <el-button type="primary" icon="plus" @click="addIndex">新增指标</el-button>
+  <el-table :data="listData" border style="width: 100%;margin-top:10px">
+    <el-table-column prop="objName" label="对象名称" width="180" />
+    <el-table-column prop="metricsName" label="指标名称" />
+    <el-table-column label="操作" width="80">
+      <template #default="scope">
+        <el-button type="primary" link icon="delete" @click="handleDelete(scope.row)">删除</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+  <el-dialog title="新增指标" v-model="visible" width="800">
+    <div style="margin-top:-10px;">
+      <el-input v-model="query.objName" placeholder="请输入对象名称" style="width: 150px;margin-right:10px;"/>
+      <el-input v-model="query.metricsName" placeholder="请输入指标名称" style="width: 150px;margin-right:10px;"/>
+      <el-button type="primary" @click="addIndex">查询</el-button>
+    </div>
+    <el-table :data="indexData" border style="width: 100%;margin-top:10px" size="small" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column prop="objName" label="对象名称" width="180" />
+      <el-table-column prop="metricsName" label="指标名称" />
+    </el-table>
+    <pagination :background="false" v-show="total>0" :total="total" v-model:page="query.pageNum" layout="prev, pager, next" v-model:limit="query.pageSize" @pagination="addIndex" />
+    <div class="food-list-footer">
+      <el-button type="primary" @click="handleAppend">添加</el-button>
+      <el-button @click="visible=false">取消</el-button>
+    </div>
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import {msConfigList,msConfigDel,msConfigSelect,msConfigAdd} from "@/api/index/hl"
+import {onMounted, reactive} from "vue";
+const {proxy} = getCurrentInstance()
+const listData=ref([])
+const visible = ref(false)
+const indexData = ref([])
+const ids = ref([])
+const query = reactive({
+  objName:"",
+  metricsName:"",
+  pageNum:1,
+  pageSize:10
+})
+const total = ref(0)
+const emit = defineEmits(["update"])
+onMounted(()=>{
+  getConfigList()
+})
+
+async function getConfigList(){
+  const res = await msConfigList()
+  listData.value = res.data
+}
+
+function handleSelectionChange(selection){
+  ids.value = selection.map(item=>item.objMetricsId)
+}
+
+async function handleDelete(row){
+  await msConfigDel(row.imId)
+  proxy.$message.success("删除成功")
+  await getConfigList()
+  emit("update")
+}
+
+async function addIndex(){
+  visible.value = true
+  const res = await msConfigSelect(query)
+  ids.value = []
+  indexData.value = res.rows
+  total.value = res.total
+}
+
+async function handleAppend(){
+  if(ids.value.length==0){
+    return proxy.$message.error("请选择要添加的指标")
+  }
+  await msConfigAdd({ids:ids.value.join(",")})
+  proxy.$message.success("添加成功")
+  await addIndex()
+  await getConfigList()
+  emit("update")
+}
+
+</script>

+ 37 - 0
src/views/index/widget/runResult.vue

@@ -0,0 +1,37 @@
+<template>
+    <div class="column-row">
+      <div class="column-row-title">
+        <span class="title-text"><img src="@/assets/images/title-icon.png" alt=""/>运行风险预测结果
+          <span class="s-span">(截止前一天)</span>
+        </span>
+        <div><el-checkbox v-model="risk" label="只看风险" size="large" @change="getRisk"/><span class="yellow-txt">{{riskNum}}</span></div>
+      </div>
+      <div class="column-content">
+        <div class="c-row" v-for="item in riskData">
+          <span class="c-item">{{item.objName}}</span>
+          <span class="c-item">{{item.metricsName}}</span>
+          <span :class=" item.status==2 ? 'statue-risk' : 'statue-normal' ">{{item.status==1 ? '正常' : '存在风险'}}</span>
+        </div>
+      </div>
+    </div>
+</template>
+<script setup lang="ts">
+import {riskList} from "@/api/index/hl"
+import {onMounted} from "vue";
+const risk = ref(false)
+const riskData = ref([])
+const riskNum = ref(0)
+
+onMounted(()=>{
+  getRisk()
+})
+
+async function getRisk(){
+  const {data} = await riskList(risk.value?2:1)
+  riskData.value = data.data
+  riskNum.value = data.num;
+}
+
+</script>
+<style scoped lang="scss">
+</style>

+ 114 - 0
src/views/index/widget/runTendency.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="content-chart-row">
+    <div class="column-row-title">
+      <span class="title-text"><img src="@/assets/images/title-icon.png" alt=""/>重点指标运行趋势
+        <span class="s-span">(周)</span>
+      </span>
+      <span class="btn-span" @click="visible=true">指标配置</span>
+    </div>
+    <div class="column-content chart-content run-tendency">
+      <div class="run-title">
+        <div v-for="(item,index) in trendData" :class="[`r-t-row`,{'active':index===active}]" @click="clickTrend(index)">
+          <div class="r-value">{{item.value.toFixed(1)}}%</div>
+          <div>
+            <div class="r-obj-name">{{item.objName}}</div>
+            <div class="r-obj-metrics-name">{{item.metricsName}}</div>
+          </div>
+        </div>
+      </div>
+      <div class="chart-sort">
+        {{trendData[active]?.objName || "-"}}
+        <div ref="chartSort" style="height: 218px;" />
+      </div>
+    </div>
+  </div>
+
+  <el-dialog title="指标配置" v-model="visible" width="600">
+    <index-config @update="getMsTrend"/>
+  </el-dialog>
+
+</template>
+<script setup lang="ts">
+import {msTrend,msTrendDetail} from "@/api/index/hl"
+import {formatDate} from "@/utils/index.js"
+import indexConfig from "./indexConfig.vue"
+import * as echarts from "echarts";
+const trendData = ref([])
+const active = ref(0)
+const chartSort = ref(null)
+const visible = ref(false)
+onMounted(() => {
+  getMsTrend()
+})
+
+async function getMsTrend(){
+  const res = await msTrend()
+  trendData.value = res.data
+  await clickTrend(0)
+}
+
+async function clickTrend(index){
+  active.value = index
+  const res = await msTrendDetail(trendData.value[index].objMetricsId)
+  initChart(res.data)
+}
+
+function initChart(res) {
+  const myChart = echarts.init(chartSort.value);
+  const option = {
+    grid: {
+      left: '0%',
+      right: '0%',
+      bottom: '0%',
+      top: '5%',
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: res.map(p=>formatDate(p.time)),
+
+    },
+    yAxis: {
+      type: 'value',
+      splitLine: {
+        lineStyle: {
+          color: "#6B8AC080",
+          type: 'dashed',
+        }
+      }
+    },
+    series: [
+      {
+        data: res.map(p=>p.value),
+        type: 'line',
+        symbolSize: 0,
+        lineStyle:{
+          color:'#61d1fc'
+        },
+        areaStyle: {
+          normal: {
+            color: {
+              type: "linear", //设置线性渐变
+              x: 0,
+              y: 1,
+              colorStops: [
+                {
+                  offset: 0,
+                  color: "#225c8e", // 100% 处的颜色
+                },
+                {
+                  offset: 1,
+                  color: "#014082", // 0% 处的颜色
+                },
+              ],
+              globalCoord: false, // 缺省为 false
+            },
+          },
+        },
+      }
+    ]
+  }
+  myChart.setOption(option)
+}
+
+</script>

+ 29 - 0
src/views/index/widget/topScrollBar.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="top-scroll-bar">
+    <div class="top-scroll-bar-left">
+      <span class="left-title">
+       <img src="@/assets/images/scroll-title.png"  alt=""/>
+      </span>
+      <div class="left-content">
+        <span class="s-time">
+          {{result.time}}
+        </span>
+        <span class="status-item">已完成</span>
+        <span class="result-text">巡检结果:本次自动巡检{{result.metrics || 0}}个指标,共发现{{result.problem || 0}}处问题
+          <a href="javascript:;" class="text-a">查看详情 <el-icon><Right /></el-icon></a>
+        </span>
+      </div>
+    </div>
+    <span class="right-btn">立即巡检</span>
+  </div>
+</template>
+<script setup lang="ts">
+import {hlCheck} from "@/api/index/hl"
+import {Right} from "@element-plus/icons-vue"
+import {onMounted,ref} from "vue";
+const result = ref({})
+onMounted(async ()=>{
+  const res = await hlCheck()
+  result.value = res.data
+})
+</script>

+ 1 - 1
vite.config.js

@@ -31,7 +31,7 @@ export default defineConfig(({ mode, command }) => {
       proxy: {
         // https://cn.vitejs.dev/config/#server-proxy
         '/dev-api': {
-          target: 'http://localhost:9527',
+          target: 'http://localhost:8080',
           changeOrigin: true,
           rewrite: (p) => p.replace(/^\/dev-api/, '')
         }