Commit 04340bf7 by renyizhao

动态规格

parent 5d3142e4
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="1000">
<!-- 搜索区域 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="82px"
>
<el-form-item label="配置类别" prop="configCategory">
<el-select
v-model="queryParams.configCategory"
placeholder="请选择配置类别"
clearable
class="!w-200px"
>
<el-option
v-for="dict in categoryOptions"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="配置选项" prop="configOption">
<el-input
v-model="queryParams.configOption"
placeholder="请输入配置选项"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
<!-- 规格配置列表 -->
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
@selection-change="handleSelectionChange"
height="400"
>
<el-table-column type="selection" width="55" :reserve-selection="true" />
<el-table-column label="配置类别" align="center" prop="configCategory" min-width="160">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMPUTE_RESOURCE_CONFIG_CATEGORY" :value="scope.row.configCategory" />
</template>
</el-table-column>
<el-table-column label="配置选项" align="center" prop="configOption" min-width="180" />
<el-table-column label="详情备注" align="center" prop="detailRemark" min-width="200" />
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
class="mt-4"
/>
<!-- 底部按钮 -->
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">
确定选择 ({{ selectedCount }})
</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, nextTick } from 'vue'
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { ResourceConfigApi, ResourceConfig } from '@/api/compute/resourceconfig'
interface Props {
modelValue: boolean
title?: string
/** 已选中的规格 ID 列表(用于回显勾选状态) */
selectedIds?: number[]
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm', selections: ResourceConfig[]): void
}
const props = withDefaults(defineProps<Props>(), {
title: '选择规格',
selectedIds: () => []
})
const emit = defineEmits<Emits>()
const dialogVisible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const dialogTitle = computed(() => props.title)
// 字典选项
const categoryOptions = getStrDictOptions(DICT_TYPE.COMPUTE_RESOURCE_CONFIG_CATEGORY)
const loading = ref(false)
const list = ref<ResourceConfig[]>([])
const total = ref(0)
const tableRef = ref()
// 跨分页的全量已选规格(key: id,value: 完整规格对象)
// 用于支持多选 + 翻页 + 取消勾选场景
const allSelectedMap = reactive(new Map<number, ResourceConfig>())
const selectedCount = computed(() => allSelectedMap.size)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
configCategory: undefined,
configOption: undefined,
status: 0 // 默认只查询启用的规格
})
const queryFormRef = ref()
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ResourceConfigApi.getResourceConfigPage(queryParams)
list.value = data.list
total.value = data.total
// 数据加载完后回显已选中的项
await nextTick()
restoreSelection()
} finally {
loading.value = false
}
}
/** 回显已选中的规格(跨分页) */
const restoreSelection = () => {
if (!props.selectedIds?.length || !tableRef.value) return
const idSet = new Set(props.selectedIds)
for (const row of list.value) {
if (idSet.has(row.id)) {
tableRef.value.toggleRowSelection(row, true)
}
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
queryParams.status = 0
handleQuery()
}
/** 处理表格多选变化
* el-table 的 selection-change 只会带当前页可见的已选行;
* 这里用 allSelectedMap 维护所有分页的全量已选规格。
* 策略:把当前页 id 全部从 map 中移除,再把 rows(当前页最新已选)写入 map,
* 这样能正确处理「取消勾选」和「翻页」两种场景。
*/
const handleSelectionChange = (rows: ResourceConfig[]) => {
const currentPageIds = list.value.map((r) => r.id)
const visibleSelectedIds = new Set(rows.map((r) => r.id))
for (const id of currentPageIds) {
if (!visibleSelectedIds.has(id)) {
allSelectedMap.delete(id)
}
}
for (const row of rows) {
allSelectedMap.set(row.id, row)
}
}
/** 确认选择 - 提交全量已选(跨分页) */
const handleConfirm = () => {
emit('confirm', Array.from(allSelectedMap.values()))
dialogVisible.value = false
}
/** 打开弹窗时加载数据
* 弹窗只展示启用规格,但 props.selectedIds 里可能既有启用的也有禁用的;
* 跨分页的已选规格在第一页打开时不会触发 selection-change,所以这里要主动
* 预加载所有已选的启用规格到 allSelectedMap,让底部计数从一开始就是全量。
*/
const handleOpen = async () => {
allSelectedMap.clear()
if (props.selectedIds?.length) {
const results = await Promise.all(
props.selectedIds.map((id) => ResourceConfigApi.getResourceConfig(id).catch(() => null))
)
for (const cfg of results) {
// 只统计启用规格(弹窗只展示启用规格,禁用的不在计数范围内)
if (cfg && cfg.status !== 1) {
allSelectedMap.set(cfg.id, cfg)
}
}
}
getList()
}
// 监听弹窗打开
watch(
() => props.modelValue,
(val) => {
if (val) {
handleOpen()
} else {
// 关闭时清空全量选择
allSelectedMap.clear()
}
},
{ immediate: true }
)
</script>
......@@ -17,83 +17,6 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item label="CPU" prop="cpu">
<el-select v-model="queryParams.cpu" placeholder="请选择CPU配置" clearable class="!w-240px">
<el-option
v-for="option in resourceConfigStore.getOptionsByType('cpu')"
:key="option.id"
:label="option.configOption"
:value="option.configOption"
/>
</el-select>
</el-form-item>
<el-form-item label="GPU" prop="gpu">
<el-select v-model="queryParams.gpu" placeholder="请选择GPU配置" clearable class="!w-240px">
<el-option
v-for="option in resourceConfigStore.getOptionsByType('gpu')"
:key="option.id"
:label="option.configOption"
:value="option.configOption"
/>
</el-select>
</el-form-item>
<el-form-item label="内存" prop="ram">
<el-select
v-model="queryParams.ram"
placeholder="请选择内存配置"
clearable
class="!w-240px"
>
<el-option
v-for="option in resourceConfigStore.getOptionsByType('ram')"
:key="option.id"
:label="option.configOption"
:value="option.configOption"
/>
</el-select>
</el-form-item>
<el-form-item label="存储" prop="storage">
<el-select
v-model="queryParams.storage"
placeholder="请选择存储配置"
clearable
class="!w-240px"
>
<el-option
v-for="option in resourceConfigStore.getOptionsByType('storage')"
:key="option.id"
:label="option.configOption"
:value="option.configOption"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="服务器ip" prop="ip">-->
<!-- <el-input-->
<!-- v-model="queryParams.ip"-->
<!-- placeholder="请输入服务器ip"-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="初始用户名" prop="initUsername">-->
<!-- <el-input-->
<!-- v-model="queryParams.initUsername"-->
<!-- placeholder="请输入初始用户名"-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="初始密码" prop="initPassword">-->
<!-- <el-input-->
<!-- v-model="queryParams.initPassword"-->
<!-- placeholder="请输入初始密码"-->
<!-- clearable-->
<!-- @keyup.enter="handleQuery"-->
<!-- class="!w-240px"-->
<!-- />-->
<!-- </el-form-item>-->
<el-form-item label="商品简介" prop="intro">
<el-input
v-model="queryParams.intro"
......@@ -133,21 +56,6 @@
/>
</el-select>
</el-form-item>
<el-form-item label="服务器所在地" prop="location">
<el-select
v-model="queryParams.location"
placeholder="请选择服务器所在地"
clearable
class="!w-240px"
>
<el-option
v-for="option in resourceConfigStore.getOptionsByType('location')"
:key="option.id"
:label="option.configOption"
:value="option.configOption"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="商品封面图" prop="picUrl">-->
<!-- <el-input-->
<!-- v-model="queryParams.picUrl"-->
......@@ -216,7 +124,7 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
<!-- <el-button
type="success"
plain
@click="handleExport"
......@@ -224,7 +132,7 @@
v-hasPermi="['compute:resource-spu:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-button> -->
<el-button
type="danger"
plain
......@@ -268,16 +176,11 @@
</template>
</el-table-column>
<!-- 硬件配置字段已隐藏 -->
<!-- <el-table-column label="算力资源分类编号" align="center" prop="categoryId" />-->
<!-- <el-table-column label="算力资源分类编号" align="center" prop="categoryId" />-->
<el-table-column label="算力资源分类" align="center" prop="categoryName" min-width="150" />
<el-table-column label="规格" min-width="240">
<template #default="{ row }">
<el-tag
v-for="(value, key) in row.specMap"
:key="key"
size="small"
class="!mr-1"
>
<el-tag v-for="(value, key) in row.specMap" :key="key" size="small" class="!mr-1">
{{ key }}: {{ value }}
</el-tag>
</template>
......@@ -351,10 +254,8 @@ import download from '@/utils/download'
import { createImageViewer } from '@/components/ImageViewer'
import { ResourceSpuApi, ResourceSpu } from '@/api/compute/resourcespu'
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
import { useResourceConfigStore } from '@/store/modules/compute/hardwareConfig'
const { push } = useRouter()
const resourceConfigStore = useResourceConfigStore()
/** 算力资源SPU表(基础配置信息) 列表 */
defineOptions({ name: 'ResourceSpu' })
......@@ -370,20 +271,9 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
cpu: undefined,
gpu: undefined,
ram: undefined,
storage: undefined,
ip: undefined,
initUsername: undefined,
initPassword: undefined,
intro: undefined,
categoryId: undefined,
source: undefined,
location: undefined,
picUrl: undefined,
sliderPicUrls: undefined,
sales: undefined,
status: undefined,
createTime: []
})
......@@ -485,15 +375,9 @@ const handleExport = async () => {
/** 初始化 **/
onMounted(async () => {
try {
// 并行加载数据
const [categoryResponse] = await Promise.all([
ResourceSpuApi.listSimpleCategory(),
resourceConfigStore.loadConfigOptions()
])
// 加载分类列表 + 列表数据
const [categoryResponse] = await Promise.all([ResourceSpuApi.listSimpleCategory(), getList()])
categoryList.value = categoryResponse
// 加载列表数据
getList()
} catch (error) {
console.error('初始化失败:', error)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment