Forráskód Böngészése

feature(bug 修复+ 接口解析 errrate +接口解析甜甜圈)

liujing 8 hónapja
szülő
commit
bc55cb3225

+ 1 - 0
package.json

@@ -57,6 +57,7 @@
     "@vue/composition-api": "^1.7.2",
     "awe-dnd": "^0.3.4",
     "axios": "0.21.1",
+    "bignumber.js": "^9.1.2",
     "clipboard": "2.0.6",
     "codemirror": "5.62.0",
     "core-js": "^3.33.0",

+ 49 - 2
src/views/business-analysis/analysis/components/AnalysisMap.vue

@@ -68,7 +68,8 @@
         <el-table-column v-if="colData[5].istrue" header-align="center" label="errRate" sortable prop="error_rate" align="right" :show-overflow-tooltip="true">
           <template slot-scope="scope">
             <div :class="scope.row.error_rate==0?'table_bg':'table_bg_red'">
-              {{ scope.row.error_rate==0?scope.row.error_rate:Number(scope.row.error_rate).toFixed(2) }}
+              {{getErrRate(scope.row.error_rate)}}
+              <!-- {{ scope.row.error_rate==0?scope.row.error_rate:Number(scope.row.error_rate).toFixed(2) }} -->
             </div>
           </template>
         </el-table-column>
@@ -302,6 +303,7 @@ import resize from '../../mixins/resize'
 import moment from 'moment'
 // import elTableInfiniteScroll from 'el-table-infinite-scroll';
 import Topo from './Topo'
+import BigNumber from 'bignumber.js'
 export default {
   name: 'ServiceMap',
   components: {
@@ -488,6 +490,7 @@ export default {
       this.$forceUpdate()
     }
     this.columnChange()
+
   },
   mounted() {
     this.$forceUpdate()
@@ -499,6 +502,49 @@ export default {
     this.disposeEcharts(this.myChartBox2List)
   },
   methods: {
+    getErrRate(error_rate){
+      let strErrorRate = numToString(error_rate)
+      let length = null
+      if (strErrorRate !== '0'){
+        length = strErrorRate.split('.')[1].length
+      }
+      if(error_rate == 0) {
+        return error_rate
+      } else if (length<=2 ) {
+        return numToString(error_rate) + '%'
+      } else {
+        return convertToPercentage(error_rate)
+      }
+      function numToString(num) {
+        let str = num.toString();
+        if (str.includes('e')) {
+          let parts = str.split('e');
+          let base = parts[0];
+          let exp = parseInt(parts[1]);
+          let decimalPlaces = base.split('.')[1]?.length || 0;
+          let zerosNeeded = Math.abs(exp) - decimalPlaces;
+          if (exp < 0) {
+            str = '0.' + '0'.repeat(zerosNeeded) + base.replace('.', '');
+          } else {
+            str = base + '0'.repeat(zerosNeeded);
+          }
+        }
+        return str;
+      }
+      function convertToPercentage(value) {
+        let bnValue = new BigNumber(value);
+        let percentage = bnValue.multipliedBy(100);// 乘法运算
+        let roundedPercentage = percentage.decimalPlaces(2, BigNumber.ROUND_FLOOR);// decimalPlaces保留小数位
+
+        // 如果四舍五入后的值小于原始值,手动增加 0.01
+        if (roundedPercentage.isLessThan(percentage)) {// 判断是否小于
+          roundedPercentage = roundedPercentage.plus(0.01);
+        }
+
+        return `${roundedPercentage.toFixed(2)}%`;
+      }
+
+    },
     handleUpdateName() {
       this.open = true
       this.form = this.detailObj
@@ -542,6 +588,7 @@ export default {
       this.handelgetBizDetail()
     },
     handleRowClick(row, column, event) {
+      console.log('row------', row)
       storage.set('detailObj', row)
       this.$router.push({
         path: '/business-analysis/analysisDetail/index',
@@ -642,7 +689,7 @@ export default {
         }
       }
     },
-    getList() {
+    getList() {// 解析列表的table 数据
       this.loading = true
       const _this = this
       this.myChartList = []

+ 0 - 1
src/views/business-analysis/analysisDetail/components/Topo.vue

@@ -505,7 +505,6 @@ export default {
       if (typeof onInit === 'function') {
         onInit(graph)
       }
-      console.log('graph.node------99999-------', data)
       graph.data(data)
       graph.render()
       // 让画布内容适应视口

+ 121 - 27
src/views/business-analysis/analysisDetail/index.vue

@@ -17,11 +17,27 @@
         <div style="margin-bottom:16px;">
           <div class="flexWrap">
             <div class="flex-left">
-              <div v-if="JSON.stringify(graphData)!=&quot;{}&quot;" v-loading="loading">
-                <div style="font-size:14px;font-weight:bold;text-align:right">
-                  <svg-icon :icon-class="isFull?'exit-fullscreen':'fullscreen'" @click.native.prevent="clickFull" />
+              <div class="ob-switch-box">
+                <el-switch
+                  v-model="switchValue"
+                  active-color="#165DFF"
+                  inactive-color="#C9CDD4"
+                  @change="handelSwitch"
+                />
+              </div>
+              <div v-if="switchValue">
+                <div v-if="JSON.stringify(graphData)!=&quot;{}&quot;" v-loading="loading">
+                  <div style="font-size:14px;font-weight:bold;text-align:right">
+                    <svg-icon :icon-class="isFull?'exit-fullscreen':'fullscreen'" @click.native.prevent="clickFull" />
+                  </div>
+                  <Topo ref="topo" :graph-data="graphData" topo-id="container" @changeState="clickRowHandleNode" />
+                </div>
+              </div>
+              <div v-else style="position:relative;height: 100%;">
+                <div v-if="isLoading" v-loading="isLoading" class="loading-overlay"></div>
+                <div v-else>
+                  <NodeG6Charts v-if="sweetGraphData && Object.keys(sweetGraphData).length > 0" :chart-content-id="'serviceDrawerContainer'" :graph-data="sweetGraphData" />
                 </div>
-                <Topo ref="topo" :graph-data="graphData" topo-id="container" @changeState="clickRowHandleNode" />
               </div>
             </div>
             <div class="flex-right">
@@ -62,7 +78,7 @@
           <!-- 添加或修改应用配置对话框 -->
           <el-dialog v-if="open" :title="title" :visible.sync="open" width="700px" :center="true" :close-on-click-modal="false">
             <el-form ref="form" :model="form" label-width="95px">
-              <el-form-item label="中文别名" prop="name">
+              <el-form-item label="中文别名22" prop="name">
                 <el-input v-model="form.name" placeholder="请输入中文别名" />
               </el-form-item>
             </el-form>
@@ -206,13 +222,15 @@
 </template>
 <script>
 import storage from '@/utils/storage'
-import { getBizDetail, listBizGraph, updateBiz, listBiz, bizNodeStats, bizNodeName, bizNodeSpans } from '@/api/mapping'
+import { getBizDetail, listBizGraph, updateBiz, listBiz, bizNodeStats, bizNodeName, bizNodeSpans, getSweetList, addUrlMapping, listBizStats } from '@/api/mapping'
 import Topo from './components/Topo'
 import resize from '../mixins/resize'
 import moment from 'moment'
+import NodeG6Charts from '../../service/InterfaceDetail/components/NodeG6Charts.vue'
 export default {
   components: {
-    Topo
+    Topo,
+    NodeG6Charts
   },
   mixins: [resize],
   data() {
@@ -291,7 +309,21 @@ export default {
       nodeCount: 0,
       nodeloading: false,
       checked: false,
-      onlyException: false
+      onlyException: false,
+      switchValue: false,
+      isLoading: false,
+      sweetquanParams: {
+        biz_id: 0,
+        start_time: '',
+        end_time: ''
+      },
+      sweetGraphData:{},
+      appItem: {},
+      chartQuantiles: {
+        biz_id: null,
+        start_time: '',
+        end_time: ''
+      }
     }
   },
   watch: {
@@ -309,11 +341,21 @@ export default {
 
           this.queryStats.start_time = newValue.startTime
           this.queryStats.end_time = newValue.endTime
+          this.sweetquanParams.start_time = newValue.startTime
+          this.sweetquanParams.end_time = newValue.endTime
+
+          this.chartQuantiles.start_time = newValue.startTime
+          this.chartQuantiles.end_time = newValue.endTime
 
           this.querySpans.pageIndex = 1
-          this.getList()
-          this.handelgetBizDetail()
-          this.getListBizGraph(this.$route.query.id)
+          this.getList() // 获取前一个页面-解析列表的table 数据
+          this.handelgetBizDetail()// 获取table 表格
+          //
+          if (this.switchValue) {
+            this.getListBizGraph(this.$route.query.id)// 获取紧凑树接口
+          } else {
+            this.getSweetListFn() // 获取甜甜圈接口
+          }
           if (newValue.timeOut) {
             if (newValue.timeOut == 1) {
               clearInterval(this.timer)
@@ -342,28 +384,60 @@ export default {
 
     this.queryStats.start_time = start_time
     this.queryStats.end_time = end_time
+    this.sweetquanParams.start_time = start_time
+    this.sweetquanParams.end_time = end_time
 
+    this.chartQuantiles.start_time = start_time
+    this.chartQuantiles.end_time = end_time
+
+    this.appItem = storage.get('appsItem')
     this.detailObj = storage.get('detailObj')
     if (this.$route.query.id != undefined) {
       this.childQueryParams.biz_id = this.$route.query.id
+      this.chartQuantiles.biz_id = this.$route.query.id
       this.detailTitle = this.$route.query.name
-      this.queryParams.app_id = this.$route.query.id
+      this.queryParams.app_id = this.appItem.id
+      this.sweetquanParams.biz_id = this.$route.query.id
       this.handelgetBizDetail()
-      this.getListBizGraph(this.$route.query.id)
+      this.getList()
+      if (this.switchValue) {
+        this.getListBizGraph(this.$route.query.id)
+      } else {
+        this.getSweetListFn()
+      }
+
     }
   },
   mounted() {
-    this.$nextTick(() => {
-      if (document.getElementById('scaleMain')){
-        this.drawEchartsScale(this.detailObj)
-      }
-    })
+    // this.$nextTick(() => {
+    //   if (document.getElementById('scaleMain')){
+    //     this.drawEchartsScale(this.detailObj)
+    //   }
+    // })
   },
   beforeDestroy() {
     clearInterval(this.timer)
     this.graphData = {}
   },
   methods: {
+    handelSwitch() {
+      if (this.switchValue) {
+        this.getListBizGraph(this.sweetquanParams.biz_id)
+      } else {
+        this.getSweetListFn()
+      }
+    },
+    async getSweetListFn() {
+      this.isLoading = true
+      this.sweetGraphData = {}
+      const res = await getSweetList(this.sweetquanParams)
+      if (res && res.code === 200) {
+        this.sweetGraphData = res.data
+      } else {
+        this.sweetGraphData = {}
+      }
+      this.isLoading = false
+    },
     gotoPathService(path, service_name) {
       const href = this.$router.resolve({
         path: path,
@@ -414,7 +488,7 @@ export default {
     },
     getNodeStats() {
       bizNodeStats(this.queryStats).then(res => {
-        if (res.code == 200) {
+        if (res&& res.code == 200) {
           this.nodeStats = res.data
         }
       })
@@ -473,10 +547,6 @@ export default {
                 this.$router.push({
                   path: '/business-analysis/analysis/index'
                 })
-                // this.queryParams.pageIndex =1;
-                // this.serveceMapList=[];
-                // this.tempList=[];
-                // this.getList()
               } else {
                 this.msgError(response.msg)
               }
@@ -503,12 +573,28 @@ export default {
       this.open = false
       this.reset()
     },
-    getList() {
+    getList() { // 获取前一个页面-解析列表的table 数据
       listBiz(this.queryParams).then(res => {
         if (res.code == 200) {
           for (let i = 0; i < res.data.list.length; i++) {
-            if (res.data.list[i].id = this.this.$route.query.id) {
+            if (res.data.list[i].id == this.$route.query.id) {
               this.detailTitle = res.data.list[i].name
+              this.detailObj = res.data.list[i]
+
+              listBizStats(this.chartQuantiles).then(response => {
+                if (response.code == 200) {
+                  if (response.data.biz_id != undefined) {
+                    if (this.detailObj.id == response.data.biz_id) {
+                      this.detailObj = Object.assign({}, this.detailObj, response.data)
+                      storage.set('detailObj', this.detailObj)
+                      setTimeout(()=>{
+                        this.drawEchartsScale(this.detailObj)
+                      },100)
+                    }
+                  }
+                }
+              })
+
             }
           }
         }
@@ -538,7 +624,7 @@ export default {
         path: '/business-analysis/analysis/index'
       })
     },
-    handelgetBizDetail() {
+    handelgetBizDetail() {// 获取table 表格
       this.tableLoading = true
       getBizDetail(this.childQueryParams).then(res => {
         if (res.code == 200) {
@@ -583,7 +669,10 @@ export default {
     },
     drawEchartsScale(row) {
       const _this = this
-      const myChartScale = this.$echarts5.init(document.getElementById('scaleMain'))
+      let myChartScale = this.$echarts5.getInstanceByDom(document.getElementById('scaleMain'));
+      if (myChartScale == undefined) {
+        myChartScale = this.$echarts5.init(document.getElementById('scaleMain'));
+      }
       // 绘制趋势echarts
       const option = {
         tooltip: {
@@ -744,6 +833,7 @@ export default {
 </script>
 <style lang="scss" scoped>
 .node-details{
+
   .node-details-content{
     // width:420px;
     padding:0 36px;
@@ -794,6 +884,10 @@ export default {
     width:50%;
   }
 }
+.ob-switch-box{
+  text-align: right;
+  width: 100%;
+}
 ::v-deep .el-drawer__header {
   padding:16px;
   background:#1890ff;

+ 25 - 7
src/views/index/index.vue

@@ -65,10 +65,12 @@
                   placeholder="请输入url"
                   @select="handleSelect"
                 >
-                  <template slot-scope="{ item }">
-                    <div v-show="item.Route">
-                      <span>{{ item.Route }}</span>
-                      <div class="ob-select-right">
+                  <template slot-scope="{ item, $index }">
+                    <div v-show="item.Route"  class="ob-select-box">
+                      <el-tooltip placement="bottom" effect="dark"  :content="item.Route"  >
+                        <span class="ob-select-title"  ref="KnowledgeContent">{{ item.Route }}</span>
+                      </el-tooltip>
+                      <div >
                         <el-tag size="mini" type="success" class="ob-tag-right">{{ item.Method }}</el-tag>
                         <el-tag size="mini">{{ item.ServiceName }}</el-tag>
                       </div>
@@ -267,7 +269,8 @@ export default {
         pageSize: 100
       },
       pageTotal: 0,
-      openMonitor: false
+      openMonitor: false,
+      isShowTooltip: false
     }
   },
   computed: {
@@ -325,6 +328,11 @@ export default {
     clearInterval(this.appTimer)
   },
   methods: {
+    onMouseOver (i) {
+      const parentWidth = this.$refs.KnowledgeContent[i]?.parentNode?.offsetWidth // 获取当前元素父级可视宽度
+      const contentWidth = this.$refs.KnowledgeContent[i]?.offsetWidth // 获取当前元素可视宽度
+      this.isShowTooltip = contentWidth <= parentWidth
+    },
     async logout() {
       this.$confirm('确定注销并退出系统吗?', '提示', {
         confirmButtonText: '确定',
@@ -512,6 +520,7 @@ export default {
     handleSelect(item) {
       // 处理选中项
       const obj = this.appList.filter(m => m.alias === item.AppAlias)[0]
+      console.log('item..----', item.AppAlias,  item.$index)
       storage.set('appsItem', obj)
       const rowItem = {
         method: item.Method,
@@ -801,8 +810,17 @@ export default {
       font-weight: bold;
       font-size: 18px;
     }
-    .ob-select-right{
-      float:right;
+    .ob-select-title{
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      display: inline-block;
+      max-width: 70%;
+    }
+    .ob-select-box{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
     }
     .ob-tag-right{
       margin-right: 10px;

+ 51 - 3
src/views/service/Interface/components/InterfaceMap.vue

@@ -132,9 +132,8 @@
         <el-table-column v-if="colData[6].istrue" header-align="center" label="errRate" sortable prop="error_rate" align="right" :show-overflow-tooltip="true">
           <template slot-scope="scope">
             <div :class="scope.row.error_rate==0?'table_bg':'table_bg_red'">
-              <!-- <el-tag v-if="scope.row.error_rate==0">{{  scope.row.error_rate }}</el-tag>
-                  <el-tag v-else type="danger">{{  Number(scope.row.error_rate).toFixed(2)}}</el-tag> -->
-              {{ scope.row.error_rate==0?scope.row.error_rate:Number(scope.row.error_rate).toFixed(2) }}
+              {{getErrRate(scope.row.error_rate)}}
+              <!-- {{ scope.row.error_rate==0?scope.row.error_rate:Number(scope.row.error_rate).toFixed(2) }} -->
             </div>
           </template>
         </el-table-column>
@@ -360,6 +359,7 @@ import { getToken } from '@/utils/auth'
 // import echarts from 'echarts'
 import resize from './mixins/resize'
 import moment from 'moment'
+import BigNumber from 'bignumber.js'
 export default {
   name: 'ServiceMap',
   mixins: [resize],
@@ -555,6 +555,54 @@ export default {
     this.disposeEcharts(this.myChartBox2List)
   },
   methods: {
+    getErrRate(error_rate){
+      console.log('error_rate---', error_rate)
+      let strErrorRate = numToString(error_rate)
+      let length = null
+      if (strErrorRate !== '0'){
+        length = strErrorRate.split('.')[1].length
+      }
+      if(error_rate == 0) {
+        return error_rate
+      } else if(length<=2 ) {
+        return numToString(error_rate) + '%'
+      } else {
+        return convertToPercentage(error_rate)
+      }
+      //  0.00000000000000000003---->3e-20
+      function numToString(num) {
+        let str = num.toString();
+        if (str.includes('e')) {
+          let parts = str.split('e');
+          let base = parts[0];
+          let exp = parseInt(parts[1]);
+          let decimalPlaces = base.split('.')[1]?.length || 0;
+          let zerosNeeded = Math.abs(exp) - decimalPlaces;
+          if (exp < 0) {
+            str = '0.' + '0'.repeat(zerosNeeded) + base.replace('.', '');
+          } else {
+            str = base + '0'.repeat(zerosNeeded);
+          }
+        }
+        return str;
+      }
+      function convertToPercentage(value) {
+        let bnValue = new BigNumber(value);
+        let percentage = bnValue.multipliedBy(100);// 乘法运算
+        let roundedPercentage = percentage.decimalPlaces(2, BigNumber.ROUND_FLOOR);// decimalPlaces保留小数位
+
+        // 如果四舍五入后的值小于原始值,手动增加 0.01
+        if (roundedPercentage.isLessThan(percentage)) {// 判断是否小于
+          roundedPercentage = roundedPercentage.plus(0.01);
+        }
+
+        return `${roundedPercentage.toFixed(2)}%`;
+      }
+
+
+
+
+    },
     handChange(val) {
       this.queryParams.service_name = val
       this.handleQuery()

+ 1 - 1
src/views/service/InterfaceDetail/components/NodeDetailDrawer.vue

@@ -305,7 +305,7 @@ export default {
     },
     getNodeStats() {
       bizNodeStats(this.queryStats).then(res => {
-        if (res.code == 200) {
+        if (res && res.code == 200) {
           this.nodeStats = res.data
         }
       })

+ 202 - 197
src/views/service/InterfaceDetail/components/NodeG6Charts.vue

@@ -38,218 +38,223 @@ export default {
       /**
      * by Shiwu
      */
-      data.edges.forEach(edge => {
-        edge.label = `${edge.relation}`
-      })
+      if (data) {
 
-      const colors = {
-        'err': '#F44C4C',
-        'ok': '#53F44C'
-      }
-      const imgObj = {
-        cpp: require('../../../../assets/G6-icons/cpp.svg'),
-        dotnet: require('../../../../assets/G6-icons/dot-net.svg'),
-        erlang: require('../../../../assets/G6-icons/erlang.svg'),
-        go: require('../../../../assets/G6-icons/go.svg'),
-        java: require('../../../../assets/G6-icons/java.svg'),
-        nodejs: require('../../../../assets/G6-icons/nodejs.svg'),
-        php: require('../../../../assets/G6-icons/php.svg'),
-        python: require('../../../../assets/G6-icons/python.svg'),
-        ruby: require('../../../../assets/G6-icons/ruby.svg'),
-        rust: require('../../../../assets/G6-icons/rust.svg'),
-        swift: require('../../../../assets/G6-icons/swift.svg'),
-        webjs: require('../../../../assets/G6-icons/webjs.svg'),
-        mysql: require('../../../../assets/G6-icons/mysql.svg'),
-        oracle: require('../../../../assets/G6-icons/oracle.svg'),
-        postgresql: require('../../../../assets/G6-icons/postgresql.svg'),
-        hive: require('../../../../assets/G6-icons/hive.svg'),
-        progress: require('../../../../assets/G6-icons/progress.svg'),
-        hsqldb: require('../../../../assets/G6-icons/hsqldb.svg'),
-        kafka: require('../../../../assets/G6-icons/kafka.svg'),
-        rabbitmq: require('../../../../assets/G6-icons/rabbitmq.svg'),
-        rocketmq: require('../../../../assets/G6-icons/rocketmq.svg'),
-        activemq: require('../../../../assets/G6-icons/activemq.svg'),
-        servicebus: require('../../../../assets/G6-icons/serviceBus.svg'),
-        client: require('../../../../assets/G6-icons/client.svg'),
-        aws_sqs: require('../../../../assets/G6-icons/aws.svg'),
-        default: require(`../../../../assets/G6-icons/default.svg`)
-      } 
-      data.nodes.forEach(node => {
-        node.donutColorMap = colors
-        node.size = 0
-        node.label = node.name
-        node.iconType = node.type
-        node.donutAttrs = node.attrs// donutAttrs
-        Object.keys(node.donutAttrs).forEach(key => {
-          node.size += Number(node.donutAttrs[key])
+        data.edges && data.edges.length > 0 && data.edges.forEach(edge => {
+          edge.label = `${edge.relation}`
         })
-        node.size = Math.sqrt(node.size) * 60
-        delete node.type
-      })
 
-      const legendData = {
-        nodes: [{
-          id: 'err',
-          label: 'Err',
-          order: 0,
-          style: {
-            fill: '#F44C4C'
-          }
-        }, {
-          id: 'ok',
-          label: '0k',
-          order: 2,
-          style: {
-            fill: '#53F44C'
-          }
-        }]
-      }
-      const tooltip = new G6.Tooltip({
-        // offsetX and offsetY include the padding of the parent container
-        offsetX: 20,
-        offsetY: 30,
-        itemTypes: ['node'],
-        getContent: (e) => {
-          const outDiv = document.createElement('div')
-          // outDiv.style.padding = '0px 0px 20px 0px';
-          const nodeName = e.item.getModel().name
-          const item = e.item.getModel()
-          outDiv.innerHTML = `
-            <div style="margin-bottom:8px box-shadow: rgb(174, 174, 174) 0px 0px 10px;
-              width: fit-content;
-              color: #fff;
-              border-radius = 4px;">
-              <span style='display:inline-block;width:120px;text-align:right;'>Ok:</span>
-              <span style='display:inline-block;'>${item}</span>
-            </div>
-            <div style="margin-bottom:8px">
-              <span style='display:inline-block;width:120px;text-align:right;'>Err:</span>
-              <span style='display:inline-block;'>${item}</span>
-            </div>
-           `
-          return outDiv
-        },
-        shouldBegin: (e) => {
-          if (e.target.get('name') === 'name-shape' || e.target.get('name') === 'mask-label-shape') return true
-          return false
+        const colors = {
+          'err': '#F44C4C',
+          'ok': '#53F44C'
         }
-      })
-      const legend = new G6.Legend({
-        data: legendData,
-        align: 'center',
-        layout: 'horizontal', // vertical
-        position: 'top-left',
-        vertiSep: 12,
-        horiSep: 24,
-        offsetY: -24,
-        padding: [4, 8, 4, 8],
-        containerStyle: {
-          fill: '#ccc',
-          lineWidth: 1
-        },
-        title: ' ',
-        titleConfig: {
-          offsetY: -8
+        const imgObj = {
+          cpp: require('../../../../assets/G6-icons/cpp.svg'),
+          dotnet: require('../../../../assets/G6-icons/dot-net.svg'),
+          erlang: require('../../../../assets/G6-icons/erlang.svg'),
+          go: require('../../../../assets/G6-icons/go.svg'),
+          java: require('../../../../assets/G6-icons/java.svg'),
+          nodejs: require('../../../../assets/G6-icons/nodejs.svg'),
+          php: require('../../../../assets/G6-icons/php.svg'),
+          python: require('../../../../assets/G6-icons/python.svg'),
+          ruby: require('../../../../assets/G6-icons/ruby.svg'),
+          rust: require('../../../../assets/G6-icons/rust.svg'),
+          swift: require('../../../../assets/G6-icons/swift.svg'),
+          webjs: require('../../../../assets/G6-icons/webjs.svg'),
+          mysql: require('../../../../assets/G6-icons/mysql.svg'),
+          oracle: require('../../../../assets/G6-icons/oracle.svg'),
+          postgresql: require('../../../../assets/G6-icons/postgresql.svg'),
+          hive: require('../../../../assets/G6-icons/hive.svg'),
+          progress: require('../../../../assets/G6-icons/progress.svg'),
+          hsqldb: require('../../../../assets/G6-icons/hsqldb.svg'),
+          kafka: require('../../../../assets/G6-icons/kafka.svg'),
+          rabbitmq: require('../../../../assets/G6-icons/rabbitmq.svg'),
+          rocketmq: require('../../../../assets/G6-icons/rocketmq.svg'),
+          activemq: require('../../../../assets/G6-icons/activemq.svg'),
+          servicebus: require('../../../../assets/G6-icons/serviceBus.svg'),
+          client: require('../../../../assets/G6-icons/client.svg'),
+          aws_sqs: require('../../../../assets/G6-icons/aws.svg'),
+          default: require(`../../../../assets/G6-icons/default.svg`)
         }
 
-      })
-      this.$nextTick(() => {
-        const container = document.getElementById('serviceDrawerContainer')
-        const width = container.scrollWidth
-        const height = container.scrollHeight || 500
-        const graph = new G6.Graph({
-          container: 'serviceDrawerContainer',
-          width,
-          height,
-          fitView: data.nodes.length > 2 ? true:false,
-          maxZoom: 8, // 默认值是10
-          minZoom: 0.02, // 默认值是0.02
-          // translate the graph to align the canvas's center, support by v3.5.1
-          fitCenter: true,
-          plugins: [legend],
-          modes: {
-            default: ['drag-canvas', 'drag-node', 'zoom-canvas',
-              {
-                type: 'tooltip', // 提示框
-                // offset:0,
-                formatText(model) {
-                // 提示框文本内容
-                  return `<div  style="background-color: rgba(0,0,0, 0.65);  box-shadow: rgb(174, 174, 174) 0px 0px 10px;
-              width: fit-content;
-              color: #fff;
-              padding: 10px;
-              border-radius:4px;">
-                <div style="margin-bottom:8px">
-                  <span >Ok:</span>
-                  <span>${model.attrs.ok == '0.00000' ? 0 : model.attrs.ok == '1.00000' ? 1 : model.attrs.ok}</span>
-                </div>
-                <div>
-                  <span >Err:</span>
-                  <span>${model.attrs.err == '0.00000'? 0: (model.attrs.err == '1.00000' ? 1 : model.attrs.err)}</span>
-                </div>
-                </div>`
-                }
-              }
-            ] // 启用拖拽画布和缩放画布的交互模式
-          },
-          layout: {
-            type: 'radial',
-            focusNode: 'li',
-            linkDistance: 200,
-            unitRadius: 200,
-            preventOverlap: true, // 防止节点重叠
-            nodeSpacing: 500,
-            // 防碰撞必须设置nodeSize或size,否则不生效,由于节点的size设置了40,虽然节点不碰撞了,但是节点之间的距离很近,label几乎都挤在一起,所以又重新设置了大一点的nodeSize,这样效果会好很多
-            nodeSize: 60,
-            strictRadial:false,
-            linkDistance: data.nodes.length>6? 400 : 200 // 指定边距离为150
-          },
-          defaultEdge: {
+        data.nodes && data.nodes.length > 0 && data.nodes.forEach(node => {
+          node.donutColorMap = colors
+          node.size = 0
+          node.label = node.name
+          node.iconType = node.type
+          node.donutAttrs = node.attrs// donutAttrs
+          Object.keys(node.donutAttrs).forEach(key => {
+            node.size += Number(node.donutAttrs[key])
+          })
+          node.size = Math.sqrt(node.size) * 60
+          delete node.type
+        })
+
+        const legendData = {
+          nodes: [{
+            id: 'err',
+            label: 'Err',
+            order: 0,
             style: {
-              endArrow: true
-            },
-            labelCfg: { // 标签文本配置项
-              autoRotate: true,
-              style: {
-                stroke: '#fff',
-                lineWidth: 5
-              }
+              fill: '#F44C4C'
             }
-          },
-          defaultNode: {
-            type: 'donut',
+          }, {
+            id: 'ok',
+            label: '0k',
+            order: 2,
             style: {
-              lineWidth: 0,
-              cursor: 'pointer'
-            },
-            labelCfg: {
-              position: 'bottom',
-              autoRotate: true,
+              fill: '#53F44C'
             }
+          }]
+        }
+        const tooltip = new G6.Tooltip({
+          // offsetX and offsetY include the padding of the parent container
+          offsetX: 20,
+          offsetY: 30,
+          itemTypes: ['node'],
+          getContent: (e) => {
+            const outDiv = document.createElement('div')
+            // outDiv.style.padding = '0px 0px 20px 0px';
+            const nodeName = e.item.getModel().name
+            const item = e.item.getModel()
+            outDiv.innerHTML = `
+              <div style="margin-bottom:8px box-shadow: rgb(174, 174, 174) 0px 0px 10px;
+                width: fit-content;
+                color: #fff;
+                border-radius = 4px;">
+                <span style='display:inline-block;width:120px;text-align:right;'>Ok:</span>
+                <span style='display:inline-block;'>${item}</span>
+              </div>
+              <div style="margin-bottom:8px">
+                <span style='display:inline-block;width:120px;text-align:right;'>Err:</span>
+                <span style='display:inline-block;'>${item}</span>
+              </div>
+            `
+            return outDiv
+          },
+          shouldBegin: (e) => {
+            if (e.target.get('name') === 'name-shape' || e.target.get('name') === 'mask-label-shape') return true
+            return false
           }
         })
-        // 节点配置中使用
-        graph.node((node) => {
-          return {
-            icon: {
-              show: true,
-              // 使用函数动态指定图标路径 imgObj[node.iconType] || imgObj.default
-              img: imgObj[node.iconType] || imgObj.default
-            }
-          };
-        });
-        graph.data(data)
-        graph.render()
-        graph.on('node:mouseenter', (evt) => {
-          const { item } = evt
-          graph.setItemState(item, 'active', true)
+        const legend = new G6.Legend({
+          data: legendData,
+          align: 'center',
+          layout: 'horizontal', // vertical
+          position: 'bottom-left',
+          vertiSep: 12,
+          horiSep: 24,
+          offsetY: -24,
+          padding: [4, 8, 4, 8],
+          containerStyle: {
+            fill: '#ccc',
+            lineWidth: 1
+          },
+          title: ' ',
+          titleConfig: {
+            offsetY: -8
+          }
+
         })
+        this.$nextTick(() => {
+          const container = document.getElementById('serviceDrawerContainer')
+          const width = container.scrollWidth
+          const height = container.scrollHeight || 500
+          const graph = new G6.Graph({
+            container: 'serviceDrawerContainer',
+            width,
+            height,
+            fitView: data.nodes.length > 2 ? true:false,
+            maxZoom: 8, // 默认值是10
+            minZoom: 0.02, // 默认值是0.02
+            // translate the graph to align the canvas's center, support by v3.5.1
+            fitCenter: true,
+            plugins: [legend],
+            modes: {
+              default: ['drag-canvas', 'drag-node', 'zoom-canvas',
+                {
+                  type: 'tooltip', // 提示框
+                  // offset:0,
+                  formatText(model) {
+                  // 提示框文本内容
+                    return `<div  style="background-color: rgba(0,0,0, 0.65);  box-shadow: rgb(174, 174, 174) 0px 0px 10px;
+                width: fit-content;
+                color: #fff;
+                padding: 10px;
+                border-radius:4px;">
+                  <div style="margin-bottom:8px">
+                    <span >Ok:</span>
+                    <span>${model.attrs.ok == '0.00000' ? 0 : model.attrs.ok == '1.00000' ? 1 : model.attrs.ok}</span>
+                  </div>
+                  <div>
+                    <span >Err:</span>
+                    <span>${model.attrs.err == '0.00000'? 0: (model.attrs.err == '1.00000' ? 1 : model.attrs.err)}</span>
+                  </div>
+                  </div>`
+                  }
+                }
+              ] // 启用拖拽画布和缩放画布的交互模式
+            },
+            layout: {
+              type: 'radial',
+              focusNode: 'li',
+              linkDistance: 200,
+              unitRadius: 200,
+              preventOverlap: true, // 防止节点重叠
+              nodeSpacing: 500,
+              // 防碰撞必须设置nodeSize或size,否则不生效,由于节点的size设置了40,虽然节点不碰撞了,但是节点之间的距离很近,label几乎都挤在一起,所以又重新设置了大一点的nodeSize,这样效果会好很多
+              nodeSize: 60,
+              strictRadial:false,
+              linkDistance: data.nodes.length>6? 400 : 200 // 指定边距离为150
+            },
+            defaultEdge: {
+              style: {
+                endArrow: true
+              },
+              labelCfg: { // 标签文本配置项
+                autoRotate: true,
+                style: {
+                  stroke: '#fff',
+                  lineWidth: 5
+                }
+              }
+            },
+            defaultNode: {
+              type: 'donut',
+              style: {
+                lineWidth: 0,
+                cursor: 'pointer'
+              },
+              labelCfg: {
+                position: 'bottom',
+                autoRotate: true,
+              }
+            }
+          })
+          // 节点配置中使用
+          graph.node((node) => {
+            return {
+              icon: {
+                show: true,
+                // 使用函数动态指定图标路径 imgObj[node.iconType] || imgObj.default
+                img: imgObj[node.iconType] || imgObj.default
+              }
+            };
+          });
+          graph.data(data)
+          graph.render()
+          graph.on('node:mouseenter', (evt) => {
+            const { item } = evt
+            graph.setItemState(item, 'active', true)
+          })
 
-        graph.on('node:mouseleave', (evt) => {
-          const { item } = evt
-          graph.setItemState(item, 'active', false)
+          graph.on('node:mouseleave', (evt) => {
+            const { item } = evt
+            graph.setItemState(item, 'active', false)
+          })
         })
-      })
+      }
+
     }
   }
 

+ 7 - 2
src/views/service/InterfaceDetail/index.vue

@@ -15,8 +15,8 @@
           <div class="service-header">
             <div class="service-header-left" @click="goDrawer">
               <span class="fontSize18">基本信息:</span>
-              <div style="width:100%"><p class="service-header-p"><span class="service-span-right">所属应用: </span><span class="service-span-left">{{ baseInfoObj.app_name }} ({{ appItem.alias }})</span></p></div>
-              <div style="width:100%"><p class="service-header-p" ><span class="service-span-right">所属服务:</span><span class="service-span-left" style="color: #165DFF;"> {{getServiceName}} </span></p></div>
+              <div style="width:100%"><p class="service-header-p"><span class="service-span-right">所属应用: </span><el-tooltip class="item" effect="dark" :content="baseInfoObj.app_name +'('+ appItem.alias +')'" placement="top"><span class="service-span-left">{{ baseInfoObj.app_name }} ({{ appItem.alias }})</span></el-tooltip></p></div>
+              <div style="width:100%"><p class="service-header-p" ><span class="service-span-right">所属服务:</span><el-tooltip class="item" effect="dark" :content="getServiceName" placement="top"><span class="service-span-left" style="color: #165DFF;"> {{getServiceName}} </span></el-tooltip></p></div>
             </div>
             <div class="service-header-right">
               <div class="service-header-rightcol">
@@ -474,6 +474,8 @@ export default {
     font-size:0.2rem;;
     margin-bottom: 0;
     margin-top:10px;
+    display: flex;
+    align-items: center;
     span{
       display: inline-block;
     }
@@ -484,6 +486,9 @@ export default {
     .service-span-left{
       text-align: left;
       width:70%;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
     }
   }
 .interface-service-header{

+ 44 - 5
src/views/service/service/index.vue

@@ -80,12 +80,11 @@
                 </div>
               </template>
             </el-table-column>
-            <el-table-column v-if="colData[5].istrue" header-align="center" label="errRate" sortable prop="error_rate" align="right" :show-overflow-tooltip="true">
+            <el-table-column v-if="colData[5].istrue" header-align="center" label="errRate2343" sortable prop="error_rate" align="right" :show-overflow-tooltip="true">
               <template slot-scope="scope">
                 <div :class="scope.row.error_rate==0?'table_bg':'table_bg_red'">
-                  <!-- <el-tag v-if="scope.row.error_rate==0">{{  scope.row.error_rate }}</el-tag>
-                  <el-tag v-else type="danger">{{  Number(scope.row.error_rate).toFixed(2)}}</el-tag> -->
-                  {{ scope.row.error_rate==0?scope.row.error_rate:Number(scope.row.error_rate).toFixed(2) }}
+                  <!-- {{ scope.row.error_rate==0?scope.row.error_rate:Number(scope.row.error_rate).toFixed(2) }} -->
+                  {{ getErrRate(scope.row.error_rate) }}
                 </div>
               </template>
             </el-table-column>
@@ -233,7 +232,7 @@ import ServiceMap from './components/ServiceMap.vue'
 import { listService, listServiceStats, digitsService, updateService, addService, serviceAppsScore, serviceBar } from '@/api/service'
 import storage from '@/utils/storage'
 import resize from './mixins/resize'
-
+import BigNumber from 'bignumber.js'
 export default {
   components: {
     ServiceMap
@@ -420,7 +419,47 @@ export default {
     this.disposeEcharts(this.myChartBox2List)
   },
   methods: {
+    getErrRate(error_rate){
+      let strErrorRate = numToString(error_rate)
+      let length = null
+      if (strErrorRate !== '0'){
+        length = strErrorRate.split('.')[1].length
+      }
+      if(error_rate == 0) {
+        return error_rate
+      } else if (length<=2 ) {
+        return numToString(error_rate) + '%'
+      } else {
+        return convertToPercentage(error_rate)
+      }
+      function numToString(num) {
+        let str = num.toString();
+        if (str.includes('e')) {
+          let parts = str.split('e');
+          let base = parts[0];
+          let exp = parseInt(parts[1]);
+          let decimalPlaces = base.split('.')[1]?.length || 0;
+          let zerosNeeded = Math.abs(exp) - decimalPlaces;
+          if (exp < 0) {
+            str = '0.' + '0'.repeat(zerosNeeded) + base.replace('.', '');
+          } else {
+            str = base + '0'.repeat(zerosNeeded);
+          }
+        }
+        return str;
+      }
+      function convertToPercentage(value) {
+        let bnValue = new BigNumber(value);
+        let percentage = bnValue.multipliedBy(100);// 乘法运算
+        let roundedPercentage = percentage.decimalPlaces(2, BigNumber.ROUND_FLOOR);// decimalPlaces保留小数位
 
+        // 如果四舍五入后的值小于原始值,手动增加 0.01
+        if (roundedPercentage.isLessThan(percentage)) {// 判断是否小于
+          roundedPercentage = roundedPercentage.plus(0.01);
+        }
+        return `${roundedPercentage.toFixed(2)}%`;
+      }
+    },
     getDigitsService() {
       digitsService(this.digitsQuery).then((res) => {
         if (res.code == 200) {