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
+143 -3
View File
@@ -434,6 +434,27 @@
</div>
</el-tab-pane>
<!-- 流量策略 -->
<el-tab-pane v-if="isVmGoods" label="流量策略" name="trafficPolicy">
<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="openTrafficPolicyDialog">修改流量策略</el-button>
<el-button size="small" type="success" @click="openAddTrafficDialog('fixed')">增加固定流量</el-button>
<el-button size="small" type="warning" @click="openAddTrafficDialog('temporary')">增加临时流量</el-button>
<el-button size="small" :icon="Refresh" @click="loadTrafficPolicy" :loading="trafficPolicyLoading">刷新</el-button>
</div>
</div>
<el-descriptions v-if="trafficPolicy" :column="3" border size="small" style="margin-top:12px">
<el-descriptions-item label="流量上限">{{ trafficPolicy.traffic_max_mb != null ? (trafficPolicy.traffic_max_mb === 0 ? '不限' : trafficPolicy.traffic_max_mb + ' MB') : '-' }}</el-descriptions-item>
<el-descriptions-item label="耗尽下行限速">{{ trafficPolicy.exhausted_rx_mbps != null ? (trafficPolicy.exhausted_rx_mbps === 0 ? '不限' : trafficPolicy.exhausted_rx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
<el-descriptions-item label="耗尽上行限速">{{ trafficPolicy.exhausted_tx_mbps != null ? (trafficPolicy.exhausted_tx_mbps === 0 ? '不限' : trafficPolicy.exhausted_tx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
</el-descriptions>
<el-empty v-else-if="!trafficPolicyLoading" description="暂无流量策略数据" :image-size="60" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
@@ -647,6 +668,18 @@
</el-select>
</div>
</el-form-item>
<el-form-item label="耗尽下行限速">
<div class="unit-input-row">
<el-input-number v-model="trafficForm.traffic_exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
<span class="unit-text" style="margin-left:8px">Mbps0 不限)</span>
</div>
</el-form-item>
<el-form-item label="耗尽上行限速">
<div class="unit-input-row">
<el-input-number v-model="trafficForm.traffic_exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
<span class="unit-text" style="margin-left:8px">Mbps0 不限)</span>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="trafficVisible = false">取消</el-button>
@@ -654,6 +687,50 @@
</template>
</el-dialog>
<!-- 流量策略管理 -->
<el-dialog v-model="trafficPolicyVisible" title="修改流量策略" width="440px" destroy-on-close>
<el-form :model="trafficPolicyForm" label-width="120px">
<el-form-item label="流量上限">
<div class="unit-input-row">
<el-input-number v-model="trafficPolicyForm.traffic_max_mb" :min="0" controls-position="right" style="flex:1" />
<span class="unit-text" style="margin-left:8px">MB0 不限)</span>
</div>
</el-form-item>
<el-form-item label="耗尽下行限速">
<div class="unit-input-row">
<el-input-number v-model="trafficPolicyForm.exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
<span class="unit-text" style="margin-left:8px">Mbps0 不限)</span>
</div>
</el-form-item>
<el-form-item label="耗尽上行限速">
<div class="unit-input-row">
<el-input-number v-model="trafficPolicyForm.exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
<span class="unit-text" style="margin-left:8px">Mbps0 不限)</span>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="trafficPolicyVisible = false">取消</el-button>
<el-button type="primary" :loading="trafficPolicyLoading" @click="submitUpdateTrafficPolicy">确定</el-button>
</template>
</el-dialog>
<!-- 增加固定/临时流量 -->
<el-dialog v-model="addTrafficVisible" :title="addTrafficType === 'fixed' ? '增加固定流量上限' : '增加一次性临时流量'" width="400px" destroy-on-close>
<el-form :model="addTrafficForm" label-width="110px">
<el-form-item label="流量数量">
<div class="unit-input-row">
<el-input-number v-model="addTrafficForm.traffic_mb" :min="1" controls-position="right" style="flex:1" />
<span class="unit-text" style="margin-left:8px">MB</span>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addTrafficVisible = false">取消</el-button>
<el-button type="primary" :loading="trafficPolicyLoading" @click="submitAddTraffic">确定</el-button>
</template>
</el-dialog>
<!-- 转移用户 -->
<el-dialog v-model="transferVisible" title="转移虚拟机" width="440px" destroy-on-close>
<el-form label-width="100px">
@@ -1008,7 +1085,8 @@ import {
getUserVmPostGroupDetail,
getUserVmNetworkList, getUserVmNetworkingList, createUserVmNetworking, assignUserVmNetworking, removeUserVmNetworkingNetwork, deleteUserVmNetworking,
getUserGoodsDetail,
getUserVmMetricsHistory
getUserVmMetricsHistory,
getUserVmTrafficPolicy, updateUserVmTrafficPolicy, addUserVmFixedTraffic, addUserVmTemporaryTraffic
} from '@/api/admin/userVm'
import { extractApiError } from '@/utils/kvmErrorUtil'
import { vmStatusLabel as vmStatusLabelUtil, vmStatusType as vmStatusTypeUtil, volumeStatusLabel, volumeStatusType } from '@/utils/tool'
@@ -1184,6 +1262,7 @@ const handleTabChange = (tab) => {
if (tab === 'security') loadSgLockInfo()
if (tab === 'networking') loadNetworkings()
if (tab === 'monitor' && !metricsData.value) loadMetricsHistory()
if (tab === 'trafficPolicy') loadTrafficPolicy()
}
// 请求安全组详情补充 lock 字段(使用用户虚拟机安全组详情接口)
@@ -1234,7 +1313,7 @@ const submitPower = async () => {
const handleMoreCmd = (cmd) => {
if (powerLabels[cmd]) { handlePower(cmd); return }
if (cmd === 'rebuild') { rebuildImageId.value = 0; rebuildImageName.value = ''; rebuildImages.value = []; rebuildVisible.value = true; loadRebuildImages() }
if (cmd === 'updateTraffic') { Object.assign(trafficForm, { rx_bandwidth: vm.value?.rx_bandwidth || 0, tx_bandwidth: vm.value?.tx_bandwidth || 0, _trafficValue: +((vm.value?.traffic_max || 0) / 1024).toFixed(2), _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB' }); trafficVisible.value = true }
if (cmd === 'updateTraffic') { Object.assign(trafficForm, { rx_bandwidth: vm.value?.rx_bandwidth || 0, tx_bandwidth: vm.value?.tx_bandwidth || 0, _trafficValue: +((vm.value?.traffic_max || 0) / 1024).toFixed(2), _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB', traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 }); trafficVisible.value = true }
if (cmd === 'transfer') { Object.assign(transferForm, { target_user_id: 0, _userName: '' }); transferVisible.value = true }
if (cmd === 'updateVm') openEditVm()
if (cmd === 'refactorVm') openRefactorVm()
@@ -1897,7 +1976,7 @@ const submitEditVm = async () => {
// ---- 修改带宽 ----
const trafficVisible = ref(false)
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficValue: 0, _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB' })
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficValue: 0, _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB', traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 })
const submitUpdateTraffic = async () => {
actionLoading.value = true
try {
@@ -1912,12 +1991,73 @@ const submitUpdateTraffic = async () => {
tx_bandwidth: Math.round(txBw),
traffic_max: Math.round(trafficMb)
}
if (trafficForm.traffic_exhausted_rx_mbps > 0) payload.traffic_exhausted_rx_mbps = trafficForm.traffic_exhausted_rx_mbps
if (trafficForm.traffic_exhausted_tx_mbps > 0) payload.traffic_exhausted_tx_mbps = trafficForm.traffic_exhausted_tx_mbps
const res = await updateUserVmTraffic(payload)
if (res?.data?.code === 200) { ElMessage.success('修改成功'); trafficVisible.value = false; loadDetail() }
else ElMessage.error(extractApiError(res?.data, '修改失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
}
// ---- 流量策略 ----
// 测试未通过(接口新增,待联调)
const trafficPolicy = ref(null)
const trafficPolicyLoading = ref(false)
const trafficPolicyVisible = ref(false)
const addTrafficVisible = ref(false)
const addTrafficType = ref('fixed')
const trafficPolicyForm = reactive({ traffic_max_mb: 0, exhausted_rx_mbps: 0, exhausted_tx_mbps: 0 })
const addTrafficForm = reactive({ traffic_mb: 1 })
const loadTrafficPolicy = async () => {
if (!userGoodsId.value) return
trafficPolicyLoading.value = true
try {
const res = await getUserVmTrafficPolicy({ user_goods_id: userGoodsId.value })
if (res?.data?.code === 200) trafficPolicy.value = res.data.data
} catch { /* ignore */ } finally { trafficPolicyLoading.value = false }
}
const openTrafficPolicyDialog = () => {
Object.assign(trafficPolicyForm, {
traffic_max_mb: trafficPolicy.value?.traffic_max_mb || 0,
exhausted_rx_mbps: trafficPolicy.value?.exhausted_rx_mbps || 0,
exhausted_tx_mbps: trafficPolicy.value?.exhausted_tx_mbps || 0
})
trafficPolicyVisible.value = true
}
const submitUpdateTrafficPolicy = async () => {
trafficPolicyLoading.value = true
try {
const res = await updateUserVmTrafficPolicy({
user_goods_id: userGoodsId.value,
traffic_max_mb: trafficPolicyForm.traffic_max_mb,
exhausted_rx_mbps: trafficPolicyForm.exhausted_rx_mbps,
exhausted_tx_mbps: trafficPolicyForm.exhausted_tx_mbps
})
if (res?.data?.code === 200) { ElMessage.success('修改成功'); trafficPolicyVisible.value = false; loadTrafficPolicy() }
else ElMessage.error(extractApiError(res?.data, '修改失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { trafficPolicyLoading.value = false }
}
const openAddTrafficDialog = (type) => {
addTrafficType.value = type
addTrafficForm.traffic_mb = 1
addTrafficVisible.value = true
}
const submitAddTraffic = async () => {
if (!addTrafficForm.traffic_mb || addTrafficForm.traffic_mb < 1) { ElMessage.warning('请输入有效的流量数量'); return }
trafficPolicyLoading.value = true
try {
const apiFn = addTrafficType.value === 'fixed' ? addUserVmFixedTraffic : addUserVmTemporaryTraffic
const res = await apiFn({ user_goods_id: userGoodsId.value, traffic_mb: addTrafficForm.traffic_mb })
if (res?.data?.code === 200) { ElMessage.success('操作成功'); addTrafficVisible.value = false; loadTrafficPolicy() }
else ElMessage.error(extractApiError(res?.data, '操作失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { trafficPolicyLoading.value = false }
}
// ---- 转移 ----
const transferVisible = ref(false)
const showTransferUserSelector = ref(false)