@@ -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 {
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);