fix: 虚拟机模块
Build and Deploy Vue3 / build (push) Successful in 1m58s
Build and Deploy Vue3 / deploy (push) Successful in 1m13s

This commit is contained in:
2026-04-15 16:35:41 +08:00
parent cf188bb94a
commit 7652b290b0
+119 -7
View File
@@ -138,7 +138,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="宿主机" v-if="hostMode === 'host'">
<el-select v-model="createForm.host_id" placeholder="选择宿主机" filterable style="width: 100%" @change="(v) => loadNetworkOptions(v)">
<el-select v-model="createForm.host_id" placeholder="选择宿主机" filterable style="width: 100%" @change="onHostChange">
<el-option v-for="h in hostOptions" :key="h.id" :label="`${h.name} (${h.ip || h.id})`" :value="h.id" />
</el-select>
</el-form-item>
@@ -203,10 +203,16 @@
</el-form-item>
</div>
<el-form-item label="网络IP列表" v-if="ipMode === 'ids'">
<el-select v-model="createForm.network_ids" multiple filterable placeholder="选择可用网络IP" style="width: 100%">
<el-option v-for="n in networkOptions" :key="n.id" :label="`${n.name || ''} - ${n.address || n.ip || ''}`" :value="n.id" />
</el-select>
<div class="form-tip" v-if="!networkOptions.length">请先选择宿主机以加载可用网络(仅显示未使用的网络)</div>
<div style="display:flex;align-items:center;gap:8px;width:100%">
<el-button :disabled="!createForm.host_id" @click="showCreateNetSelector = true">选择网络</el-button>
<span v-if="createNetSelected.length" style="font-size:13px;color:#606266">已选 {{ createNetSelected.length }} 个</span>
<span v-else style="font-size:13px;color:#909399">{{ createForm.host_id ? '未选择' : '请先选择宿主机' }}</span>
</div>
<div v-if="createNetSelected.length" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:4px">
<el-tag v-for="n in createNetSelected" :key="n.id" closable size="small" @close="removeCreateNet(n.id)">
{{ n.name || n.bridge_name || n.id }} - {{ n.address || '' }} (ID:{{ n.id }})
</el-tag>
</div>
</el-form-item>
</div>
</el-form>
@@ -218,6 +224,48 @@
</template>
</el-dialog>
<NetworkSelectorPopup ref="createNetSelectorRef" v-model="showCreateNetSelector"
:service-id="serviceId" :host-id="createForm.host_id || 0"
filter-type="bridge" filter-used="false" :multiple="true"
@confirm="handleCreateNetConfirm" @create="handleCreateNetCreate" />
<!-- 创建网络弹窗 -->
<el-dialog v-model="netCreateVisible" title="创建网络" width="600px" destroy-on-close append-to-body class="tk-dialog">
<el-form ref="netCreateFormRef" :model="netCreateForm" :rules="netCreateRules" label-width="100px">
<div class="tk-section">
<div class="tk-section-title">基本信息</div>
<el-form-item label="名称" prop="name"><el-input v-model="netCreateForm.name" placeholder="网络名称" /></el-form-item>
<el-form-item label="网络类型" prop="type">
<el-select v-model="netCreateForm.type" style="width: 100%">
<el-option label="网桥(Bridge/外网)" value="bridge" />
<el-option label="内网(NAT)" value="nat" />
</el-select>
</el-form-item>
<el-form-item label="IP" prop="address">
<div style="display:flex;align-items:center;gap:8px;width:100%">
<el-input v-model="netCreateForm.address" placeholder="例如 192.168.1.0/24" />
<span style="color:#909399;white-space:nowrap">CIDR</span>
</div>
</el-form-item>
<el-form-item label="网关地址" prop="gateway"><el-input v-model="netCreateForm.gateway" placeholder="例如 192.168.1.1" /></el-form-item>
<el-form-item label="DNS 服务器"><el-input v-model="netCreateForm.nameservers" placeholder="默认 114.114.114.114,8.8.8.8" /></el-form-item>
</div>
<div class="tk-section">
<div class="tk-section-title">高级配置</div>
<el-form-item label="MAC 地址"><el-input v-model="netCreateForm.mac_address" placeholder="不填则随机" /></el-form-item>
<el-form-item label="虚拟网桥名"><el-input v-model="netCreateForm.bridge_name" placeholder="不填使用默认" /></el-form-item>
<el-form-item label="逻辑网桥名"><el-input v-model="netCreateForm.ls_bridge_name" placeholder="不填使用默认" /></el-form-item>
<el-form-item label="逻辑端口名"><el-input v-model="netCreateForm.ls_name" placeholder="不填使用默认" /></el-form-item>
</div>
</el-form>
<template #footer>
<div style="display:flex;justify-content:flex-end;gap:8px">
<el-button @click="netCreateVisible = false">取消</el-button>
<el-button type="primary" :loading="netCreateLoading" @click="submitNetCreate">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 重装弹窗 -->
<el-dialog v-model="rebuildDialogVisible" title="重装虚拟机" width="480px" destroy-on-close class="tk-dialog">
<el-form label-width="100px">
@@ -368,20 +416,21 @@
</template>
<script setup>
import { ref, reactive, computed, inject, onMounted, onBeforeUnmount } from 'vue'
import { ref, reactive, computed, inject, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search, ArrowLeft, ArrowDown, WarningFilled } from '@element-plus/icons-vue'
import {
getRemoteHostList, getVmList, getVmDetail, getVmStatus,
createVm, rebuildVm, startVm, stopVm, rebootVm, suspendVm,
resumeVm, rescueVm, exitRescueVm, deleteVm, getNetworkList, getMetricsHistory,
resumeVm, rescueVm, exitRescueVm, deleteVm, getNetworkList, createNetwork, getMetricsHistory,
getDataMigrateProgress
} from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
import ImageSelectorPopup from '@/components/admin/ImageSelectorPopup.vue'
import HostGroupSelectorPopup from '@/components/admin/HostGroupSelectorPopup.vue'
import UserListSelector from '@/components/admin/UserListSelector.vue'
import NetworkSelectorPopup from '@/components/admin/NetworkSelectorPopup.vue'
const route = useRoute()
const router = useRouter()
@@ -420,6 +469,9 @@ const showUserSelector = ref(false)
const hostMode = ref('host')
const ipMode = ref('num')
const networkOptions = ref([])
const showCreateNetSelector = ref(false)
const createNetSelectorRef = ref(null)
const createNetSelected = ref([])
// 内存单位: API传输单位为 bytes
const memoryUnitOptions = [
@@ -462,6 +514,65 @@ const loadNetworkOptions = async (hid) => {
} catch { networkOptions.value = [] }
}
const onHostChange = (v) => {
createNetSelected.value = []
createForm.network_ids = []
loadNetworkOptions(v)
}
const handleCreateNetConfirm = (items) => {
const arr = Array.isArray(items) ? items : [items]
const existIds = new Set(createNetSelected.value.map(n => n.id))
arr.forEach(n => { if (!existIds.has(n.id)) createNetSelected.value.push(n) })
createForm.network_ids = createNetSelected.value.map(n => n.id)
}
const removeCreateNet = (id) => {
createNetSelected.value = createNetSelected.value.filter(n => n.id !== id)
createForm.network_ids = createNetSelected.value.map(n => n.id)
}
const netCreateVisible = ref(false)
const netCreateLoading = ref(false)
const netCreateFormRef = ref(null)
const netCreateForm = reactive({ name: '', address: '', gateway: '', nameservers: '', type: 'bridge', mac_address: '', bridge_name: '', ls_bridge_name: '', ls_name: '' })
const netCreateRules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
address: [{ required: true, message: '请输入 CIDR 格式网段', trigger: 'blur' }],
gateway: [{ required: true, message: '请输入网关', trigger: 'blur' }],
type: [{ required: true, message: '请选择类型', trigger: 'change' }]
}
const handleCreateNetCreate = () => {
Object.assign(netCreateForm, { name: '', address: '', gateway: '', nameservers: '', type: 'bridge', mac_address: '', bridge_name: '', ls_bridge_name: '', ls_name: '' })
netCreateVisible.value = true
}
const submitNetCreate = () => {
netCreateFormRef.value?.validate(async (valid) => {
if (!valid) return
netCreateLoading.value = true
try {
const fd = new FormData()
fd.append('service_id', serviceId.value)
fd.append('name', netCreateForm.name)
fd.append('address', netCreateForm.address)
fd.append('gateway', netCreateForm.gateway)
fd.append('type', netCreateForm.type)
fd.append('host_id', createForm.host_id)
if (netCreateForm.nameservers) fd.append('nameservers', netCreateForm.nameservers)
if (netCreateForm.mac_address) fd.append('mac_address', netCreateForm.mac_address)
if (netCreateForm.bridge_name) fd.append('bridge_name', netCreateForm.bridge_name)
if (netCreateForm.ls_bridge_name) fd.append('ls_bridge_name', netCreateForm.ls_bridge_name)
if (netCreateForm.ls_name) fd.append('ls_name', netCreateForm.ls_name)
const res = await createNetwork(fd)
if (res?.data?.code === 200) {
ElMessage.success('创建成功')
netCreateVisible.value = false
nextTick(() => createNetSelectorRef.value?.loadList())
} else ElMessage.error(extractApiError(res?.data, '创建失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { netCreateLoading.value = false }
})
}
const getHostLabel = (hid) => {
const h = hostOptions.value.find(x => x.id === hid)
return h ? `${h.name}` : (hid || '-')
@@ -648,6 +759,7 @@ const handleAdd = () => {
hostMode.value = 'host'
ipMode.value = 'num'
networkOptions.value = []
createNetSelected.value = []
loadHostOptions()
createDialogVisible.value = true
if (injectedHostId?.value) {