feat: 用户商品状态筛选与统计对接
Build and Deploy Vue3 / build (push) Successful in 1m46s
Build and Deploy Vue3 / deploy (push) Successful in 39s

- 新增 getUserGoodsCount 接口对接,列表页/虚拟机列表页增加状态筛选与统计卡片

- 已删除/已到期商品适配及相关页面更新

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shiran
2026-06-24 22:12:50 +08:00
parent a8954bd85d
commit 6f82e5e79d
17 changed files with 1637 additions and 371 deletions
+106 -2
View File
@@ -28,8 +28,24 @@
</el-select>
</div>
<!-- 批量操作栏 -->
<div class="batch-bar" v-if="selectedVms.length">
<span class="batch-info">已选择 <strong>{{ selectedVms.length }}</strong> 台虚拟机</span>
<el-button type="success" size="small" @click="handleBatchPower('start')" :loading="batchLoading">
<el-icon><VideoPlay /></el-icon>批量开机
</el-button>
<el-button type="warning" size="small" @click="handleBatchPower('stop')" :loading="batchLoading">
<el-icon><SwitchButton /></el-icon>批量关机
</el-button>
<el-button type="danger" size="small" @click="handleBatchDelete" :loading="batchLoading">
<el-icon><Delete /></el-icon>批量删除
</el-button>
<el-button size="small" @click="clearSelection">取消选择</el-button>
</div>
<!-- 虚拟机列表 -->
<el-table :data="vmList" v-loading="loading" stripe>
<el-table ref="vmTableRef" :data="vmList" v-loading="loading" stripe @selection-change="handleSelectionChange">
<el-table-column type="selection" width="45" />
<el-table-column prop="id" label="ID" width="70" />
<el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip />
<el-table-column label="配置" min-width="200">
@@ -433,7 +449,7 @@
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 { Plus, Refresh, Search, ArrowLeft, ArrowDown, WarningFilled, VideoPlay, SwitchButton, Delete } from '@element-plus/icons-vue'
import {
getRemoteHostList, getVmList, getVmDetail, getVmStatus,
createVm, rebuildVm, startVm, stopVm, rebootVm, suspendVm,
@@ -466,6 +482,7 @@ const serviceName = computed(() => injectedServiceName?.value || route.query.ser
const loading = ref(false)
const submitLoading = ref(false)
const detailLoading = ref(false)
const batchLoading = ref(false)
const vmList = ref([])
const total = ref(0)
const keyword = ref('')
@@ -473,6 +490,12 @@ const filterStatus = ref('')
const hostOptions = ref([])
const queryParams = reactive({ page: 1, page_size: 10 })
// 批量操作
const vmTableRef = ref(null)
const selectedVms = ref([])
const handleSelectionChange = (selection) => { selectedVms.value = selection }
const clearSelection = () => { vmTableRef.value?.clearSelection() }
// 选择器
const showCreateImageSelector = ref(false)
const showRebuildImageSelector = ref(false)
@@ -989,6 +1012,72 @@ const handleDelete = (row) => {
}).catch(() => {})
}
// 批量电源操作(开机/关机)
const handleBatchPower = (action) => {
const label = action === 'start' ? '开机' : '关机'
const targets = selectedVms.value
const skipped = targets.filter(v =>
action === 'start' ? v.status === 'running' : (v.status === 'stopped' || v.status === 'stop')
)
const toOperate = targets.filter(v => !skipped.includes(v))
if (!toOperate.length) {
ElMessage.warning(`所选虚拟机均已处于目标状态,无需${label}`)
return
}
const msg = skipped.length
? `将对 ${toOperate.length} 台虚拟机执行${label}${skipped.length} 台已跳过),是否继续?`
: `确定要对 ${toOperate.length} 台虚拟机执行批量${label}吗?`
ElMessageBox.confirm(msg, `批量${label}`, {
confirmButtonText: '确定', cancelButtonText: '取消',
type: action === 'stop' ? 'warning' : 'info'
}).then(async () => {
batchLoading.value = true
const api = action === 'start' ? startVm : stopVm
let success = 0, fail = 0
await Promise.allSettled(toOperate.map(async (vm) => {
try {
const fd = new FormData()
fd.append('service_id', serviceId.value)
fd.append('vm_id', vm.id)
const res = await api(fd)
if (res?.data?.code === 200) success++
else fail++
} catch { fail++ }
}))
batchLoading.value = false
clearSelection()
ElMessage[fail ? 'warning' : 'success'](`批量${label}完成:成功 ${success},失败 ${fail}`)
loadList()
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
const count = selectedVms.value.length
ElMessageBox.confirm(
`确定要删除选中的 ${count} 台虚拟机吗?此操作不可恢复。`,
'批量删除',
{ confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning' }
).then(async () => {
batchLoading.value = true
let success = 0, fail = 0
await Promise.allSettled(selectedVms.value.map(async (vm) => {
try {
const res = await deleteVm({ service_id: serviceId.value, vm_id: vm.id })
if (res?.data?.code === 200) success++
else fail++
} catch { fail++ }
}))
batchLoading.value = false
clearSelection()
ElMessage[fail ? 'warning' : 'success'](`批量删除完成:成功 ${success},失败 ${fail}`)
loadList()
}).catch(() => {})
}
const goBack = () => { router.push('/virtualization/kvm-service') }
onMounted(async () => {
@@ -1007,4 +1096,19 @@ defineExpose({ loadList })
.migrate-inline-status { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
.migrate-inline-label { color: #e6a23c; font-size: 13px; font-weight: 600; white-space: nowrap; }
.migrate-inline-pct { color: #e6a23c; font-size: 12px; white-space: nowrap; }
.batch-bar {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
margin-bottom: 12px;
background: #ecf5ff;
border: 1px solid #d9ecff;
border-radius: 6px;
}
.batch-info {
font-size: 13px;
color: #409eff;
margin-right: 4px;
}
</style>