style: 优化布局和交互(Loading/空状态/骨架屏)
Build and Deploy Vue3 / build (push) Successful in 1m51s
Build and Deploy Vue3 / deploy (push) Successful in 1m15s

This commit is contained in:
2026-04-07 16:51:12 +08:00
parent f0e89695f4
commit 2f06aa9f5f
15 changed files with 2216 additions and 1681 deletions
+146 -91
View File
@@ -78,33 +78,36 @@
</div>
<!-- 新建虚拟机弹窗 -->
<el-dialog v-model="createVisible" title="新建用户虚拟机" width="940px" destroy-on-close class="scrollable-dialog">
<el-form ref="createFormRef" :model="createForm" :rules="createRules" label-width="110px" v-loading="createLoading">
<el-row :gutter="16">
<el-dialog v-model="createVisible" title="新建用户虚拟机" width="900px" destroy-on-close class="scrollable-dialog">
<el-form ref="createFormRef" :model="createForm" :rules="createRules" label-width="120px" v-loading="createLoading">
<!-- 行1: 商品 + 用户 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="商品" prop="good_id">
<div class="selector-row">
<el-input :model-value="createForm._goodName || (createForm.good_id ? `商品 #${createForm.good_id}` : '')" readonly placeholder="请选择商品" style="flex:1" />
<el-button type="primary" @click="showProductSelector = true" style="margin-left:8px">选择</el-button>
<el-input :model-value="createForm._goodName || (createForm.good_id ? `商品 #${createForm.good_id}` : '')" readonly placeholder="请选择商品" />
<el-button type="primary" @click="showProductSelector = true">选择</el-button>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户" prop="user_id">
<div class="selector-row">
<el-input :model-value="createForm._userName || (createForm.user_id ? `用户 #${createForm.user_id}` : '')" readonly placeholder="请选择用户" style="flex:1" />
<el-button type="primary" @click="showUserSelector = true" style="margin-left:8px">选择</el-button>
<el-input :model-value="createForm._userName || (createForm.user_id ? `用户 #${createForm.user_id}` : '')" readonly placeholder="请选择用户" />
<el-button type="primary" @click="showUserSelector = true">选择</el-button>
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 行2: 虚拟机名称 -->
<el-form-item label="虚拟机名称" prop="name">
<el-input v-model="createForm.name" placeholder="虚拟机名称" />
</el-form-item>
<el-row :gutter="16">
<!-- 行3: 内存 + vCPU -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="内存(MB)" prop="memory">
<el-input-number v-model="createForm._memoryMB" :min="0" controls-position="right" style="width:100%" placeholder="MB" />
<el-input-number v-model="createForm._memoryMB" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -113,95 +116,136 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<!-- 行4: 系统盘 + 主控服务 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="系统盘(GB)" prop="system_size">
<el-input-number v-model="createForm.system_size" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="镜像ID" prop="image_id">
<div style="display:flex;flex-direction:column;gap:6px;width:100%">
<div class="selector-row">
<el-input :model-value="createForm._serviceName || (createForm._serviceId ? `主控 #${createForm._serviceId}` : '')"
readonly placeholder="1. 选择主控服务" style="flex:1" />
<el-button type="primary" @click="showServiceSelector = true" style="margin-left:8px">选择</el-button>
<el-button v-if="createForm._serviceId" @click="createForm._serviceId = 0; createForm._serviceName = ''; createForm.image_id = 0; createForm._imageName = ''" style="margin-left:4px">清除</el-button>
</div>
<div class="selector-row">
<el-input :model-value="createForm._imageName || (createForm.image_id ? `镜像 #${createForm.image_id}` : '')"
readonly placeholder="2. 选择镜像" style="flex:1" />
<el-button type="primary" @click="showImageSelector = true" :disabled="!createForm._serviceId" style="margin-left:8px">选择</el-button>
<el-button v-if="createForm.image_id" @click="createForm.image_id = 0; createForm._imageName = ''" style="margin-left:4px">清除</el-button>
</div>
<el-form-item label="主控服务">
<div class="selector-row">
<el-input :model-value="createForm._serviceName || (createForm._serviceId ? `主控 #${createForm._serviceId}` : '')" readonly placeholder="选择主控服务" />
<el-button type="primary" @click="showServiceSelector = true">选择</el-button>
<el-button v-if="createForm._serviceId" @click="createForm._serviceId = 0; createForm._serviceName = ''; createForm.image_id = 0; createForm._imageName = ''">清除</el-button>
</div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<!-- 行5: 下行带宽 + 镜像 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="下行带宽(Mbps)" width="220px">
<el-form-item label="下行带宽(Mbps)">
<el-input-number v-model="createForm.rx_bandwidth" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="上行带宽(Mbps)" width="220px">
<el-form-item label="镜像" prop="image_id">
<div class="selector-row">
<el-input :model-value="createForm._imageName || (createForm.image_id ? `镜像 #${createForm.image_id}` : '')" readonly placeholder="选择镜像" />
<el-button type="primary" @click="showImageSelector = true" :disabled="!createForm._serviceId">选择</el-button>
<el-button v-if="createForm.image_id" @click="createForm.image_id = 0; createForm._imageName = ''">清除</el-button>
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 行6: 上行带宽 + IPv4 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="上行带宽(Mbps)">
<el-input-number v-model="createForm.tx_bandwidth" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="IPv4数量" label-width="90px">
<el-form-item label="IPv4数量">
<el-input-number v-model="createForm.ipv4_num" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<!-- 行7: IPv6 + 快照上限 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="IPv6数量" label-width="90px">
<el-form-item label="IPv6数量">
<el-input-number v-model="createForm.ipv6_num" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="快照上限">
<el-input-number v-model="createForm.snapshot_num" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<!-- 行8: 备份上限 + 续费价格 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="备份上限">
<el-input-number v-model="createForm.backup_num" :min="0" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="续费价格()">
<el-input-number v-model="createForm._renewPriceYuan" :min="0" :precision="2" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<!-- 行9: 基础价格 + 订单 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="基础价格()">
<el-input-number v-model="createForm._basePriceYuan" :min="0" :precision="2" controls-position="right" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="订单">
<div class="selector-row">
<el-input :model-value="createForm._orderName || (createForm.order_id ? `订单 #${createForm.order_id}` : '')" readonly placeholder="可选" style="flex:1" />
<el-button type="primary" @click="showOrderSelector = true" style="margin-left:8px">选择</el-button>
<el-button v-if="createForm.order_id" @click="createForm.order_id = 0; createForm._orderName = ''" style="margin-left:4px">清除</el-button>
<el-input :model-value="createForm._orderName || (createForm.order_id ? `订单 #${createForm.order_id}` : '')" readonly placeholder="可选" />
<el-button type="primary" @click="showOrderSelector = true">选择</el-button>
<el-button v-if="createForm.order_id" @click="createForm.order_id = 0; createForm._orderName = ''">清除</el-button>
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 行10: 到期时间 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="到期时间">
<el-date-picker v-model="createForm.expire_time" type="datetime" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="指定宿主机">
<div class="selector-row">
<el-input :model-value="createForm._hostName || (createForm.host_id ? `宿主机 #${createForm.host_id}` : '')" readonly placeholder="可选不选则自动分配" />
<el-button type="primary" @click="showHostSelector = true" :disabled="!createForm._serviceId">选择</el-button>
<el-button v-if="createForm.host_id" @click="createForm.host_id = 0; createForm._hostName = ''">清除</el-button>
</div>
</el-form-item>
</el-col>
</el-row>
<!-- 行11: 宿主机组 + 网络 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="指定宿主机组">
<div class="selector-row">
<el-input :model-value="createForm._hostGroupName || (createForm.host_group_id ? `主机组 #${createForm.host_group_id}` : '')" readonly placeholder="可选不选则用商品绑定的" />
<el-button type="primary" @click="showHostGroupSelector = true" :disabled="!createForm._serviceId">选择</el-button>
<el-button v-if="createForm.host_group_id" @click="createForm.host_group_id = 0; createForm._hostGroupName = ''">清除</el-button>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="网络">
<div class="selector-row">
<el-input :model-value="createForm._networkNames || ''" readonly placeholder="可选可多选" />
<el-button type="primary" @click="showNetworkSelector = true" :disabled="!createForm._serviceId || !createForm.host_id">选择</el-button>
<el-button v-if="createForm.network_ids.length" @click="createForm.network_ids = []; createForm._networkNames = ''">清除</el-button>
</div>
<div v-if="!createForm.host_id && createForm._serviceId" style="font-size:12px;color:#c0c4cc;margin-top:2px">请先选择宿主机</div>
</el-form-item>
</el-col>
</el-row>
<!-- 行12: 备注 -->
<el-form-item label="备注">
<el-input v-model="createForm.note" type="textarea" :rows="2" />
</el-form-item>
@@ -212,7 +256,7 @@
</template>
</el-dialog>
<!-- 编辑虚拟机弹窗(对接 /user_vm/update -->
<!-- 编辑虚拟机弹窗 -->
<el-dialog v-model="editVisible" title="编辑虚拟机配置" width="560px" destroy-on-close class="scrollable-dialog">
<el-form :model="editForm" label-width="130px" v-loading="editLoading">
<el-row :gutter="16">
@@ -251,17 +295,15 @@
</el-row>
<el-form-item label="安全组">
<div class="selector-row">
<el-input :model-value="editForm._sgName || (editForm.port_group_id ? `安全组 #${editForm.port_group_id}` : '')"
readonly placeholder="可选" style="flex:1" />
<el-input :model-value="editForm._sgName || (editForm.port_group_id ? `安全组 #${editForm.port_group_id}` : '')" readonly placeholder="可选" style="flex:1" />
<el-button type="primary" @click="showSgSelector = true" :disabled="!editForm.id" style="margin-left:8px">选择</el-button>
<el-button v-if="editForm.port_group_id" @click="editForm.port_group_id = 0; editForm._sgName = ''" style="margin-left:4px">清除</el-button>
</div>
</el-form-item>
<el-form-item label="公网网络">
<div class="selector-row">
<el-input :model-value="editForm._networkName || (editForm.internet_network_id ? `网络 #${editForm.internet_network_id}` : '')"
readonly placeholder="可选仅网桥类型" style="flex:1" />
<el-button type="primary" @click="showNetworkSelector = true" :disabled="!editForm.id" style="margin-left:8px">选择</el-button>
<el-input :model-value="editForm._networkName || (editForm.internet_network_id ? `网络 #${editForm.internet_network_id}` : '')" readonly placeholder="可选仅网桥类型" style="flex:1" />
<el-button type="primary" @click="showEditNetworkSelector = true" :disabled="!editForm.id" style="margin-left:8px">选择</el-button>
<el-button v-if="editForm.internet_network_id" @click="editForm.internet_network_id = 0; editForm._networkName = ''" style="margin-left:4px">清除</el-button>
</div>
</el-form-item>
@@ -272,22 +314,20 @@
</template>
</el-dialog>
<!-- 商品选择器 -->
<!-- 选择器组件 -->
<ProductSelector v-model="showProductSelector" @confirm="p => { createForm.good_id = p.id; createForm._goodName = p.name }" />
<!-- 用户选择器 -->
<UserSelector v-model:visible="showUserSelector" @select="u => { createForm.user_id = u.user_id; createForm._userName = u.user_name }" />
<!-- 订单选择器 -->
<OrderSelector v-model="showOrderSelector" @confirm="o => { createForm.order_id = o.id; createForm._orderName = o.name }" />
<!-- 主控服务选择器(镜像用) -->
<KvmServiceSelector v-model="showServiceSelector" @confirm="s => { createForm._serviceId = s.id; createForm._serviceName = s.name; createForm.image_id = 0; createForm._imageName = '' }" />
<!-- 镜像选择器 -->
<KvmServiceSelector v-model="showServiceSelector" @confirm="s => { createForm._serviceId = s.id; createForm._serviceName = s.name; createForm.image_id = 0; createForm._imageName = ''; createForm.host_id = 0; createForm._hostName = ''; createForm.host_group_id = 0; createForm._hostGroupName = ''; createForm.network_ids = []; createForm._networkNames = '' }" />
<ImageSelectorPopup v-model="showImageSelector" :service-id="createForm._serviceId || 0" @confirm="img => { createForm.image_id = img.id; createForm._imageName = img.name }" />
<!-- 编辑用安全组选择器 -->
<UserVmSecurityGroupSelector v-model="showSgSelector" :user-goods-id="editForm.id"
@confirm="sg => { editForm.port_group_id = sg.id; editForm._sgName = sg.name }" />
<!-- 编辑用公网网络选择器 -->
<UserVmNetworkSelector v-model="showNetworkSelector" :user-goods-id="editForm.id"
@confirm="net => { editForm.internet_network_id = net.id; editForm._networkName = net.name }" />
<!-- 宿主机选择器 -->
<HostSelectorPopup v-model="showHostSelector" :service-id="createForm._serviceId || 0" @confirm="h => { createForm.host_id = h.id; createForm._hostName = h.name || h.ip; createForm.network_ids = []; createForm._networkNames = '' }" />
<!-- 宿主机组选择器 -->
<HostGroupSelectorPopup v-model="showHostGroupSelector" :service-id="createForm._serviceId || 0" @confirm="g => { createForm.host_group_id = g.id; createForm._hostGroupName = g.name }" />
<!-- 网络选择器(多选用弹窗内表格) -->
<NetworkSelectorPopup v-model="showNetworkSelector" :service-id="createForm._serviceId || 0" :host-id="createForm.host_id || 0" @confirm="n => addNetwork(n)" />
<UserVmSecurityGroupSelector v-model="showSgSelector" :user-goods-id="editForm.id" @confirm="sg => { editForm.port_group_id = sg.id; editForm._sgName = sg.name }" />
<UserVmNetworkSelector v-model="showEditNetworkSelector" :user-goods-id="editForm.id" @confirm="net => { editForm.internet_network_id = net.id; editForm._networkName = net.name }" />
</div>
</template>
@@ -304,6 +344,9 @@ import UserSelector from '@/components/UserSelector/index.vue'
import OrderSelector from '@/components/admin/OrderSelector.vue'
import KvmServiceSelector from '@/components/admin/KvmServiceSelector.vue'
import ImageSelectorPopup from '@/components/admin/ImageSelectorPopup.vue'
import HostSelectorPopup from '@/components/admin/HostSelectorPopup.vue'
import HostGroupSelectorPopup from '@/components/admin/HostGroupSelectorPopup.vue'
import NetworkSelectorPopup from '@/components/admin/NetworkSelectorPopup.vue'
import UserVmSecurityGroupSelector from '@/components/admin/UserVmSecurityGroupSelector.vue'
import UserVmNetworkSelector from '@/components/admin/UserVmNetworkSelector.vue'
import dayjs from 'dayjs'
@@ -314,7 +357,6 @@ const list = ref([])
const total = ref(0)
const query = reactive({ page: 1, count: 10, key: '', bound: null })
const formatTime = (t) => t ? dayjs(t).format('YYYY-MM-DD HH:mm:ss') : '-'
const formatExpireTime = (t) => {
if (!t) return '-'
const d = dayjs(t)
@@ -352,28 +394,41 @@ const showUserSelector = ref(false)
const showOrderSelector = ref(false)
const showServiceSelector = ref(false)
const showImageSelector = ref(false)
const showHostSelector = ref(false)
const showHostGroupSelector = ref(false)
const showNetworkSelector = ref(false)
const createForm = reactive({
good_id: 0, _goodName: '', user_id: 0, _userName: '',
order_id: 0, _orderName: '',
name: '',
_memoryMB: 0,
vcpu: 0, system_size: 0,
order_id: 0, _orderName: '', name: '',
_memoryMB: 0, vcpu: 0, system_size: 0,
rx_bandwidth: 0, tx_bandwidth: 0,
_serviceId: 0, _serviceName: '', image_id: 0, _imageName: '',
host_id: 0, _hostName: '', host_group_id: 0, _hostGroupName: '',
network_ids: [], _networkNames: '',
ipv4_num: 0, ipv6_num: 0, snapshot_num: 0, backup_num: 0,
_renewPriceYuan: 0, _basePriceYuan: 0,
note: '', expire_time: ''
})
const createRules = {
good_id: [{ required: true, validator: (r, v, cb) => v > 0 ? cb() : cb(new Error('请选择商品')), trigger: 'change' }],
user_id: [{ required: true, validator: (r, v, cb) => v > 0 ? cb() : cb(new Error('请选择用户')), trigger: 'change' }],
vcpu: [{ required: true, message: '请填写vCPU', trigger: 'blur' }],
system_size: [{ required: true, message: '请填写系统盘大小', trigger: 'blur' }],
image_id: [{ required: true, validator: (r, v, cb) => v > 0 ? cb() : cb(new Error('请填写镜像ID')), trigger: 'blur' }]
image_id: [{ required: true, validator: (r, v, cb) => v > 0 ? cb() : cb(new Error('请选择镜像')), trigger: 'blur' }]
}
const handleCreate = () => {
Object.assign(createForm, { good_id: 0, _goodName: '', user_id: 0, _userName: '', order_id: 0, _orderName: '', name: '', _memoryMB: 0, vcpu: 0, system_size: 0, rx_bandwidth: 0, tx_bandwidth: 0, _serviceId: 0, _serviceName: '', image_id: 0, _imageName: '', ipv4_num: 0, ipv6_num: 0, snapshot_num: 0, backup_num: 0, _renewPriceYuan: 0, _basePriceYuan: 0, note: '', expire_time: '' })
Object.assign(createForm, {
good_id: 0, _goodName: '', user_id: 0, _userName: '', order_id: 0, _orderName: '', name: '',
_memoryMB: 0, vcpu: 0, system_size: 0, rx_bandwidth: 0, tx_bandwidth: 0,
_serviceId: 0, _serviceName: '', image_id: 0, _imageName: '',
host_id: 0, _hostName: '', host_group_id: 0, _hostGroupName: '',
network_ids: [], _networkNames: '',
ipv4_num: 0, ipv6_num: 0, snapshot_num: 0, backup_num: 0,
_renewPriceYuan: 0, _basePriceYuan: 0, note: '', expire_time: ''
})
createVisible.value = true
}
@@ -386,7 +441,7 @@ const submitCreate = () => {
good_id: createForm.good_id,
user_id: createForm.user_id,
name: createForm.name,
memory: Math.round((createForm._memoryMB || 0) * 1024), // MB → KB
memory: Math.round((createForm._memoryMB || 0) * 1024),
vcpu: createForm.vcpu,
system_size: createForm.system_size,
rx_bandwidth: createForm.rx_bandwidth,
@@ -396,12 +451,15 @@ const submitCreate = () => {
ipv6_num: createForm.ipv6_num,
snapshot_num: createForm.snapshot_num,
backup_num: createForm.backup_num,
renew_price: Math.round(createForm._renewPriceYuan || 0 ),
base_price: Math.round(createForm._basePriceYuan || 0 ),
renew_price: Math.round((createForm._renewPriceYuan || 0) * 100),
base_price: Math.round((createForm._basePriceYuan || 0) * 100),
note: createForm.note
}
if (createForm.order_id) payload.order_id = createForm.order_id
if (createForm.expire_time) payload.expire_time = formatToApiTime(createForm.expire_time)
if (createForm.host_id) payload.host_id = createForm.host_id
if (createForm.host_group_id) payload.host_group_id = createForm.host_group_id
if (createForm.network_ids.length) payload.network_ids = createForm.network_ids
const res = await createUserVm(payload)
if (res?.data?.code === 200) { ElMessage.success('创建成功'); createVisible.value = false; loadList() }
else ElMessage.error(extractApiError(res?.data, '创建失败'))
@@ -409,28 +467,32 @@ const submitCreate = () => {
})
}
// 网络多选:每次选择追加(不重复)
const addNetwork = (n) => {
if (!createForm.network_ids.includes(n.id)) {
createForm.network_ids.push(n.id)
const names = createForm._networkNames ? createForm._networkNames + ', ' + (n.name || n.address || `#${n.id}`) : (n.name || n.address || `#${n.id}`)
createForm._networkNames = names
}
}
// ---- 编辑 ----
const editVisible = ref(false)
const editLoading = ref(false)
const showSgSelector = ref(false)
const showNetworkSelector = ref(false)
const showEditNetworkSelector = ref(false)
const editForm = reactive({
id: 0,
rx_bandwidth: 0, tx_bandwidth: 0,
root_password: '',
ssh_port: 22,
id: 0, rx_bandwidth: 0, tx_bandwidth: 0,
root_password: '', ssh_port: 22,
port_group_id: 0, _sgName: '',
snapshot_num: 0, backup_num: 0,
internet_network_id: 0, _networkName: ''
})
const handleEdit = async (row) => {
// 先重置
Object.assign(editForm, {
id: row.id,
rx_bandwidth: 0, tx_bandwidth: 0,
root_password: '',
ssh_port: 22,
id: row.id, rx_bandwidth: 0, tx_bandwidth: 0,
root_password: '', ssh_port: 22,
port_group_id: 0, _sgName: '',
snapshot_num: 0, backup_num: 0,
internet_network_id: 0, _networkName: ''
@@ -449,20 +511,12 @@ const handleEdit = async (row) => {
editForm.snapshot_num = vm.snapshot_num || 0
editForm.backup_num = vm.backup_num || 0
}
// 回填入站安全组
const inSg = d.vm?.in_port_group
if (inSg) {
editForm.port_group_id = inSg.id
editForm._sgName = inSg.name
}
// 回填公网网络(取第一个 bridge 类型)
if (inSg) { editForm.port_group_id = inSg.id; editForm._sgName = inSg.name }
const bridgeNet = (d.vm?.networks || []).find(n => n.type === 'bridge')
if (bridgeNet) {
editForm.internet_network_id = bridgeNet.id
editForm._networkName = bridgeNet.name || bridgeNet.address
}
if (bridgeNet) { editForm.internet_network_id = bridgeNet.id; editForm._networkName = bridgeNet.name || bridgeNet.address }
}
} catch { /* 回填失败不影响编辑 */ } finally { editLoading.value = false }
} catch { } finally { editLoading.value = false }
}
const submitEdit = async () => {
@@ -485,7 +539,7 @@ const submitEdit = async () => {
// ---- 删除 ----
const handleDelete = (row) => {
ElMessageBox.confirm(`确定删除该用户虚拟机吗?此操作会同时删除远程VM和用户商品记录!`, '删除确认', { type: 'error' })
ElMessageBox.confirm('确定删除该用户虚拟机吗?此操作会同时删除远程VM和用户商品记录!', '删除确认', { type: 'error' })
.then(async () => {
try {
const res = await deleteUserVm({ user_goods_id: row.id })
@@ -503,7 +557,8 @@ onMounted(loadList)
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 8px; }
.toolbar-left, .toolbar-right { display: flex; gap: 8px; align-items: center; }
.pagination-wrapper { display: flex; justify-content: flex-end; margin-top: 16px; }
.selector-row { display: flex; align-items: center; width: 100%; }
.selector-row { display: flex; align-items: center; width: 100%; gap: 8px; }
.selector-row .el-input { flex: 1; }
:global(.scrollable-dialog .el-dialog__body) {
max-height: 65vh;