|
|
@@ -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>
|