|  |  |  | @ -7,11 +7,16 @@ | 
			
		
	
		
			
				
					|  |  |  |  |   </list-header> | 
			
		
	
		
			
				
					|  |  |  |  |   <div v-loading="loading" style="min-height: 90px;"> | 
			
		
	
		
			
				
					|  |  |  |  |     <block-box | 
			
		
	
		
			
				
					|  |  |  |  |       v-for="{ poolId, poolName, nodeNum, cpuCores, gpuNum, availableMemory, totalMemory, diskSize, nodeList, linkUrl }, index in paginatedList" | 
			
		
	
		
			
				
					|  |  |  |  |       v-for="{ poolId, poolName, poolType, nodeNum, cpuCores, gpuNum, availableMemory, totalMemory, diskSize, nodeList, linkUrl }, index in paginatedList" | 
			
		
	
		
			
				
					|  |  |  |  |       :key="poolId" style="margin: 15px 0 0 0;"> | 
			
		
	
		
			
				
					|  |  |  |  |       <el-row style="align-items: center;"> | 
			
		
	
		
			
				
					|  |  |  |  |         <div class="left"> | 
			
		
	
		
			
				
					|  |  |  |  |           <b class="title">{{ poolName }}</b> | 
			
		
	
		
			
				
					|  |  |  |  |           <div style="display: flex;align-items: center;"> | 
			
		
	
		
			
				
					|  |  |  |  |             <b class="title" style="margin-right: 10px;">{{ poolName }}</b> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-tag disable-transitions :type="typeColorMap[poolType]"> | 
			
		
	
		
			
				
					|  |  |  |  |               {{ ['单机单卡', '单机多卡', '多机多卡'][poolType] || '未知类型' }} | 
			
		
	
		
			
				
					|  |  |  |  |             </el-tag> | 
			
		
	
		
			
				
					|  |  |  |  |           </div> | 
			
		
	
		
			
				
					|  |  |  |  |           <div class="tags"> | 
			
		
	
		
			
				
					|  |  |  |  |             <span>节点数量  {{ nodeNum }}</span> | 
			
		
	
		
			
				
					|  |  |  |  |             <span>CPU数  {{ cpuCores }}核</span> | 
			
		
	
	
		
			
				
					|  |  |  | @ -27,9 +32,7 @@ | 
			
		
	
		
			
				
					|  |  |  |  |             <el-button @click="sendRouteChange(linkUrl, 'open')" type="text">配置</el-button> | 
			
		
	
		
			
				
					|  |  |  |  |           </template> | 
			
		
	
		
			
				
					|  |  |  |  |           <template v-else> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-button | 
			
		
	
		
			
				
					|  |  |  |  |               @click="dialogVisible = true; editId = poolId; nodeSelect = nodeList.map(e => e.nodeIp); input = poolName" | 
			
		
	
		
			
				
					|  |  |  |  |               type="text">编辑</el-button> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-button @click="handleEdit(poolId, poolName, nodeList, poolType)" type="text">编辑</el-button> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-button @click="() => handleDelete(poolId)" type="text">删除</el-button> | 
			
		
	
		
			
				
					|  |  |  |  |           </template> | 
			
		
	
		
			
				
					|  |  |  |  |         </div> | 
			
		
	
	
		
			
				
					|  |  |  | @ -38,27 +41,38 @@ | 
			
		
	
		
			
				
					|  |  |  |  |   </div> | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   <!-- 分页组件 --> | 
			
		
	
		
			
				
					|  |  |  |  |   <el-pagination style="margin-top: 15px;" background v-model:current-page="currentPage" v-model:page-size="pageSize" | 
			
		
	
		
			
				
					|  |  |  |  |   <el-pagination style="margin-top: 15px;" background :current-page="currentPage" :page-size="pageSize" | 
			
		
	
		
			
				
					|  |  |  |  |     layout="total, ->, sizes, jumper, prev, next" :page-sizes="[10, 20, 50, 100]" :total="list.length" | 
			
		
	
		
			
				
					|  |  |  |  |     @size-change="handleSizeChange" @current-change="handleCurrentChange" /> | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   <el-dialog @close="editId = null; input = ''; nodeSelect = []" v-model="dialogVisible" | 
			
		
	
		
			
				
					|  |  |  |  |   <el-dialog @close="editId = null; input = ''; poolType = null; nodeSelect = []" v-model="dialogVisible" | 
			
		
	
		
			
				
					|  |  |  |  |     :title="editId ? '编辑资源池' : '创建资源池'" width="1180" :before-close="handleClose"> | 
			
		
	
		
			
				
					|  |  |  |  |     <el-row :wrap="false" style="align-items: center;"> | 
			
		
	
		
			
				
					|  |  |  |  |       <span style="flex-shrink: 0; margin-right: 14px;">资源池名称</span> | 
			
		
	
		
			
				
					|  |  |  |  |       <el-input style="flex: 1;" v-model="input" size="large" maxlength="20" /> | 
			
		
	
		
			
				
					|  |  |  |  |     </el-row> | 
			
		
	
		
			
				
					|  |  |  |  |     <div style="margin-top: 20px; margin-bottom: 10px;"> | 
			
		
	
		
			
				
					|  |  |  |  |     <el-row :wrap="false" style="align-items: center; margin-top: 20px;"> | 
			
		
	
		
			
				
					|  |  |  |  |       <span style="flex-shrink: 0; margin-right: 14px;">资源池类型</span> | 
			
		
	
		
			
				
					|  |  |  |  |       <el-select v-model="poolType" size="large" style="flex: 1;" placeholder="请选择资源池类型" @change="handleTypeChange"> | 
			
		
	
		
			
				
					|  |  |  |  |         <el-option label="单机单卡" value="0" /> | 
			
		
	
		
			
				
					|  |  |  |  |         <el-option label="单机多卡" value="1" /> | 
			
		
	
		
			
				
					|  |  |  |  |         <el-option label="多机多卡" value="2" /> | 
			
		
	
		
			
				
					|  |  |  |  |       </el-select> | 
			
		
	
		
			
				
					|  |  |  |  |     </el-row> | 
			
		
	
		
			
				
					|  |  |  |  |     <div v-if="poolType" style="margin-top: 20px; margin-bottom: 10px;"> | 
			
		
	
		
			
				
					|  |  |  |  |       <span>选择节点</span> | 
			
		
	
		
			
				
					|  |  |  |  |       <span style="float: right;">已选<span style="color: #3061D0; margin: 0 5px;">{{ nodeSelect.length | 
			
		
	
		
			
				
					|  |  |  |  |           }}</span>个节点</span> | 
			
		
	
		
			
				
					|  |  |  |  |     </div> | 
			
		
	
		
			
				
					|  |  |  |  |     <div class="wrap"> | 
			
		
	
		
			
				
					|  |  |  |  |     <div v-if="poolType" class="wrap"> | 
			
		
	
		
			
				
					|  |  |  |  |       <div class="wrap-left"> | 
			
		
	
		
			
				
					|  |  |  |  |         <div style="margin-top: 12px;" | 
			
		
	
		
			
				
					|  |  |  |  |           v-for="{ nodeIp, cpuCores, gpuNum, gpuMemory, totalMemory, diskSize }, index in nodeList" :key="nodeIp"> | 
			
		
	
		
			
				
					|  |  |  |  |           v-for="{ nodeIp, cpuCores, gpuNum, gpuMemory, totalMemory, diskSize } in filteredNodeList" | 
			
		
	
		
			
				
					|  |  |  |  |           :key="nodeIp"> | 
			
		
	
		
			
				
					|  |  |  |  |           <div style="display: flex; align-items: center;"> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-checkbox :model-value="nodeSelect.includes(nodeIp)" @change="handleCheckboxChange(nodeIp)" /> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-checkbox v-if="poolType === '2'" :model-value="nodeSelect.includes(nodeIp)" | 
			
		
	
		
			
				
					|  |  |  |  |               @change="handleCheckboxChange(nodeIp)" /> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-radio v-else :model-value="nodeSelect[0]" :label="nodeIp" @change="handleRadioChange(nodeIp)" /> | 
			
		
	
		
			
				
					|  |  |  |  |             <span style="color: #0B1524; margin-left: 8px;">{{ nodeIp }}</span> | 
			
		
	
		
			
				
					|  |  |  |  |           </div> | 
			
		
	
		
			
				
					|  |  |  |  |           <div class="wrap-row"> | 
			
		
	
	
		
			
				
					|  |  |  | @ -75,7 +89,7 @@ | 
			
		
	
		
			
				
					|  |  |  |  |       </div> | 
			
		
	
		
			
				
					|  |  |  |  |       <div class="wrap-right"> | 
			
		
	
		
			
				
					|  |  |  |  |         <div style="margin-top: 12px;" | 
			
		
	
		
			
				
					|  |  |  |  |           v-for="{ nodeIp, cpuCores, gpuNum, gpuMemory, totalMemory, diskSize }, index in nodeList.filter(e => nodeSelect.includes(e.nodeIp))" | 
			
		
	
		
			
				
					|  |  |  |  |           v-for="{ nodeIp, cpuCores, gpuNum, gpuMemory, totalMemory, diskSize } in nodeList.filter(e => nodeSelect.includes(e.nodeIp))" | 
			
		
	
		
			
				
					|  |  |  |  |           :key="nodeIp"> | 
			
		
	
		
			
				
					|  |  |  |  |           <div style="display: flex; align-items: center;"> | 
			
		
	
		
			
				
					|  |  |  |  |             <el-icon :size="16" color="red" style="cursor: pointer;" @click="handleCheckboxChange(nodeIp)"> | 
			
		
	
	
		
			
				
					|  |  |  | @ -111,6 +125,12 @@ import useParentAction from '~/vgpu/hooks/useParentAction'; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | const { sendRouteChange } = useParentAction(); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | const typeColorMap = { | 
			
		
	
		
			
				
					|  |  |  |  |   0: 'success', // 绿色 - 单机单卡 | 
			
		
	
		
			
				
					|  |  |  |  |   1: 'warning', // 橙色 - 单机多卡 | 
			
		
	
		
			
				
					|  |  |  |  |   2: ''  //  蓝色 - 多机多卡 | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 数据列表相关 | 
			
		
	
		
			
				
					|  |  |  |  | const list = ref([]) | 
			
		
	
		
			
				
					|  |  |  |  | const loading = ref(true) | 
			
		
	
	
		
			
				
					|  |  |  | @ -124,6 +144,7 @@ const total = ref(0) | 
			
		
	
		
			
				
					|  |  |  |  | const dialogVisible = ref(false) | 
			
		
	
		
			
				
					|  |  |  |  | const editId = ref(null) | 
			
		
	
		
			
				
					|  |  |  |  | const input = ref('') | 
			
		
	
		
			
				
					|  |  |  |  | const poolType = ref(null) // 默认不选择 | 
			
		
	
		
			
				
					|  |  |  |  | const btnLoading = ref(false) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 节点选择相关 | 
			
		
	
	
		
			
				
					|  |  |  | @ -137,6 +158,19 @@ const paginatedList = computed(() => { | 
			
		
	
		
			
				
					|  |  |  |  |   return list.value.slice(start, end) | 
			
		
	
		
			
				
					|  |  |  |  | }) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 根据类型过滤节点列表 | 
			
		
	
		
			
				
					|  |  |  |  | const filteredNodeList = computed(() => { | 
			
		
	
		
			
				
					|  |  |  |  |   if (!poolType.value) return [] | 
			
		
	
		
			
				
					|  |  |  |  |    | 
			
		
	
		
			
				
					|  |  |  |  |   if (poolType.value === '1') { | 
			
		
	
		
			
				
					|  |  |  |  |     // 单机多卡:过滤掉只有1张显卡的节点 | 
			
		
	
		
			
				
					|  |  |  |  |     return nodeList.value.filter(node => node.gpuNum > 1) | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  |    | 
			
		
	
		
			
				
					|  |  |  |  |   // 单机单卡(0)和多机多卡(2):显示所有节点 | 
			
		
	
		
			
				
					|  |  |  |  |   return nodeList.value | 
			
		
	
		
			
				
					|  |  |  |  | }) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 分页大小变化 | 
			
		
	
		
			
				
					|  |  |  |  | const handleSizeChange = (val) => { | 
			
		
	
		
			
				
					|  |  |  |  |   pageSize.value = val | 
			
		
	
	
		
			
				
					|  |  |  | @ -157,6 +191,13 @@ const handleOk = async () => { | 
			
		
	
		
			
				
					|  |  |  |  |     }) | 
			
		
	
		
			
				
					|  |  |  |  |     return; | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  |   if (!poolType.value) { | 
			
		
	
		
			
				
					|  |  |  |  |     ElMessage({ | 
			
		
	
		
			
				
					|  |  |  |  |       message: '请选择资源池类型', | 
			
		
	
		
			
				
					|  |  |  |  |       type: 'warning', | 
			
		
	
		
			
				
					|  |  |  |  |     }) | 
			
		
	
		
			
				
					|  |  |  |  |     return; | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  |   if (!nodeSelect.value.length) { | 
			
		
	
		
			
				
					|  |  |  |  |     ElMessage({ | 
			
		
	
		
			
				
					|  |  |  |  |       message: '请选择节点', | 
			
		
	
	
		
			
				
					|  |  |  | @ -172,11 +213,13 @@ const handleOk = async () => { | 
			
		
	
		
			
				
					|  |  |  |  |       res = await pollApi.update({ | 
			
		
	
		
			
				
					|  |  |  |  |         pool_id: editId.value, | 
			
		
	
		
			
				
					|  |  |  |  |         pool_name: input.value, | 
			
		
	
		
			
				
					|  |  |  |  |         pool_type: poolType.value, | 
			
		
	
		
			
				
					|  |  |  |  |         nodes | 
			
		
	
		
			
				
					|  |  |  |  |       }) | 
			
		
	
		
			
				
					|  |  |  |  |     } else { | 
			
		
	
		
			
				
					|  |  |  |  |       res = await pollApi.create({ | 
			
		
	
		
			
				
					|  |  |  |  |         pool_name: input.value, | 
			
		
	
		
			
				
					|  |  |  |  |         pool_type: poolType.value, | 
			
		
	
		
			
				
					|  |  |  |  |         nodes | 
			
		
	
		
			
				
					|  |  |  |  |       }) | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
	
		
			
				
					|  |  |  | @ -191,6 +234,27 @@ const handleOk = async () => { | 
			
		
	
		
			
				
					|  |  |  |  |   btnLoading.value = false; | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 编辑处理 | 
			
		
	
		
			
				
					|  |  |  |  | const handleEdit = (poolId, poolName, nodes, type) => { | 
			
		
	
		
			
				
					|  |  |  |  |   dialogVisible.value = true; | 
			
		
	
		
			
				
					|  |  |  |  |   editId.value = poolId; | 
			
		
	
		
			
				
					|  |  |  |  |   input.value = poolName; | 
			
		
	
		
			
				
					|  |  |  |  |   nodeSelect.value = nodes.map(e => e.nodeIp); | 
			
		
	
		
			
				
					|  |  |  |  |   // 设置资源池类型 | 
			
		
	
		
			
				
					|  |  |  |  |   poolType.value = type.toString(); | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 类型变化处理 | 
			
		
	
		
			
				
					|  |  |  |  | const handleTypeChange = () => { | 
			
		
	
		
			
				
					|  |  |  |  |   // 类型改变时清空已选择的节点 | 
			
		
	
		
			
				
					|  |  |  |  |   nodeSelect.value = []; | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 单选变化 | 
			
		
	
		
			
				
					|  |  |  |  | const handleRadioChange = (ip) => { | 
			
		
	
		
			
				
					|  |  |  |  |   nodeSelect.value = [ip]; | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // 复选框变化 | 
			
		
	
		
			
				
					|  |  |  |  | const handleCheckboxChange = (ip) => { | 
			
		
	
		
			
				
					|  |  |  |  |   const index = nodeSelect.value.indexOf(ip); | 
			
		
	
	
		
			
				
					|  |  |  | 
 |