feat: 添加用户虚拟机商品管理
Build and Deploy Vue3 / build (push) Successful in 1m40s
Build and Deploy Vue3 / deploy (push) Successful in 1m8s

This commit is contained in:
2026-03-31 15:13:04 +08:00
parent 71d3605f4f
commit c07e09c151
28 changed files with 7143 additions and 621 deletions
+104 -11
View File
@@ -96,9 +96,10 @@
disabled
style="flex: 1"
/>
<el-button type="primary" @click="showGroupSelector = true" style="margin-left: 8px">选择</el-button>
<el-button type="primary" @click="showGroupSelector = true" style="margin-left: 8px" :disabled="!!bindForm.good_id">选择</el-button>
<el-button v-if="bindForm.good_group_id" @click="clearBindGroup" style="margin-left: 4px">清除</el-button>
</div>
<div v-if="bindForm.good_id" style="font-size: 12px; color: #e6a23c; margin-top: 4px">已绑定商品请先清除商品后再绑定商品组</div>
</el-form-item>
<el-form-item label="绑定商品">
<div class="bind-selector-row">
@@ -107,9 +108,10 @@
disabled
style="flex: 1"
/>
<el-button type="primary" @click="showProductSelector = true" style="margin-left: 8px">选择</el-button>
<el-button type="primary" @click="showProductSelector = true" style="margin-left: 8px" :disabled="!!bindForm.good_group_id">选择</el-button>
<el-button v-if="bindForm.good_id" @click="clearBindProduct" style="margin-left: 4px">清除</el-button>
</div>
<div v-if="bindForm.good_group_id" style="font-size: 12px; color: #e6a23c; margin-top: 4px">已绑定商品组请先清除商品组后再绑定商品</div>
</el-form-item>
<el-alert type="info" :closable="false" style="margin-bottom: 12px;">
<template #title>
@@ -123,7 +125,34 @@
</template>
</el-dialog>
<!-- 商品组选择器 -->
<!-- 生成商品 - 父级商品组选择器 -->
<ProductGroupSelector
v-model="showGenerateGroupSelector"
:current-group-id="generateForm.parent_group_id"
@confirm="g => { generateForm.parent_group_id = g.id; generateForm._parentGroupName = g.name }"
/>
<!-- 生成商品 - 标签选择器 -->
<el-dialog v-model="showGenerateTagSelector" title="选择标签" width="560px" append-to-body destroy-on-close>
<div style="margin-bottom: 12px; display: flex; gap: 8px">
<el-input v-model="tagKeyword" placeholder="搜索标签名称" clearable style="width: 220px" @input="filterTags">
<template #prefix><el-icon><Search /></el-icon></template>
</el-input>
<el-button :icon="Refresh" @click="() => { tagOptions.value = []; fetchTagOptions() }" :loading="tagLoading">刷新</el-button>
</div>
<el-table :data="filteredTagOptions" v-loading="tagLoading" highlight-current-row
@current-change="row => selectedTagRow = row" :height="300" stripe size="small">
<el-table-column prop="id" label="ID" width="70" />
<el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip />
</el-table>
<el-empty v-if="!filteredTagOptions.length && !tagLoading" description="暂无标签" :image-size="60" />
<template #footer>
<el-button @click="showGenerateTagSelector = false">取消</el-button>
<el-button type="primary" :disabled="!selectedTagRow" @click="confirmTagSelect">确定选择</el-button>
</template>
</el-dialog>
<!-- 绑定弹窗用商品组选择器 -->
<ProductGroupSelector
v-model="showGroupSelector"
:current-group-id="bindForm.good_group_id"
@@ -146,13 +175,29 @@
</el-alert>
<el-form ref="generateFormRef" :model="generateForm" :rules="generateFormRules" label-width="120px">
<el-form-item label="起始主机组ID" prop="id">
<el-input-number v-model="generateForm.id" :min="1" disabled style="width: 100%" />
<el-input :model-value="generateForm.id" disabled style="width: 100%" />
</el-form-item>
<el-form-item label="父级GoodGroup">
<el-input-number v-model="generateForm.parent_group_id" :min="0" placeholder="挂载到已有父级(可选)" style="width: 100%" />
<div class="bind-selector-row">
<el-input
:model-value="generateForm.parent_group_id ? `商品组 #${generateForm.parent_group_id}${generateForm._parentGroupName ? ' - ' + generateForm._parentGroupName : ''}` : '不挂载父级'"
disabled
style="flex: 1"
/>
<el-button type="primary" @click="showGenerateGroupSelector = true" style="margin-left: 8px">选择</el-button>
<el-button v-if="generateForm.parent_group_id" @click="generateForm.parent_group_id = 0; generateForm._parentGroupName = ''" style="margin-left: 4px">清除</el-button>
</div>
</el-form-item>
<el-form-item label="标签ID">
<el-input-number v-model="generateForm.tag_id" :min="0" placeholder="根节点标签ID(可选)" style="width: 100%" />
<el-form-item label="标签">
<div class="bind-selector-row">
<el-input
:model-value="generateForm.tag_id ? `标签 #${generateForm.tag_id}${generateForm._tagName ? ' - ' + generateForm._tagName : ''}` : '不设置标签'"
disabled
style="flex: 1"
/>
<el-button type="primary" @click="showGenerateTagSelector = true" style="margin-left: 8px">选择</el-button>
<el-button v-if="generateForm.tag_id" @click="generateForm.tag_id = 0; generateForm._tagName = ''" style="margin-left: 4px">清除</el-button>
</div>
</el-form-item>
<el-form-item label="Table标识" prop="table">
<el-input v-model="generateForm.table" placeholder=" kvm_service" />
@@ -167,7 +212,7 @@
</template>
<script setup>
import { ref, reactive, computed, inject, onMounted } from 'vue'
import { ref, reactive, computed, inject, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, RefreshRight, Search, ArrowLeft } from '@element-plus/icons-vue'
@@ -181,6 +226,7 @@ import {
getKvmServiceList
} from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
import { getProductGroupTagList } from '@/api/admin/product'
import ProductGroupSelector from '@/components/admin/ProductGroupSelector.vue'
import ProductSelector from '@/components/admin/ProductSelector.vue'
import dayjs from 'dayjs'
@@ -211,7 +257,7 @@ const normalizeService = (s) => ({
const loadServiceOptions = async () => {
try {
const res = await getKvmServiceList({ page: 1, count: 100, key: '' })
const res = await getKvmServiceList({ page: 1, count: 10, key: '' })
if (res?.data?.code === 200 && res?.data?.data) {
const inner = res.data.data
const raw = inner.data || inner.list || (Array.isArray(inner) ? inner : [])
@@ -471,7 +517,9 @@ const generateFormRef = ref(null)
const generateForm = reactive({
id: undefined,
parent_group_id: 0,
_parentGroupName: '',
tag_id: 0,
_tagName: '',
table: 'kvm_service'
})
@@ -479,11 +527,56 @@ const generateFormRules = {
id: [{ required: true, message: '主机组ID不能为空', trigger: 'blur' }]
}
// 父级商品组选择器
const showGenerateGroupSelector = ref(false)
// 标签选择器
const showGenerateTagSelector = ref(false)
const tagOptions = ref([])
const tagLoading = ref(false)
const tagKeyword = ref('')
const selectedTagRow = ref(null)
const filteredTagOptions = computed(() =>
tagKeyword.value
? tagOptions.value.filter(t => t.name?.includes(tagKeyword.value))
: tagOptions.value
)
const filterTags = () => { /* computed 自动响应 */ }
const confirmTagSelect = () => {
if (!selectedTagRow.value) return
generateForm.tag_id = selectedTagRow.value.id
generateForm._tagName = selectedTagRow.value.name
showGenerateTagSelector.value = false
selectedTagRow.value = null
tagKeyword.value = ''
}
const loadTagOptions = async () => {
if (tagOptions.value.length) return
await fetchTagOptions()
}
const fetchTagOptions = async () => {
tagLoading.value = true
try {
const res = await getProductGroupTagList()
if (res?.data?.code === 200 && res?.data?.data) {
const inner = res.data.data
tagOptions.value = Array.isArray(inner) ? inner : (inner.data || inner.list || [])
}
} catch { /* */ } finally { tagLoading.value = false }
}
// 监听标签选择器打开时加载数据
watch(showGenerateTagSelector, (val) => { if (val) loadTagOptions() })
const handleGenerateGoods = (row) => {
Object.assign(generateForm, {
id: Number(row.Id ?? row.id),
parent_group_id: 0,
_parentGroupName: '',
tag_id: 0,
_tagName: '',
table: 'kvm_service'
})
generateDialogVisible.value = true
@@ -501,8 +594,8 @@ const submitGenerate = () => {
generateSubmitLoading.value = true
try {
const payload = { id: generateForm.id }
if (generateForm.parent_group_id > 0) payload.parent_group_id = generateForm.parent_group_id
if (generateForm.tag_id > 0) payload.tag_id = generateForm.tag_id
if (generateForm.parent_group_id) payload.parent_group_id = generateForm.parent_group_id
if (generateForm.tag_id) payload.tag_id = generateForm.tag_id
if (generateForm.table) payload.table = generateForm.table
const res = await generateGoodsByHostGroup(payload)