feat: 虚拟机列表缓存+模糊搜索
- 添加keep-alive缓存VMList组件,从详情页返回不重新请求API - 只有点击刷新按钮才重新获取虚拟机列表 - 新增模糊搜索框,支持按名称/IP/MAC搜索 - 搜索时显示匹配数量统计
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user