Merge pull request #246 from ikun-Lg/master

Add corresponding tab(Form View Tab and YAML View Tab) for all routing modules under traffic control module.
diff --git a/ui-vue3/src/api/mock/mockService.ts b/ui-vue3/src/api/mock/mockService.ts
index c10e0cd..2383bcd 100644
--- a/ui-vue3/src/api/mock/mockService.ts
+++ b/ui-vue3/src/api/mock/mockService.ts
@@ -28,56 +28,192 @@
     data: [
       {
         serviceName: 'org.apache.dubbo.samples.UserService',
-        interfaceNum: 4,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 6,
         avgRT: '194ms',
         requestTotal: 200
       },
       {
         serviceName: 'org.apache.dubbo.samples.OrderService',
-        interfaceNum: 12,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 13,
         avgRT: '189ms',
         requestTotal: 164
       },
       {
         serviceName: 'org.apache.dubbo.samples.DetailService',
-        interfaceNum: 14,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 0.5,
         avgRT: '268ms',
         requestTotal: 1324
       },
       {
         serviceName: 'org.apache.dubbo.samples.PayService',
-        interfaceNum: 8,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 9,
         avgRT: '346ms',
         requestTotal: 189
       },
       {
         serviceName: 'org.apache.dubbo.samples.CommentService',
-        interfaceNum: 9,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 8,
         avgRT: '936ms',
         requestTotal: 200
       },
       {
         serviceName: 'org.apache.dubbo.samples.RepayService',
-        interfaceNum: 16,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 17,
         avgRT: '240ms',
         requestTotal: 146
       },
       {
         serviceName: 'org.apche.dubbo.samples.TransportService',
-        interfaceNum: 5,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 43,
         avgRT: '89ms',
         requestTotal: 367
       },
       {
         serviceName: 'org.apche.dubbo.samples.DistributionService',
-        interfaceNum: 5,
+        versionGroup: [
+          {
+            version: '1.0.0',
+            group: 'group1'
+          },
+          {
+            version: '1.0.0',
+            group: null
+          },
+          {
+            version: null,
+            group: 'group1'
+          },
+          {
+            version: null,
+            group: null
+          }
+        ],
         avgQPS: 4,
         avgRT: '78ms',
         requestTotal: 145
diff --git a/ui-vue3/src/api/mock/mockServiceDistribution.ts b/ui-vue3/src/api/mock/mockServiceDistribution.ts
index 4a88fa4..23b74aa 100644
--- a/ui-vue3/src/api/mock/mockServiceDistribution.ts
+++ b/ui-vue3/src/api/mock/mockServiceDistribution.ts
@@ -28,67 +28,101 @@
       {
         applicationName: 'shop-order',
         instanceNum: 15,
-        instanceIP: [
-          '192.168.32.28:8697',
-          '192.168.32.26:20880',
-          '192.168.32.24:28080',
-          '192.168.32.22:20880'
-        ]
+        instanceName: 'shop-order0',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '1000ms',
+        retryNum: '2',
+        label: 'region=beijing'
       },
       {
         applicationName: 'shop-order',
         instanceNum: 15,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+        instanceName: 'shop-order1',
+        rpcPort: '172.168.45.24:20888',
+        timeout: '500ms',
+        retryNum: '1',
+        label: 'region=wuhan'
       },
       {
         applicationName: 'shop-user',
         instanceNum: 12,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+        instanceName: 'shop-order2',
+        rpcPort: '172.161.23.89:20888',
+        timeout: '200ms',
+        retryNum: '1',
+        label: 'region=shanghai'
       },
       {
         applicationName: 'shop-order',
         instanceNum: 15,
-        instanceIP: [
-          '192.168.32.28:8697',
-          '192.168.32.26:20880',
-          '192.168.32.24:28080',
-          '192.168.32.22:20880'
-        ]
+        instanceName: 'shop-order3',
+        rpcPort: '172.168.45.89:12423',
+        timeout: '2000ms',
+        retryNum: '2',
+        label: 'region=hangzhou'
       },
       {
         applicationName: 'shop-order',
         instanceNum: 15,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+        instanceName: 'shop-order4',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '100ms',
+        retryNum: '0',
+        label: 'region=wuxi'
       },
       {
         applicationName: 'shop-user',
         instanceNum: 12,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+        instanceName: 'shop-order5',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '1000ms',
+        retryNum: '2',
+        label: 'region=beijing'
       },
       {
         applicationName: 'shop-order',
         instanceNum: 15,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+        instanceName: 'shop-order6',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '1000ms',
+        retryNum: '2',
+        label: 'region=ningbo'
       },
       {
         applicationName: 'shop-user',
         instanceNum: 12,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+        instanceName: 'shop-order7',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '1000ms',
+        retryNum: '2',
+        label: 'region=shenzhen'
       },
       {
         applicationName: 'shop-user',
         instanceNum: 12,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+        instanceName: 'shop-order8',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '1000ms',
+        retryNum: '2',
+        label: 'region=guangzhou'
       },
       {
         applicationName: 'shop-order',
         instanceNum: 15,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+        instanceName: 'shop-order9',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '1000ms',
+        retryNum: '2',
+        label: 'region=nanjing'
       },
       {
         applicationName: 'shop-user',
         instanceNum: 12,
-        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+        instanceName: 'shop-order10',
+        rpcPort: '172.168.45.89:20888',
+        timeout: '1000ms',
+        retryNum: '2',
+        label: 'region=beijing'
       }
     ]
   }
diff --git a/ui-vue3/src/base/i18n/en.ts b/ui-vue3/src/base/i18n/en.ts
index dab06b5..f197248 100644
--- a/ui-vue3/src/base/i18n/en.ts
+++ b/ui-vue3/src/base/i18n/en.ts
@@ -122,6 +122,9 @@
     labels: 'Labels',
     startTime_k8s: 'StartTime(k8s)'
   },
+  serviceDomain: {
+    name: 'Name'
+  },
   appServiceTimeout: 'Adjusting the timeout for application service provision',
   enableAppInstanceLogs: 'Enable access logs for all instances of this application',
   appServiceLoadBalance: 'Adjusting the load balancing strategy for application service provision',
@@ -160,6 +163,10 @@
   instance: 'Instance',
   resourceDetails: 'Resource Details',
   service: 'Service',
+  versionGroup: 'Version & Group',
+  avgQPS: 'last 1min QPS',
+  avgRT: 'last 1min RT',
+  requestTotal: 'last 1min request total',
   serviceSearch: 'Search Service',
   serviceGovernance: 'Routing Rule',
   trafficManagement: 'Traffic Management',
@@ -452,6 +459,7 @@
   debug: 'Debug',
   distribution: 'Distribution',
   tracing: 'Tracing',
+  sceneConfig: 'Scene Config',
 
   provideService: 'Provide Service',
   dependentService: 'Dependent Service',
diff --git a/ui-vue3/src/base/i18n/zh.ts b/ui-vue3/src/base/i18n/zh.ts
index f90352b..28e68a7 100644
--- a/ui-vue3/src/base/i18n/zh.ts
+++ b/ui-vue3/src/base/i18n/zh.ts
@@ -122,7 +122,14 @@
     creationTime_k8s: '创建时间(k8s)',
     startTime_k8s: '启动时间(k8s)'
   },
+  serviceDomain: {
+    name: '服务名'
+  },
   service: '服务',
+  versionGroup: '版本&分组',
+  avgQPS: '近1min QPS',
+  avgRT: '近1min RT',
+  requestTotal: '近1min 请求总量',
   serviceSearch: '服务查询',
   serviceGovernance: '路由规则',
   trafficManagement: '流量管控',
@@ -424,6 +431,7 @@
   distribution: '分布',
   monitor: '监控',
   tracing: '链路追踪',
+  sceneConfig: '场景配置',
   event: '事件',
 
   provideService: '提供服务',
diff --git a/ui-vue3/src/router/defaultRoutes.ts b/ui-vue3/src/router/defaultRoutes.ts
index 977fe70..a33feef 100644
--- a/ui-vue3/src/router/defaultRoutes.ts
+++ b/ui-vue3/src/router/defaultRoutes.ts
@@ -20,6 +20,7 @@
 import LayoutTab from '../layout/tab/layout_tab.vue'
 import _ from 'lodash'
 import AppTabHeaderSlot from '@/views/resources/applications/slots/AppTabHeaderSlot.vue'
+import ServiceTabHeaderSlot from '@/views/resources/services/slots/ServiceTabHeaderSlot.vue'
 
 export declare type RouteRecordType = RouteRecordRaw & {
   key?: string
@@ -208,7 +209,10 @@
             redirect: 'search',
             component: LayoutTab,
             meta: {
-              tab_parent: true
+              tab_parent: true,
+              slots: {
+                header: ServiceTabHeaderSlot
+              }
             },
             children: [
               {
@@ -220,7 +224,7 @@
                 }
               },
               {
-                path: '/detail/:serviceName',
+                path: '/detail/:pathId',
                 name: 'detail',
                 component: () => import('../views/resources/services/tabs/detail.vue'),
                 meta: {
@@ -228,7 +232,7 @@
                 }
               },
               {
-                path: '/debug/:serviceName',
+                path: '/debug/:pathId',
                 name: 'debug',
                 component: () => import('../views/resources/services/tabs/debug.vue'),
                 meta: {
@@ -236,7 +240,7 @@
                 }
               },
               {
-                path: '/distribution/:serviceName',
+                path: '/distribution/:pathId',
                 name: 'distribution',
                 component: () => import('../views/resources/services/tabs/distribution.vue'),
                 meta: {
@@ -244,7 +248,7 @@
                 }
               },
               {
-                path: '/monitor/:serviceName',
+                path: '/monitor/:pathId',
                 name: 'monitor',
                 component: () => import('../views/resources/services/tabs/monitor.vue'),
                 meta: {
@@ -252,7 +256,7 @@
                 }
               },
               {
-                path: '/tracing/:serviceName',
+                path: '/tracing/:pathId',
                 name: 'tracing',
                 component: () => import('../views/resources/services/tabs/tracing.vue'),
                 meta: {
@@ -260,7 +264,15 @@
                 }
               },
               {
-                path: '/event/:serviceName',
+                path: '/sceneConfig/:pathId',
+                name: 'sceneConfig',
+                component: () => import('../views/resources/services/tabs/sceneConfig.vue'),
+                meta: {
+                  tab: true
+                }
+              },
+              {
+                path: '/event/:pathId',
                 name: 'event',
                 component: () => import('../views/resources/services/tabs/event.vue'),
                 meta: {
diff --git a/ui-vue3/src/utils/SearchUtil.ts b/ui-vue3/src/utils/SearchUtil.ts
index aeb39ec..e6f0fcb 100644
--- a/ui-vue3/src/utils/SearchUtil.ts
+++ b/ui-vue3/src/utils/SearchUtil.ts
@@ -41,6 +41,7 @@
   ]
   searchApi: Function
   result: any
+  handleResult?: Function
   tableStyle: any
   table: {
     loading?: boolean
@@ -57,7 +58,8 @@
     searchApi: any,
     columns: TableColumnsType | any,
     paged?: any | undefined,
-    noPaged?: boolean
+    noPaged?: boolean,
+    handleResult?: Function
   ) {
     this.params = query
     this.noPaged = noPaged
@@ -72,16 +74,17 @@
       this.paged = { ...this.paged, ...paged }
     }
     this.searchApi = searchApi
-    this.onSearch()
+    handleResult && this.onSearch(handleResult)
   }
 
-  async onSearch() {
+  async onSearch(handleResult: Function) {
     this.table.loading = true
     setTimeout(() => {
       this.table.loading = false
     }, 5000)
-    let res = (await this.searchApi(this.queryForm || {})).data
-    this.result = res.data
+    const res = (await this.searchApi(this.queryForm || {})).data
+    this.result = handleResult ? handleResult(res.data) : res.data
+    console.log(this.result)
     this.paged.total = res.total
     this.table.loading = false
   }
diff --git a/ui-vue3/src/views/resources/services/search.vue b/ui-vue3/src/views/resources/services/search.vue
index 73ef847..64db7ad 100644
--- a/ui-vue3/src/views/resources/services/search.vue
+++ b/ui-vue3/src/views/resources/services/search.vue
@@ -19,7 +19,23 @@
     <search-table :search-domain="searchDomain">
       <template #bodyCell="{ column, text }">
         <template v-if="column.dataIndex === 'serviceName'">
-          <a-button type="link" @click="viewDetail(text)">{{ text }}</a-button>
+          <span class="service-link" @click="viewDetail(text)">
+            <b>
+              <Icon style="margin-bottom: -2px" icon="material-symbols:attach-file-rounded"></Icon>
+              {{ text }}
+            </b>
+          </span>
+        </template>
+        <template v-else-if="column.dataIndex === 'versionGroupSelect'">
+          <a-select v-model:value="text.versionGroupValue" :bordered="false" style="width: 80%">
+            <a-select-option
+              v-for="(item, index) in text.versionGroupArr"
+              :value="item"
+              :key="index"
+            >
+              {{ item }}
+            </a-select-option>
+          </a-select>
         </template>
       </template>
     </search-table>
@@ -33,69 +49,99 @@
 import { SearchDomain } from '@/utils/SearchUtil'
 import SearchTable from '@/components/SearchTable.vue'
 import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
+import { PRIMARY_COLOR } from '@/base/constants'
+import { Icon } from '@iconify/vue'
 
+let __null = PRIMARY_COLOR
 const router = useRouter()
 const columns = [
   {
-    title: '服务',
+    title: 'service',
+    key: 'service',
     dataIndex: 'serviceName',
-    key: 'serviceName',
     sorter: true,
     width: '30%'
   },
   {
-    title: '接口数',
-    dataIndex: 'interfaceNum',
-    key: 'interfaceNum',
-    sorter: true,
-    width: '10%'
+    title: 'versionGroup',
+    key: 'versionGroup',
+    dataIndex: 'versionGroupSelect',
+    width: '25%'
   },
   {
-    title: '近 1min QPS',
-    dataIndex: 'avgQPS',
+    title: 'avgQPS',
     key: 'avgQPS',
+    dataIndex: 'avgQPS',
     sorter: true,
     width: '15%'
   },
   {
-    title: '近 1min RT',
-    dataIndex: 'avgRT',
+    title: 'avgRT',
     key: 'avgRT',
+    dataIndex: 'avgRT',
     sorter: true,
     width: '15%'
   },
   {
-    title: '近 1min 请求总量',
-    dataIndex: 'requestTotal',
+    title: 'requestTotal',
     key: 'requestTotal',
+    dataIndex: 'requestTotal',
     sorter: true,
     width: '15%'
   }
 ]
 
+const handleResult = (result: any) => {
+  return result.map((service) => {
+    service.versionGroupSelect = {}
+    service.versionGroupSelect.versionGroupArr = service.versionGroup.map((item: any) => {
+      return (item.versionGroup =
+        (item.version ? 'version: ' + item.version + ', ' : '') +
+          (item.group ? 'group: ' + item.group : '') || '无')
+    })
+    service.versionGroupSelect.versionGroupValue = service.versionGroupSelect.versionGroupArr[0]
+    return service
+  })
+}
+
 const searchDomain = reactive(
   new SearchDomain(
     [
       {
-        label: '服务名',
+        label: 'serviceName',
         param: 'serviceName',
-        placeholder: '请输入',
+        placeholder: 'typeAppName',
         style: {
           width: '200px'
         }
       }
     ],
     searchService,
-    columns
+    columns,
+    undefined,
+    undefined,
+    handleResult
   )
 )
 
-searchDomain.onSearch()
+searchDomain.onSearch(handleResult)
 
 const viewDetail = (serviceName: string) => {
-  router.push({ name: 'detail', params: { serviceName } })
+  router.push({ name: 'detail', params: { pathId: serviceName } })
 }
 
 provide(PROVIDE_INJECT_KEY.SEARCH_DOMAIN, searchDomain)
 </script>
-<style lang="less" scoped></style>
+<style lang="less" scoped>
+.__container_services_index {
+  .service-link {
+    padding: 4px 10px 4px 4px;
+    border-radius: 4px;
+    color: v-bind('PRIMARY_COLOR');
+    &:hover {
+      cursor: pointer;
+      background: rgba(133, 131, 131, 0.13);
+    }
+  }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/slots/ServiceTabHeaderSlot.vue b/ui-vue3/src/views/resources/services/slots/ServiceTabHeaderSlot.vue
new file mode 100644
index 0000000..2962a4e
--- /dev/null
+++ b/ui-vue3/src/views/resources/services/slots/ServiceTabHeaderSlot.vue
@@ -0,0 +1,46 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<template>
+  <!--      example like blow-->
+  <div class="__container_ServiceTabHeaderSlot">
+    <span class="header-desc">{{ $t('serviceDomain.name') }}: {{ route.params?.pathId }}</span>
+    <!-- <a-select
+      v-model:value="versionGroupSelect.versionGroupValue"
+      :bordered="false"
+      style="width: 80%"
+    >
+      <a-select-option v-for="(item, index) in versionGroupSelect.versionGroupArr" :value="item" :key="index">
+        {{ item }}
+      </a-select-option>
+    </a-select> -->
+  </div>
+</template>
+
+<script setup lang="ts">
+import { inject } from 'vue'
+import { useRoute } from 'vue-router'
+import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
+const route = useRoute()
+</script>
+<style lang="less" scoped>
+.__container_ServiceTabHeaderSlot {
+  .header-desc {
+    line-height: 30px;
+    vertical-align: center;
+  }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/debug.vue b/ui-vue3/src/views/resources/services/tabs/debug.vue
index 6e67003..796349f 100644
--- a/ui-vue3/src/views/resources/services/tabs/debug.vue
+++ b/ui-vue3/src/views/resources/services/tabs/debug.vue
@@ -19,20 +19,12 @@
     <div class="tabs-title">方法列表</div>
     <a-tabs v-model:activeKey="activeKey" tab-position="left" :tabBarStyle="{ width: '200px' }">
       <a-tab-pane v-for="tabName in methodTabs" :key="tabName" :tab="`${tabName}`">
-        <a-descriptions :column="4" layout="vertical">
-          <a-descriptions-item label="接口" :span="2">
+        <a-descriptions :column="2" layout="vertical">
+          <a-descriptions-item label="接口">
             <p class="description-item-content">
               org.apache.dubbo.samples.UserService:getPermissions
             </p>
           </a-descriptions-item>
-          <a-descriptions-item label="版本&分组">
-            <a-select
-              v-model:value="versionAndGroup"
-              size="large"
-              :options="versionAndGroupOptions"
-              class="description-item-content"
-            ></a-select>
-          </a-descriptions-item>
           <a-descriptions-item label="指定生产者">
             <a-select
               v-model:value="versionAndGroup"
@@ -41,16 +33,16 @@
               class="description-item-content"
             ></a-select>
           </a-descriptions-item>
-          <a-descriptions-item label="入参类型" :span="2">
+          <a-descriptions-item label="入参类型">
             <a-tree block-node :tree-data="enterParamType" class="description-item-content" />
           </a-descriptions-item>
-          <a-descriptions-item label="出参类型" :span="2">
+          <a-descriptions-item label="出参类型">
             <a-tree block-node :tree-data="outputParamType" class="description-item-content" />
           </a-descriptions-item>
-          <a-descriptions-item label="请求" :span="2">
+          <a-descriptions-item label="请求">
             <monaco-editor editorId="requestEditor" width="90%" height="300px" />
           </a-descriptions-item>
-          <a-descriptions-item label="响应" :span="2">
+          <a-descriptions-item label="响应">
             <monaco-editor editorId="responseEditor" width="90%" height="300px" />
           </a-descriptions-item>
         </a-descriptions>
@@ -152,7 +144,7 @@
   }
   .description-item-content {
     margin-left: 20px;
-    width: 90%;
+    width: 80%;
   }
 }
 </style>
diff --git a/ui-vue3/src/views/resources/services/tabs/detail.vue b/ui-vue3/src/views/resources/services/tabs/detail.vue
index 9420d9c..4c8452b 100644
--- a/ui-vue3/src/views/resources/services/tabs/detail.vue
+++ b/ui-vue3/src/views/resources/services/tabs/detail.vue
@@ -16,46 +16,55 @@
 -->
 <template>
   <div class="__container_services_tabs_detail">
-    <a-flex>
-      <a-descriptions class="description-column" :column="1" layout="vertical">
-        <a-descriptions-item label="服务名">
-          <p class="description-item-content">{{ serviceDetail.serviceName }}</p>
-        </a-descriptions-item>
-        <a-descriptions-item label="服务版本&分组">
-          <div class="description-item-content">
-            <a-tag color="cyan" v-for="item in serviceDetail.versionGroup" :key="item">{{
-              item
-            }}</a-tag>
-          </div>
-        </a-descriptions-item>
-        <a-descriptions-item label="RPC协议">
-          <p class="description-item-content">{{ serviceDetail.protocol }}</p>
-        </a-descriptions-item>
-        <a-descriptions-item label="延迟注册时间">
-          <p class="description-item-content">{{ serviceDetail.delay }}</p>
-        </a-descriptions-item>
-        <a-descriptions-item label="超时时间">
-          <p class="description-item-content">{{ serviceDetail.timeOut }}</p>
-        </a-descriptions-item>
-        <a-descriptions-item label="重试次数">
-          <p class="description-item-content">{{ serviceDetail.retry }}</p>
-        </a-descriptions-item>
-      </a-descriptions>
-      <a-descriptions class="description-column" :column="1" layout="vertical">
-        <a-descriptions-item label="请求总量(1min)">
-          <p class="description-item-content">{{ serviceDetail.requestTotal }}</p>
-        </a-descriptions-item>
-        <a-descriptions-item label="平均RT(1min)">
-          <p class="description-item-content">{{ serviceDetail.avgRT }}</p>
-        </a-descriptions-item>
-        <a-descriptions-item label="平均qps(1min)">
-          <p class="description-item-content">{{ serviceDetail.avgQPS }}</p>
-        </a-descriptions-item>
-        <a-descriptions-item label="是否过时">
-          <p class="description-item-content">{{ serviceDetail.obsolete }}</p>
-        </a-descriptions-item>
-      </a-descriptions>
-    </a-flex>
+    <a-card-grid>
+      <a-card title="生产者详情">
+        <a-flex>
+          <a-descriptions class="description-column" :column="1" layout="vertical">
+            <a-descriptions-item label="超时时间">
+              <p class="description-item-content">{{ serviceDetail.timeOut }}</p>
+            </a-descriptions-item>
+            <a-descriptions-item label="重试次数">
+              <p class="description-item-content">{{ serviceDetail.retry }}</p>
+            </a-descriptions-item>
+            <a-descriptions-item label="是否过时">
+              <p class="description-item-content">{{ serviceDetail.obsolete }}</p>
+            </a-descriptions-item>
+          </a-descriptions>
+          <a-descriptions class="description-column" :column="1" layout="vertical">
+            <a-descriptions-item label="RPC协议">
+              <p class="description-item-content">{{ serviceDetail.protocol }}</p>
+            </a-descriptions-item>
+            <a-descriptions-item label="延迟注册时间">
+              <p class="description-item-content">{{ serviceDetail.delay }}</p>
+            </a-descriptions-item>
+          </a-descriptions>
+        </a-flex>
+      </a-card>
+      <a-card title="消费者详情" style="margin-top: 10px">
+        <a-flex>
+          <a-descriptions class="description-column" :column="1" layout="vertical">
+            <a-descriptions-item label="超时时间">
+              <p class="description-item-content">{{ serviceDetail.timeOut }}</p>
+            </a-descriptions-item>
+            <a-descriptions-item label="重试次数">
+              <p class="description-item-content">{{ serviceDetail.retry }}</p>
+            </a-descriptions-item>
+            <a-descriptions-item label="是否过时">
+              <p class="description-item-content">{{ serviceDetail.obsolete }}</p>
+            </a-descriptions-item>
+          </a-descriptions>
+          <a-descriptions class="description-column" :column="1" layout="vertical">
+            <a-descriptions-item label="RPC协议">
+              <p class="description-item-content">{{ serviceDetail.protocol }}</p>
+            </a-descriptions-item>
+            <a-descriptions-item label="延迟注册时间">
+              <p class="description-item-content">{{ serviceDetail.delay }}</p>
+            </a-descriptions-item>
+          </a-descriptions>
+        </a-flex>
+      </a-card>
+    </a-card-grid>
+    <a-flex> </a-flex>
   </div>
 </template>
 
diff --git a/ui-vue3/src/views/resources/services/tabs/distribution.vue b/ui-vue3/src/views/resources/services/tabs/distribution.vue
index 4b985a2..ef7a335 100644
--- a/ui-vue3/src/views/resources/services/tabs/distribution.vue
+++ b/ui-vue3/src/views/resources/services/tabs/distribution.vue
@@ -18,30 +18,17 @@
   <div class="__container_services_tabs_distribution">
     <a-flex vertical>
       <a-flex class="service-filter">
-        <a-flex>
-          <div>
-            <span>版本&分组:</span>
-            <a-select
-              v-model:value="versionAndGroup"
-              :options="versionAndGroupOptions"
-              class="service-filter-select"
-              @change="debounceSearch"
-            ></a-select>
-          </div>
-          <a-input-search
-            v-model:value="searchValue"
-            placeholder="搜索应用,ip,支持前缀搜索"
-            class="service-filter-input"
-            @search="debounceSearch"
-            enter-button
-          />
-        </a-flex>
-        <div>
-          <a-radio-group v-model:value="type" button-style="solid" @click="debounceSearch">
-            <a-radio-button value="producer">生产者</a-radio-button>
-            <a-radio-button value="consumer">消费者</a-radio-button>
-          </a-radio-group>
-        </div>
+        <a-radio-group v-model:value="type" button-style="solid" @click="debounceSearch">
+          <a-radio-button value="producer">生产者</a-radio-button>
+          <a-radio-button value="consumer">消费者</a-radio-button>
+        </a-radio-group>
+        <a-input-search
+          v-model:value="searchValue"
+          placeholder="搜索应用,ip,支持前缀搜索"
+          class="service-filter-input"
+          @search="debounceSearch"
+          enter-button
+        />
       </a-flex>
       <a-table
         :columns="tableColumns"
@@ -51,13 +38,18 @@
       >
         <template #bodyCell="{ column, text }">
           <template v-if="column.dataIndex === 'applicationName'">
-            <a-button type="link">{{ text }}</a-button>
+            <span class="app-link" @click="viewDetail(text)">
+              <b>
+                <Icon
+                  style="margin-bottom: -2px"
+                  icon="material-symbols:attach-file-rounded"
+                ></Icon>
+                {{ text }}
+              </b>
+            </span>
           </template>
-          <template v-if="column.dataIndex === 'instanceIP'">
-            <a-flex justify="">
-              <a-button v-for="ip in text.slice(0, 3)" :key="ip" type="link">{{ ip }}</a-button>
-              <a-button v-if="text.length > 3" type="link">更多</a-button>
-            </a-flex>
+          <template v-if="column.dataIndex === 'label'">
+            <a-tag :color="PRIMARY_COLOR">{{ text }}</a-tag>
           </template>
         </template>
       </a-table>
@@ -70,7 +62,10 @@
 import { ref, reactive, getCurrentInstance } from 'vue'
 import { getServiceDistribution } from '@/api/service/service'
 import { debounce } from 'lodash'
+import { PRIMARY_COLOR } from '@/base/constants'
+import { Icon } from '@iconify/vue'
 
+let __null = PRIMARY_COLOR
 const {
   appContext: {
     config: { globalProperties }
@@ -103,19 +98,51 @@
   {
     title: '应用名',
     dataIndex: 'applicationName',
-    width: '25%',
-    sorter: true
+    width: '20%',
+    customCell: (_, index) => {
+      if (index === 0) {
+        return { rowSpan: tableData.value.length }
+      } else {
+        return { rowSpan: 0 }
+      }
+    }
   },
   {
     title: '实例数',
     dataIndex: 'instanceNum',
-    width: '25%',
-    sorter: true
+    width: '15%',
+    customCell: (_, index) => {
+      if (index === 0) {
+        return { rowSpan: tableData.value.length }
+      } else {
+        return { rowSpan: 0 }
+      }
+    }
   },
   {
-    title: '实例ip',
-    dataIndex: 'instanceIP',
-    width: '50%'
+    title: '实例名',
+    dataIndex: 'instanceName',
+    width: '15%'
+  },
+  {
+    title: 'RPC端口',
+    dataIndex: 'rpcPort',
+    width: '15%'
+  },
+  {
+    title: '超时时间',
+    dataIndex: 'timeout',
+    width: '10%'
+  },
+  {
+    title: '重试次数',
+    dataIndex: 'retryNum',
+    width: '10%'
+  },
+  {
+    title: '标签',
+    dataIndex: 'label',
+    width: '15%'
   }
 ]
 
@@ -138,10 +165,10 @@
     globalProperties.$t('searchDomain.unit')
 }
 </script>
+
 <style lang="less" scoped>
 .__container_services_tabs_distribution {
   .service-filter {
-    justify-content: space-between;
     margin-bottom: 20px;
     .service-filter-select {
       margin-left: 10px;
@@ -152,5 +179,14 @@
       width: 300px;
     }
   }
+  .app-link {
+    padding: 4px 10px 4px 4px;
+    border-radius: 4px;
+    color: v-bind('PRIMARY_COLOR');
+    &:hover {
+      cursor: pointer;
+      background: rgba(133, 131, 131, 0.13);
+    }
+  }
 }
 </style>
diff --git a/ui-vue3/src/views/resources/services/tabs/event.vue b/ui-vue3/src/views/resources/services/tabs/event.vue
index d9a86c0..11a5eaa 100644
--- a/ui-vue3/src/views/resources/services/tabs/event.vue
+++ b/ui-vue3/src/views/resources/services/tabs/event.vue
@@ -15,8 +15,52 @@
   ~ limitations under the License.
 -->
 <template>
-  <div class="__container_services_tabs_event">事件todo</div>
+  <div class="__container_services_tabs_event">
+    <a-timeline class="timeline">
+      <a-timeline-item v-for="(item, index) in eventData" :key="index">
+        <a-tag class="time" :color="PRIMARY_COLOR">{{ item.time }}</a-tag>
+        <span class="description">{{ item.description }}</span>
+      </a-timeline-item>
+    </a-timeline>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
-<style lang="less" scoped></style>
+<script setup lang="ts">
+import { PRIMARY_COLOR } from '@/base/constants'
+
+let __null = PRIMARY_COLOR
+const eventData = [
+  {
+    time: '2022-01-01',
+    description: 'description'
+  },
+  {
+    time: '2022-01-02',
+    description: 'description'
+  },
+  {
+    time: '2022-01-03',
+    description: 'description'
+  },
+  {
+    time: '2022-01-04',
+    description: 'description'
+  },
+  {
+    time: '2022-01-05',
+    description: 'description'
+  }
+]
+</script>
+
+<style lang="less" scoped>
+.__container_services_tabs_event {
+  display: flex;
+  justify-content: center;
+  .timeline {
+    .description {
+      font-size: 16px;
+    }
+  }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/paramRoute.vue b/ui-vue3/src/views/resources/services/tabs/paramRoute.vue
new file mode 100644
index 0000000..6bcfce3
--- /dev/null
+++ b/ui-vue3/src/views/resources/services/tabs/paramRoute.vue
@@ -0,0 +1,198 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<template>
+  <div class="__container_services_tabs_param_route">
+    <a-card :bordered="false" style="width: 1000px">
+      <template #title>
+        <a-flex justify="space-between">
+          <span>路由</span>
+          <a-flex class="handle-form">
+            <EditOutlined class="edit-icon" />
+            <DeleteOutlined class="edit-icon" />
+          </a-flex>
+        </a-flex>
+      </template>
+      <a-form :labelCol="{ span: 3 }">
+        <a-form-item label="选择方法">
+          <a-select v-model:value="method.value" style="width: 120px">
+            <a-select-option v-for="(item, index) in method.selectArr" :value="item" :key="index">
+              {{ item }}
+            </a-select-option>
+          </a-select>
+        </a-form-item>
+        <a-form-item label="指定方法参数">
+          <a-table
+            :columns="functionParamsColumn"
+            :data-source="props.paramRouteForm.functionParams"
+            :pagination="false"
+          >
+            <template #bodyCell="{ column, index: idx }">
+              <template v-if="column.dataIndex === 'param'">
+                <a-input v-model:value="functionParamsEdit[idx].param" />
+              </template>
+              <template v-if="column.dataIndex === 'relation'">
+                <a-input v-model:value="functionParamsEdit[idx].relation" />
+              </template>
+              <template v-if="column.dataIndex === 'value'">
+                <a-input v-model:value="functionParamsEdit[idx].value" />
+              </template>
+              <template v-if="column.dataIndex === 'handle'">
+                <a-flex justify="space-between">
+                  <PlusOutlined
+                    class="edit-icon"
+                    @click="emit('addRow', 'functionParams', props.index, idx)"
+                  />
+                  <MinusOutlined
+                    class="edit-icon"
+                    @click="emit('deleteRow', 'functionParams', props.index, idx)"
+                  />
+                </a-flex>
+              </template>
+            </template>
+          </a-table>
+        </a-form-item>
+        <a-form-item label="路由目的地">
+          <a-table
+            :columns="destinationColumn"
+            :data-source="props.paramRouteForm.destination"
+            :pagination="false"
+          >
+            <template #bodyCell="{ column, index: idx }">
+              <template v-if="column.dataIndex === 'label'">
+                <a-input v-model:value="destinationEdit[idx].label" />
+              </template>
+              <template v-if="column.dataIndex === 'relation'">
+                <a-input v-model:value="destinationEdit[idx].relation" />
+              </template>
+              <template v-if="column.dataIndex === 'value'">
+                <a-input v-model:value="destinationEdit[idx].value" />
+              </template>
+              <template v-if="column.dataIndex === 'weight'">
+                <a-input v-model:value="destinationEdit[idx].weight" />
+              </template>
+              <template v-if="column.dataIndex === 'handle'">
+                <a-flex justify="space-between">
+                  <PlusOutlined
+                    class="edit-icon"
+                    @click="emit('addRow', 'destination', props.index, idx)"
+                  />
+                  <MinusOutlined
+                    class="edit-icon"
+                    @click="emit('deleteRow', 'destination', props.index, idx)"
+                  />
+                </a-flex>
+              </template>
+            </template>
+          </a-table>
+        </a-form-item>
+      </a-form>
+    </a-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { EditOutlined, DeleteOutlined, PlusOutlined, MinusOutlined } from '@ant-design/icons-vue'
+import { ref } from 'vue'
+
+const props = defineProps<{
+  paramRouteForm: {
+    type: Object
+    default: {}
+  }
+  index: {
+    type: Number
+  }
+}>()
+
+console.log('props', props.paramRouteForm)
+
+const method = ref(JSON.parse(JSON.stringify(props.paramRouteForm.method)))
+const functionParamsEdit = ref(JSON.parse(JSON.stringify(props.paramRouteForm.functionParams)))
+const destinationEdit = ref(JSON.parse(JSON.stringify(props.paramRouteForm.destination)))
+
+const functionParamsColumn = [
+  {
+    title: '参数索引',
+    key: 'param',
+    dataIndex: 'param',
+    width: '30%'
+  },
+  {
+    title: '关系',
+    key: 'relation',
+    dataIndex: 'relation',
+    width: '30%'
+  },
+  {
+    title: '值',
+    key: 'value',
+    dataIndex: 'value',
+    width: '30%'
+  },
+  {
+    title: '操作',
+    key: 'handle',
+    dataIndex: 'handle',
+    width: '10%'
+  }
+]
+
+const destinationColumn = [
+  {
+    title: '标签',
+    key: 'label',
+    dataIndex: 'label',
+    width: '25%'
+  },
+  {
+    title: '关系',
+    key: 'relation',
+    dataIndex: 'relation',
+    width: '25%'
+  },
+  {
+    title: '值',
+    key: 'value',
+    dataIndex: 'value',
+    width: '25%'
+  },
+  {
+    title: '权重',
+    key: 'weight',
+    dataIndex: 'weight',
+    width: '15%'
+  },
+  {
+    title: '操作',
+    key: 'handle',
+    dataIndex: 'handle',
+    width: '10%'
+  }
+]
+</script>
+
+<style lang="less" scoped>
+.__container_services_tabs_param_route {
+  .handle-form {
+    width: 50px;
+    justify-content: space-between;
+  }
+  .edit-icon {
+    font-size: 18px;
+  }
+}
+</style>
diff --git a/ui-vue3/src/views/resources/services/tabs/sceneConfig.vue b/ui-vue3/src/views/resources/services/tabs/sceneConfig.vue
new file mode 100644
index 0000000..2fc5d3e
--- /dev/null
+++ b/ui-vue3/src/views/resources/services/tabs/sceneConfig.vue
@@ -0,0 +1,146 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<template>
+  <div class="__container_services_tabs_scene_config">
+    <a-tabs v-model:activeKey="activeKey" :tab-position="'left'" animated>
+      <a-tab-pane key="timeout" tab="超时时间">
+        <a-descriptions layout="vertical">
+          <a-descriptions-item label="超时时间">
+            <a-flex v-if="!editForm.timeout.isEdit">
+              <span class="item-content">1000ms</span>
+              <EditOutlined @click="showEdit('timeout')" class="item-icon" />
+            </a-flex>
+            <a-flex v-else align="center">
+              <a-input v-model:value="editForm.timeout.value" class="item-input" />
+              <span style="margin-left: 5px">ms</span>
+              <CheckOutlined @click="hideEdit('timeout')" class="item-icon" />
+              <CloseOutlined @click="hideEdit('timeout')" class="item-icon" />
+            </a-flex>
+          </a-descriptions-item>
+        </a-descriptions>
+      </a-tab-pane>
+      <a-tab-pane key="retryNum" tab="重试次数">
+        <a-descriptions layout="vertical">
+          <a-descriptions-item label="重试次数">
+            <a-flex v-if="!editForm.retryNum.isEdit">
+              <span class="item-content">1000次</span>
+              <EditOutlined @click="showEdit('retryNum')" class="item-icon" />
+            </a-flex>
+            <a-flex v-else align="center">
+              <a-input v-model:value="editForm.retryNum.value" class="item-input" />
+              <span style="margin-left: 5px">次</span>
+              <CheckOutlined @click="hideEdit('retryNum')" class="item-icon" />
+              <CloseOutlined @click="hideEdit('retryNum')" class="item-icon" />
+            </a-flex>
+          </a-descriptions-item>
+        </a-descriptions>
+      </a-tab-pane>
+      <a-tab-pane key="sameArea" tab="同区域优先">
+        <a-descriptions layout="vertical">
+          <a-descriptions-item label="同区域优先">
+            <a-radio-group v-model:value="editForm.sameArea.value" button-style="solid">
+              <a-radio-button value="close">关闭</a-radio-button>
+              <a-radio-button value="open">开启</a-radio-button>
+            </a-radio-group>
+          </a-descriptions-item>
+        </a-descriptions>
+      </a-tab-pane>
+      <a-tab-pane key="paramRoute" tab="参数路由">
+        <paramRoute
+          v-for="(item, index) in paramRouteForms"
+          :key="index"
+          :paramRouteForm="item"
+          :index="index"
+          @addRow="() => {}"
+          @deleteRow="() => {}"
+        />
+        <a-button type="primary" style="margin-top: 20px">增加路由</a-button>
+      </a-tab-pane>
+    </a-tabs>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import { EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons-vue'
+import paramRoute from './paramRoute.vue'
+
+const editForm = reactive({
+  timeout: {
+    isEdit: false,
+    value: ''
+  },
+  retryNum: {
+    isEdit: false,
+    value: ''
+  },
+  sameArea: {
+    value: 'close'
+  },
+  paramRoute: {
+    isEdit: false,
+    value: {}
+  }
+})
+
+const activeKey = ref('timeout')
+const showEdit = (param: string) => {
+  editForm[param].isEdit = true
+}
+const hideEdit = (param: string) => {
+  editForm[param].isEdit = false
+}
+
+const paramRouteForms = [
+  {
+    method: {
+      value: 'getUserInfo',
+      selectArr: ['getUserInfo', 'register', 'login']
+    },
+    functionParams: [
+      {
+        param: '',
+        relation: '',
+        value: ''
+      }
+    ],
+    destination: [
+      {
+        label: '',
+        relation: '',
+        value: '',
+        weight: ''
+      }
+    ]
+  }
+]
+</script>
+
+<style lang="less" scoped>
+.__container_services_tabs_scene_config {
+  .item-content {
+    margin-right: 20px;
+  }
+  .item-input {
+    width: 200px;
+  }
+  .item-icon {
+    margin-left: 15px;
+    font-size: 18px;
+  }
+}
+</style>