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

- 添加keep-alive缓存VMList组件,从详情页返回不重新请求API
- 只有点击刷新按钮才重新获取虚拟机列表
- 新增模糊搜索框,支持按名称/IP/MAC搜索
- 搜索时显示匹配数量统计
This commit is contained in:
admin
2026-05-14 12:14:30 +08:00
parent 96a26a3ae6
commit da741e7593
2 changed files with 43 additions and 6 deletions
+5 -1
View File
@@ -57,7 +57,11 @@
<!-- 页面内容 --> <!-- 页面内容 -->
<div class="content"> <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> </div>
</div> </div>
+38 -5
View File
@@ -6,17 +6,24 @@
<el-option v-for="h in hosts" :key="h.id" :label="h.name" :value="h.id" /> <el-option v-for="h in hosts" :key="h.id" :label="h.name" :value="h.id" />
<el-option label="所有主机虚拟机" value="all" /> <el-option label="所有主机虚拟机" value="all" />
</el-select> </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-button type="primary" @click="showCreateDialog = true">
<el-icon><Plus /></el-icon> 创建虚拟机 <el-icon><Plus /></el-icon> 创建虚拟机
</el-button> </el-button>
<el-button @click="loadData"> <el-button @click="loadData" :loading="loading">
<el-icon><Refresh /></el-icon> 刷新 <el-icon><Refresh /></el-icon> 刷新
</el-button> </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> </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"> <el-table-column type="expand">
<template #default="{ row }"> <template #default="{ row }">
<div class="expand-content"> <div class="expand-content">
@@ -158,12 +165,14 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, computed, onMounted, onActivated } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' 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' import api from '../api'
defineOptions({ name: 'VMList' })
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const hostId = () => route.query.host_id || 'local' const hostId = () => route.query.host_id || 'local'
@@ -178,6 +187,8 @@ const showCreateDialog = ref(false)
const poolOptions = ref([]) const poolOptions = ref([])
const networkOptions = ref([]) const networkOptions = ref([])
const isoOptions = ref([]) const isoOptions = ref([])
const searchText = ref('')
const firstLoad = ref(true)
const createForm = ref({ const createForm = ref({
host_id: 'local', host_id: 'local',
@@ -205,6 +216,20 @@ function formatMem(mb) {
return mb + ' 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() { async function loadData() {
loading.value = true loading.value = true
try { try {
@@ -305,6 +330,14 @@ onMounted(async () => {
loadData() loadData()
loadOptions() loadOptions()
}) })
// keep-alive 激活时不重新加载,只有点刷新按钮才重新请求
onActivated(() => {
if (firstLoad.value) {
firstLoad.value = false
}
// 从详情页返回时使用缓存数据,不重新请求
})
</script> </script>
<style scoped> <style scoped>