Merge branch 'master' into feature/ui/improveServicePage
diff --git a/ui-vue3/src/api/mock/mockServiceDistribution.ts b/ui-vue3/src/api/mock/mockServiceDistribution.ts
new file mode 100644
index 0000000..4a88fa4
--- /dev/null
+++ b/ui-vue3/src/api/mock/mockServiceDistribution.ts
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+import Mock from 'mockjs'
+
+Mock.mock('/mock/service/distribution', 'get', {
+  code: 200,
+  message: 'success',
+  data: {
+    total: 8,
+    curPage: 1,
+    pageSize: 1,
+    data: [
+      {
+        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'
+        ]
+      },
+      {
+        applicationName: 'shop-order',
+        instanceNum: 15,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+      },
+      {
+        applicationName: 'shop-user',
+        instanceNum: 12,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+      },
+      {
+        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'
+        ]
+      },
+      {
+        applicationName: 'shop-order',
+        instanceNum: 15,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+      },
+      {
+        applicationName: 'shop-user',
+        instanceNum: 12,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+      },
+      {
+        applicationName: 'shop-order',
+        instanceNum: 15,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+      },
+      {
+        applicationName: 'shop-user',
+        instanceNum: 12,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+      },
+      {
+        applicationName: 'shop-user',
+        instanceNum: 12,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+      },
+      {
+        applicationName: 'shop-order',
+        instanceNum: 15,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
+      },
+      {
+        applicationName: 'shop-user',
+        instanceNum: 12,
+        instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
+      }
+    ]
+  }
+})
diff --git a/ui-vue3/src/api/service/serviceDistribution.ts b/ui-vue3/src/api/service/serviceDistribution.ts
new file mode 100644
index 0000000..61dbb52
--- /dev/null
+++ b/ui-vue3/src/api/service/serviceDistribution.ts
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+import request from '@/base/http/request'
+
+export const getServiceDistribution = (params: any): Promise<any> => {
+  return request({
+    url: '/service/distribution',
+    method: 'get',
+    params
+  })
+}
diff --git a/ui-vue3/src/views/resources/services/search.vue b/ui-vue3/src/views/resources/services/search.vue
index 9d36ebf..6c3b005 100644
--- a/ui-vue3/src/views/resources/services/search.vue
+++ b/ui-vue3/src/views/resources/services/search.vue
@@ -16,112 +16,86 @@
 -->
 <template>
   <div class="__container_services_index">
-    <a-flex vertical>
-      <a-flex class="service-filter">
-        <a-input-search
-          v-model:value="serviceName"
-          placeholder="请输入"
-          class="service-name-input"
-          @search="debounceSearch"
-          enter-button
-        />
-      </a-flex>
-      <a-table
-        :columns="columns"
-        :data-source="dataSource"
-        :pagination="pagination"
-        :scroll="{ y: '55vh' }"
-      >
-        <template #bodyCell="{ column, text }">
+    <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>
           </template>
         </template>
-      </a-table>
-    </a-flex>
+    </search-table>
   </div>
 </template>
 
 <script setup lang="ts">
 import { useRouter } from 'vue-router'
-import type { ComponentInternalInstance } from 'vue'
-import { ref, getCurrentInstance } from 'vue'
+import { reactive, provide } from 'vue'
 import { searchService } from '@/api/service/service.ts'
-import { debounce } from 'lodash'
-
-const {
-  appContext: {
-    config: { globalProperties }
-  }
-} = <ComponentInternalInstance>getCurrentInstance()
-
-const serviceName = ref('')
+import { SearchDomain } from '@/utils/SearchUtil'
+import SearchTable from '@/components/SearchTable.vue'
+import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
 
 const router = useRouter()
 const columns = [
   {
     title: '服务',
     dataIndex: 'serviceName',
+    key:  'serviceName',
     sorter: true,
     width: '30%'
   },
   {
     title: '接口数',
     dataIndex: 'interfaceNum',
+    key: 'interfaceNum',
     sorter: true,
     width: '10%'
   },
   {
     title: '近 1min QPS',
     dataIndex: 'avgQPS',
+    key: 'avgQPS',
     sorter: true,
     width: '15%'
   },
   {
     title: '近 1min RT',
     dataIndex: 'avgRT',
+    key: 'avgRT',
     sorter: true,
     width: '15%'
   },
   {
     title: '近 1min 请求总量',
     dataIndex: 'requestTotal',
+    key: 'requestTotal',
     sorter: true,
     width: '15%'
   }
 ]
 
-const dataSource = ref([])
+const searchDomain = reactive(
+  new SearchDomain(
+    [
+      {
+        label: '服务名',
+        param: 'serviceName',
+        placeholder: '请输入',
+        style: {
+          width: '200px'
+        }
+      }
+    ],
+    searchService,
+    columns
+  )
+)
 
-const onSearch = async () => {
-  let { data } = await searchService({})
-  dataSource.value = data.data
-}
-
-onSearch()
-
-const debounceSearch = debounce(onSearch, 300)
+searchDomain.onSearch()
 
 const viewDetail = (serviceName: string) => {
   router.push({ name: 'detail', params: { serviceName } })
 }
 
-const pagination = {
-  showTotal: (v: any) =>
-    globalProperties.$t('searchDomain.total') +
-    ': ' +
-    v +
-    ' ' +
-    globalProperties.$t('searchDomain.unit')
-}
+provide(PROVIDE_INJECT_KEY.SEARCH_DOMAIN, searchDomain)
 </script>
-<style lang="less" scoped>
-.__container_services_index {
-  .service-filter {
-    margin-bottom: 20px;
-    .service-name-input {
-      width: 500px;
-    }
-  }
-}
-</style>
+<style lang="less" scoped></style>
diff --git a/ui-vue3/src/views/resources/services/tabs/distribution.vue b/ui-vue3/src/views/resources/services/tabs/distribution.vue
index 9a402e6..9dc2901 100644
--- a/ui-vue3/src/views/resources/services/tabs/distribution.vue
+++ b/ui-vue3/src/views/resources/services/tabs/distribution.vue
@@ -25,18 +25,19 @@
               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="() => {}"
+            @search="debounceSearch"
             enter-button
           />
         </a-flex>
         <div>
-          <a-radio-group v-model:value="type" button-style="solid">
+          <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>
@@ -67,6 +68,8 @@
 <script setup lang="ts">
 import type { ComponentInternalInstance } from 'vue'
 import { ref, reactive, getCurrentInstance } from 'vue'
+import { getServiceDistribution } from '@/api/service/serviceDistribution'
+import { debounce } from 'lodash'
 
 const {
   appContext: {
@@ -116,73 +119,15 @@
   }
 ]
 
-const tableData = reactive([
-  {
-    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'
-    ]
-  },
-  {
-    applicationName: 'shop-order',
-    instanceNum: 15,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
-  },
-  {
-    applicationName: 'shop-user',
-    instanceNum: 12,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
-  },
-  {
-    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'
-    ]
-  },
-  {
-    applicationName: 'shop-order',
-    instanceNum: 15,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
-  },
-  {
-    applicationName: 'shop-user',
-    instanceNum: 12,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
-  },
-  {
-    applicationName: 'shop-order',
-    instanceNum: 15,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
-  },
-  {
-    applicationName: 'shop-user',
-    instanceNum: 12,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
-  },
-  {
-    applicationName: 'shop-user',
-    instanceNum: 12,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
-  },
-  {
-    applicationName: 'shop-order',
-    instanceNum: 15,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.26:20880', '192.168.32.24:28080']
-  },
-  {
-    applicationName: 'shop-user',
-    instanceNum: 12,
-    instanceIP: ['192.168.32.28:8697', '192.168.32.24:28080']
-  }
-])
+const tableData = ref([])
+
+const onSearch = async () => {
+  let { data } = await getServiceDistribution({});
+  tableData.value = data.data
+}
+onSearch()
+
+const debounceSearch = debounce(onSearch, 300)
 
 const pagination = {
   showTotal: (v: any) =>