feat: 虚拟机流量精细化控制接入(接口新增,待联调)
Build and Deploy Vue3 / build (push) Successful in 1m37s
Build and Deploy Vue3 / deploy (push) Successful in 1m16s

1. userVm.js/kvmService.js 新增 traffic_policy 系列 API(GET/update/add_fixed/add_temporary)
2. UserVmList.vue/VmManage.vue 创建表单新增 traffic_max、traffic_exhausted_rx/tx_mbps 三个可选字段
3. UserVmDetail.vue/VmDetail.vue 修改带宽表单新增耗尽限速字段,并各增加流量策略 Tab(展示+修改策略+增加固定/临时流量)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shiran
2026-05-08 15:10:44 +08:00
parent 475c62aefc
commit c43d1978a8
6 changed files with 366 additions and 9 deletions
+149 -3
View File
@@ -641,6 +641,28 @@
<el-empty v-else-if="!historicalMetricsLoading" description="加载监控数据中..." :image-size="80" />
</div>
</el-tab-pane>
<!-- 流量策略 -->
<el-tab-pane label="流量策略" name="vmTrafficPolicy">
<div class="section-block">
<div class="section-header">
<h3 class="section-title">流量策略</h3>
<div style="display:flex;gap:8px">
<el-button size="small" type="primary" @click="openVmTrafficPolicyDialog">修改流量策略</el-button>
<el-button size="small" type="success" @click="openVmAddTrafficDialog('fixed')">增加固定流量</el-button>
<el-button size="small" type="warning" @click="openVmAddTrafficDialog('temporary')">增加临时流量</el-button>
<el-button size="small" :icon="Refresh" @click="loadVmTrafficPolicy" :loading="vmTrafficPolicyLoading">刷新</el-button>
</div>
</div>
<el-descriptions v-if="vmTrafficPolicy" :column="3" border size="small" style="margin-top:12px">
<el-descriptions-item label="流量上限">{{ vmTrafficPolicy.traffic_max_mb != null ? (vmTrafficPolicy.traffic_max_mb === 0 ? '不限' : vmTrafficPolicy.traffic_max_mb + ' MB') : '-' }}</el-descriptions-item>
<el-descriptions-item label="耗尽下行限速">{{ vmTrafficPolicy.exhausted_rx_mbps != null ? (vmTrafficPolicy.exhausted_rx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_rx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
<el-descriptions-item label="耗尽上行限速">{{ vmTrafficPolicy.exhausted_tx_mbps != null ? (vmTrafficPolicy.exhausted_tx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_tx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
</el-descriptions>
<el-empty v-else-if="!vmTrafficPolicyLoading" description="暂无流量策略数据" :image-size="60" />
</div>
</el-tab-pane>
</el-tabs>
</div>
@@ -958,6 +980,16 @@
<el-input-number v-model="trafficForm._trafficGB" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">GB</span>
</div>
</el-form-item>
<el-form-item label="耗尽下行限速">
<div class="tk-inline-unit">
<el-input-number v-model="trafficForm.traffic_exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps0 不限)</span>
</div>
</el-form-item>
<el-form-item label="耗尽上行限速">
<div class="tk-inline-unit">
<el-input-number v-model="trafficForm.traffic_exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps0 不限)</span>
</div>
</el-form-item>
</div>
</el-form>
<template #footer>
@@ -968,6 +1000,50 @@
</template>
</el-dialog>
<!-- 流量策略管理弹窗 -->
<el-dialog v-model="vmTrafficPolicyVisible" title="修改流量策略" width="480px" destroy-on-close class="tk-dialog">
<el-form :model="vmTrafficPolicyForm" label-width="120px">
<el-form-item label="流量上限">
<div class="tk-inline-unit">
<el-input-number v-model="vmTrafficPolicyForm.traffic_max_mb" :min="0" controls-position="right" /><span class="tk-res-unit">MB0 不限)</span>
</div>
</el-form-item>
<el-form-item label="耗尽下行限速">
<div class="tk-inline-unit">
<el-input-number v-model="vmTrafficPolicyForm.exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps0 不限)</span>
</div>
</el-form-item>
<el-form-item label="耗尽上行限速">
<div class="tk-inline-unit">
<el-input-number v-model="vmTrafficPolicyForm.exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps0 不限)</span>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="tk-dialog-footer">
<el-button @click="vmTrafficPolicyVisible = false">取消</el-button>
<el-button type="primary" :loading="vmTrafficPolicyLoading" @click="submitUpdateVmTrafficPolicy">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 增加固定/临时流量弹窗 -->
<el-dialog v-model="vmAddTrafficVisible" :title="vmAddTrafficType === 'fixed' ? '增加固定流量上限' : '增加一次性临时流量'" width="400px" destroy-on-close class="tk-dialog">
<el-form :model="vmAddTrafficForm" label-width="110px">
<el-form-item label="流量数量">
<div class="tk-inline-unit">
<el-input-number v-model="vmAddTrafficForm.traffic_mb" :min="1" controls-position="right" /><span class="tk-res-unit">MB</span>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="tk-dialog-footer">
<el-button @click="vmAddTrafficVisible = false">取消</el-button>
<el-button type="primary" :loading="vmTrafficPolicyLoading" @click="submitAddVmTraffic">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 迁移虚拟机弹窗 -->
<el-dialog v-model="migrateDialogVisible" title="迁移虚拟机更换宿主机" width="580px" destroy-on-close class="tk-dialog">
<el-form label-width="100px" v-loading="migrateOptionsLoading">
@@ -1506,7 +1582,8 @@ import {
getBackupList, createBackup, restoreBackup, deleteBackup, getBackupProgress, getBackupCount, setBackupLimit,
migrateVm, getRemoteHostGroupList, getRemoteHostDetail,
dataMigrateVm, getDataMigrateProgress, abortDataMigrate,
getKvmServiceList, getMetricsHistory, getNetworkList
getKvmServiceList, getMetricsHistory, getNetworkList,
getVmTrafficPolicy, updateVmTrafficPolicy, addVmFixedTraffic, addVmTemporaryTraffic
} from '@/api/admin/kvmService'
import { getUserInfo } from '@/api/admin/user'
import { extractApiError } from '@/utils/kvmErrorUtil'
@@ -2159,14 +2236,16 @@ const submitRefactorVm = async () => {
// ---- 修改带宽 ----
const trafficDialogVisible = ref(false)
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficGB: 0 })
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficGB: 0, traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 })
const handleUpdateTraffic = () => {
if (!detail.value) return
Object.assign(trafficForm, {
rx_bandwidth: detail.value.rx_bandwidth || 0,
tx_bandwidth: detail.value.tx_bandwidth || 0,
_trafficGB: ((detail.value.traffic_max || 0) / 1024).toFixed(2) * 1
_trafficGB: ((detail.value.traffic_max || 0) / 1024).toFixed(2) * 1,
traffic_exhausted_rx_mbps: 0,
traffic_exhausted_tx_mbps: 0
})
trafficDialogVisible.value = true
}
@@ -2180,12 +2259,78 @@ const submitUpdateTraffic = async () => {
fd.append('rx_bandwidth', trafficForm.rx_bandwidth)
fd.append('tx_bandwidth', trafficForm.tx_bandwidth)
if (trafficForm._trafficGB) fd.append('traffic_max', Math.round(trafficForm._trafficGB * 1024)) // GB → Mb
if (trafficForm.traffic_exhausted_rx_mbps > 0) fd.append('traffic_exhausted_rx_mbps', trafficForm.traffic_exhausted_rx_mbps)
if (trafficForm.traffic_exhausted_tx_mbps > 0) fd.append('traffic_exhausted_tx_mbps', trafficForm.traffic_exhausted_tx_mbps)
const res = await updateVmTraffic(fd)
if (res?.data?.code === 200) { ElMessage.success('带宽修改成功'); trafficDialogVisible.value = false; loadDetail() }
else ElMessage.error(extractApiError(res?.data, '修改失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
}
// ---- 流量策略 ----
// 测试未通过(接口新增,待联调)
const vmTrafficPolicy = ref(null)
const vmTrafficPolicyLoading = ref(false)
const vmTrafficPolicyVisible = ref(false)
const vmAddTrafficVisible = ref(false)
const vmAddTrafficType = ref('fixed')
const vmTrafficPolicyForm = reactive({ traffic_max_mb: 0, exhausted_rx_mbps: 0, exhausted_tx_mbps: 0 })
const vmAddTrafficForm = reactive({ traffic_mb: 1 })
const loadVmTrafficPolicy = async () => {
if (!serviceId.value || !vmId.value) return
vmTrafficPolicyLoading.value = true
try {
const res = await getVmTrafficPolicy({ service_id: serviceId.value, vm_id: vmId.value })
if (res?.data?.code === 200) vmTrafficPolicy.value = res.data.data
} catch { /* ignore */ } finally { vmTrafficPolicyLoading.value = false }
}
const openVmTrafficPolicyDialog = () => {
Object.assign(vmTrafficPolicyForm, {
traffic_max_mb: vmTrafficPolicy.value?.traffic_max_mb || 0,
exhausted_rx_mbps: vmTrafficPolicy.value?.exhausted_rx_mbps || 0,
exhausted_tx_mbps: vmTrafficPolicy.value?.exhausted_tx_mbps || 0
})
vmTrafficPolicyVisible.value = true
}
const submitUpdateVmTrafficPolicy = async () => {
vmTrafficPolicyLoading.value = true
try {
const fd = new FormData()
fd.append('service_id', serviceId.value)
fd.append('vm_id', vmId.value)
fd.append('traffic_max_mb', vmTrafficPolicyForm.traffic_max_mb)
fd.append('exhausted_rx_mbps', vmTrafficPolicyForm.exhausted_rx_mbps)
fd.append('exhausted_tx_mbps', vmTrafficPolicyForm.exhausted_tx_mbps)
const res = await updateVmTrafficPolicy(fd)
if (res?.data?.code === 200) { ElMessage.success('修改成功'); vmTrafficPolicyVisible.value = false; loadVmTrafficPolicy() }
else ElMessage.error(extractApiError(res?.data, '修改失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { vmTrafficPolicyLoading.value = false }
}
const openVmAddTrafficDialog = (type) => {
vmAddTrafficType.value = type
vmAddTrafficForm.traffic_mb = 1
vmAddTrafficVisible.value = true
}
const submitAddVmTraffic = async () => {
if (!vmAddTrafficForm.traffic_mb || vmAddTrafficForm.traffic_mb < 1) { ElMessage.warning('请输入有效的流量数量'); return }
vmTrafficPolicyLoading.value = true
try {
const fd = new FormData()
fd.append('service_id', serviceId.value)
fd.append('vm_id', vmId.value)
fd.append('traffic_mb', vmAddTrafficForm.traffic_mb)
const apiFn = vmAddTrafficType.value === 'fixed' ? addVmFixedTraffic : addVmTemporaryTraffic
const res = await apiFn(fd)
if (res?.data?.code === 200) { ElMessage.success('操作成功'); vmAddTrafficVisible.value = false; loadVmTrafficPolicy() }
else ElMessage.error(extractApiError(res?.data, '操作失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { vmTrafficPolicyLoading.value = false }
}
// ---- 迁移虚拟机 ----
const migrateDialogVisible = ref(false)
const migrateOptionsLoading = ref(false)
@@ -3474,6 +3619,7 @@ const triggerTabLoad = (tab) => {
if (tab === 'backup') { loadBackups(); loadBackupQuota() }
if (tab === 'userNetworking') loadVmNetworkingList()
if (tab === 'security') loadSgLockInfo()
if (tab === 'vmTrafficPolicy') loadVmTrafficPolicy()
}
// 请求安全组详情补充 lock 字段