Quellcode durchsuchen

feat: 虚拟机列表缓存+模糊搜索

- 添加keep-alive缓存VMList组件,从详情页返回不重新请求API
- 只有点击刷新按钮才重新获取虚拟机列表
- 新增模糊搜索框,支持按名称/IP/MAC搜索
- 搜索时显示匹配数量统计
admin vor 1 Woche
Ursprung
Commit
da741e7593
2 geänderte Dateien mit 43 neuen und 6 gelöschten Zeilen
  1. 5 1
      frontend/src/layout/MainLayout.vue
  2. 38 5
      frontend/src/views/VMList.vue

+ 5 - 1
frontend/src/layout/MainLayout.vue

@@ -57,7 +57,11 @@
 
       <!-- 页面内容 -->
       <div class="content">
-        <router-view />
+        <router-view v-slot="{ Component, route: viewRoute }">
+          <keep-alive :include="['VMList']">
+            <component :is="Component" :key="viewRoute.fullPath" />
+          </keep-alive>
+        </router-view>
       </div>
     </div>
   </div>

+ 38 - 5
frontend/src/views/VMList.vue

@@ -6,17 +6,24 @@
         <el-option v-for="h in hosts" :key="h.id" :label="h.name" :value="h.id" />
         <el-option label="所有主机虚拟机" value="all" />
       </el-select>
+      <el-input
+        v-model="searchText"
+        placeholder="搜索虚拟机名称/IP/MAC..."
+        clearable
+        style="width: 260px;"
+        :prefix-icon="Search"
+      />
       <el-button type="primary" @click="showCreateDialog = true">
         <el-icon><Plus /></el-icon> 创建虚拟机
       </el-button>
-      <el-button @click="loadData">
+      <el-button @click="loadData" :loading="loading">
         <el-icon><Refresh /></el-icon> 刷新
       </el-button>
-      <span v-if="vmTotal" style="margin-left: 16px; color: #7a8fa3;">共 {{ vmTotal }} 台虚拟机</span>
+      <span v-if="vmTotal" style="margin-left: 16px; color: #7a8fa3;">共 {{ vmTotal }} 台虚拟机<span v-if="searchText.trim()">,匹配 {{ filteredVms.length }} 台</span></span>
     </div>
 
     <!-- 虚拟机列表 -->
-    <el-table :data="vms" stripe style="width: 100%" v-loading="loading">
+    <el-table :data="filteredVms" stripe style="width: 100%" v-loading="loading">
       <el-table-column type="expand">
         <template #default="{ row }">
           <div class="expand-content">
@@ -158,12 +165,14 @@
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
+import { ref, computed, onMounted, onActivated } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Plus, Refresh } from '@element-plus/icons-vue'
+import { Plus, Refresh, Search } from '@element-plus/icons-vue'
 import api from '../api'
 
+defineOptions({ name: 'VMList' })
+
 const route = useRoute()
 const router = useRouter()
 const hostId = () => route.query.host_id || 'local'
@@ -178,6 +187,8 @@ const showCreateDialog = ref(false)
 const poolOptions = ref([])
 const networkOptions = ref([])
 const isoOptions = ref([])
+const searchText = ref('')
+const firstLoad = ref(true)
 
 const createForm = ref({
   host_id: 'local',
@@ -205,6 +216,20 @@ function formatMem(mb) {
   return mb + ' MB'
 }
 
+// 模糊搜索过滤
+const filteredVms = computed(() => {
+  if (!searchText.value.trim()) return vms.value
+  const kw = searchText.value.trim().toLowerCase()
+  return vms.value.filter(vm => {
+    // 搜索名称、IP、MAC、宿主机
+    if (vm.name?.toLowerCase().includes(kw)) return true
+    if (vm.host_name?.toLowerCase().includes(kw)) return true
+    if (vm.host_id?.toLowerCase().includes(kw)) return true
+    if (vm.interfaces?.some(i => i.ip?.includes(kw) || i.mac?.toLowerCase().includes(kw))) return true
+    return false
+  })
+})
+
 async function loadData() {
   loading.value = true
   try {
@@ -305,6 +330,14 @@ onMounted(async () => {
   loadData()
   loadOptions()
 })
+
+// keep-alive 激活时不重新加载,只有点刷新按钮才重新请求
+onActivated(() => {
+  if (firstLoad.value) {
+    firstLoad.value = false
+  }
+  // 从详情页返回时使用缓存数据,不重新请求
+})
 </script>
 
 <style scoped>