From 96c9f33b1961394a43b38d6eb1ebc5f3831a4f70 Mon Sep 17 00:00:00 2001 From: 2256907009 <2256907009@qq.com> Date: Tue, 30 Sep 2025 21:21:25 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E6=94=B9=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E8=BF=9C=E7=A8=8B=E8=BF=9E=E6=8E=A5=E7=BD=91=E5=85=B3?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/acs/nodes/Nodes.vue | 100 +++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/src/views/acs/nodes/Nodes.vue b/src/views/acs/nodes/Nodes.vue index 9e155a0..832f7ad 100644 --- a/src/views/acs/nodes/Nodes.vue +++ b/src/views/acs/nodes/Nodes.vue @@ -203,8 +203,28 @@ - - + + + +
+
{{ item.url }}
+
用户: {{ item.username }} | 创建时间: {{ item.created_at }}
+
+
+
@@ -283,6 +303,7 @@ import { import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' import { getServer, addServer, editServer, deleteServer, getServerStatus } from '@/utils/acs/server' import { copyDomText } from "@/utils/hide" +import {getGuacamoleList} from '@/utils/acs/guacamole' const router = useRouter() @@ -334,6 +355,10 @@ const handleSizeChange = (val) => { const dialogVisible = ref(false) const dialogType = ref('add') // 'add', 'edit' +// Guacamole 相关数据 +const guacamoleList = ref([]) +const guacamoleLoading = ref(false) + // 表单对象和规则 const serverFormRef = ref(null) const serverForm = reactive({ @@ -442,6 +467,49 @@ const updateStats = () => { serverStats.offline = serverData.value.length - serverStats.online } +// 获取 Guacamole 列表 +const fetchGuacamoleList = async () => { + if (guacamoleLoading.value) return + + guacamoleLoading.value = true + try { + const res = await getGuacamoleList() + console.log("列表数据",res) + if (res && res.data && res.data.code === 200) { + guacamoleList.value = res.data.data || [] + } else { + ElMessage.error('获取Guacamole配置列表失败') + guacamoleList.value = [] + } + } catch (error) { + console.error('获取Guacamole列表失败:', error) + ElMessage.error('获取Guacamole配置列表失败') + guacamoleList.value = [] + } finally { + guacamoleLoading.value = false + } +} + +// 处理 Guacamole 选择变化 +const handleGuacamoleChange = (selectedId) => { + if (!selectedId) { + // 清空相关字段 + serverForm.username = '' + serverForm.password = '' + return + } + + // 找到选中的 Guacamole 配置 + const selectedGuacamole = guacamoleList.value.find(item => item.id === selectedId) + if (selectedGuacamole) { + // 自动填充相关字段 + serverForm.username = selectedGuacamole.username + serverForm.password = selectedGuacamole.password + + ElMessage.success(`已自动填充 ${selectedGuacamole.url} 的配置信息`) + } +} + // 添加服务器 const handleAdd = () => { dialogType.value = 'add' @@ -460,6 +528,11 @@ const handleAdd = () => { } }) + // 如果是虚拟机类型,获取 Guacamole 列表 + if (serverType.value === 'hyperV') { + fetchGuacamoleList() + } + // 确保在下一个 tick 重置表单验证状态 nextTick(() => { if (serverFormRef.value) { @@ -480,6 +553,11 @@ const handleEdit = (row) => { } }) + // 如果是虚拟机类型,获取 Guacamole 列表 + if (row.server_type === 'hyperV') { + fetchGuacamoleList() + } + nextTick(() => { if (serverFormRef.value) { serverFormRef.value.clearValidate() @@ -1027,6 +1105,24 @@ onMounted(async () => { line-height: 1.2; } +/* Guacamole 选项样式 */ +.guacamole-option { + padding: 4px 0; +} + +.guacamole-option .option-main { + font-size: 14px; + font-weight: 500; + color: #303133; + margin-bottom: 2px; +} + +.guacamole-option .option-sub { + font-size: 12px; + color: #909399; + line-height: 1.2; +} + .form-grid { display: grid; grid-template-columns: repeat(2, 1fr); From a503b2ca7530b30303eba47f0ff6855ca3589665 Mon Sep 17 00:00:00 2001 From: 2256907009 <2256907009@qq.com> Date: Wed, 1 Oct 2025 00:33:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=E4=BF=AE=E6=94=B9=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E8=99=9A=E6=8B=9F=E6=9C=BA=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/acs/images/VmImages.vue | 132 ++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 14 deletions(-) diff --git a/src/views/acs/images/VmImages.vue b/src/views/acs/images/VmImages.vue index e6bb9b4..9742c1b 100644 --- a/src/views/acs/images/VmImages.vue +++ b/src/views/acs/images/VmImages.vue @@ -143,16 +143,39 @@ - + - - - - + +
+ + + +
+ + + 创建新分类 + +
+
@@ -249,7 +272,7 @@ import { import { ElMessage, ElMessageBox } from 'element-plus' import { getServer, getServerPlan } from '@/utils/acs/server' import { - editMirror, delMirror, getUserMirrorList, addVirtualMirror, getImageTypeList + editMirror, delMirror, getUserMirrorList, addVirtualMirror, getImageTypeList, createImageType } from '@/utils/acs/mirror' import { uploadFile, getFileList } from '@/utils/acs/message' import { mainUrl } from '@/utils/request' @@ -298,13 +321,15 @@ const imageForm = reactive({ image_ico: '', server_id: '', path: '', - class_id: '' + class_id: '', + class_name: '' }) const rules = { - name: [{ required: true, message: '请输入镜像名称', trigger: 'blur' }], - path: [{ required: true, message: '请输入镜像文件路径', trigger: 'blur' }], - show_name: [{ required: true, message: '请输入展示名称', trigger: 'blur' }], + // 根据接口文档,所有字段都是可选的 + // name: [{ required: true, message: '请输入镜像名称', trigger: 'blur' }], + // path: [{ required: true, message: '请输入镜像文件路径', trigger: 'blur' }], + // show_name: [{ required: true, message: '请输入展示名称', trigger: 'blur' }], // plan_id: [{ required: true, message: '请选择套餐', trigger: 'change' }] } @@ -320,6 +345,7 @@ const picList = ref([]) const total = ref(10) const currentIndex = ref(null) const categoryList = ref([]) +const showNewCategoryInput = ref(false) // 获取操作系统图标 const getOsIcon = (os) => { @@ -447,6 +473,47 @@ const fetchCategoryList = async (serverId) => { } } +// 处理分类选择变化 +const handleCategoryChange = (value) => { + if (value) { + // 选择了现有分类,清空新分类名称 + imageForm.class_name = '' + showNewCategoryInput.value = false + } else { + // 清空选择,显示新分类输入框 + showNewCategoryInput.value = true + } +} + +// 创建新分类 +const createNewCategory = async () => { + if (!imageForm.class_name.trim()) { + ElMessage.warning('请输入分类名称') + return + } + + try { + const response = await createImageType(nowserver_id.value, imageForm.class_name.trim(), '') + if (response.data.code === 200) { + ElMessage.success('分类创建成功') + // 重新获取分类列表 + await fetchCategoryList(nowserver_id.value) + // 设置新创建的分类为选中状态 + const newCategory = categoryList.value.find(item => item.name === imageForm.class_name.trim()) + if (newCategory) { + imageForm.class_id = newCategory.class_id + imageForm.class_name = '' + showNewCategoryInput.value = false + } + } else { + ElMessage.error('创建分类失败:' + response.data.message) + } + } catch (error) { + console.error('创建分类出错:', error) + ElMessage.error('创建分类失败') + } +} + // 新增镜像 const toLoad = async (data) => { dialogVisible.value = true @@ -456,6 +523,7 @@ const toLoad = async (data) => { }) imageForm.server_id = data nowserver_id.value = data + showNewCategoryInput.value = true // 添加镜像时默认显示新分类输入框 try { // 获取套餐列表 let res = await getServerPlan({ server_id: data }) @@ -515,6 +583,7 @@ const handleEdit = async (row) => { editOr.value = true dialogVisible.value = true + showNewCategoryInput.value = false // 编辑时默认不显示新分类输入框 for (const key in row) { if (row.hasOwnProperty(key)) { imageForm[key] = row[key] @@ -553,6 +622,7 @@ const handleCreateVM = (row) => { // 关闭对话框 const handleDialogClose = () => { dialogVisible.value = false + showNewCategoryInput.value = false if (imageFormRef.value) { imageFormRef.value.resetFields() } @@ -564,10 +634,27 @@ const submitForm = async () => { await imageFormRef.value.validate((valid) => { if (valid) { + // 准备提交数据,根据接口要求处理分类字段 + const submitData = { ...imageForm } + + // 如果选择了现有分类,清空 class_name + if (submitData.class_id) { + submitData.class_name = '' + } + // 如果没有选择现有分类但有新分类名称,清空 class_id + else if (submitData.class_name) { + submitData.class_id = '' + } + // 如果都没有,清空两个字段 + else { + submitData.class_id = '' + submitData.class_name = '' + } + if (editOr.value) { - imageForm.image_id = imageForm.id - delete imageForm.id - editMirror(imageForm).then(res => { + submitData.image_id = submitData.id + delete submitData.id + editMirror(submitData).then(res => { if (res.data.code == 200) { dialogVisible.value = false ElMessage.success('编辑成功') @@ -579,7 +666,7 @@ const submitForm = async () => { ElMessage.error('编辑失败') }) } else { - addVirtualMirror(imageForm).then(res => { + addVirtualMirror(submitData).then(res => { if (res.data.code == 200) { dialogVisible.value = false ElMessage.success('添加成功') @@ -882,6 +969,23 @@ onMounted(() => { border-radius: 4px; } +/* 分类选择容器样式 */ +.category-select-container { + width: 100%; +} + +.category-input-container { + margin-top: 8px; + padding: 12px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e9ecef; +} + +.category-input-container .el-input { + margin-bottom: 8px; +} + /* 响应式调整 */ @media (max-width: 768px) { .image-info-cell {