master #2
@@ -143,16 +143,39 @@
|
|||||||
<el-form-item label="镜像名称" prop="name">
|
<el-form-item label="镜像名称" prop="name">
|
||||||
<el-input v-model="imageForm.name" placeholder="请输入镜像名称" />
|
<el-input v-model="imageForm.name" placeholder="请输入镜像名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="文件路径" prop="path" v-if="editOr == true">
|
<el-form-item label="文件路径" prop="path" >
|
||||||
<el-input v-model="imageForm.path" placeholder="请输入镜像文件路径" />
|
<el-input v-model="imageForm.path" placeholder="请输入镜像文件路径" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="展示名称" prop="show_name">
|
<el-form-item label="展示名称" prop="show_name">
|
||||||
<el-input v-model="imageForm.show_name" placeholder="请输入展示名称" />
|
<el-input v-model="imageForm.show_name" placeholder="请输入展示名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="分类" prop="class_id" v-if="editOr == true">
|
<el-form-item label="分类" prop="class_id" >
|
||||||
<el-select v-model="imageForm.class_id" placeholder="请选择分类" clearable style="width: 100%">
|
<div class="category-select-container">
|
||||||
<el-option v-for="item in categoryList" :key="item.class_id" :label="item.name" :value="item.class_id" />
|
<el-select
|
||||||
</el-select>
|
v-model="imageForm.class_id"
|
||||||
|
placeholder="请选择分类"
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleCategoryChange"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in categoryList" :key="item.class_id" :label="item.name" :value="item.class_id" />
|
||||||
|
</el-select>
|
||||||
|
<div class="category-input-container" v-if="showNewCategoryInput">
|
||||||
|
<el-input
|
||||||
|
v-model="imageForm.class_name"
|
||||||
|
placeholder="请输入新分类名称"
|
||||||
|
style="width: 100%; margin-top: 8px"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
@click="createNewCategory"
|
||||||
|
style="margin-top: 8px"
|
||||||
|
>
|
||||||
|
创建新分类
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="图标" prop="image_ico">
|
<el-form-item label="图标" prop="image_ico">
|
||||||
<div class="image-icon-upload">
|
<div class="image-icon-upload">
|
||||||
@@ -249,7 +272,7 @@ import {
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { getServer, getServerPlan } from '@/utils/acs/server'
|
import { getServer, getServerPlan } from '@/utils/acs/server'
|
||||||
import {
|
import {
|
||||||
editMirror, delMirror, getUserMirrorList, addVirtualMirror, getImageTypeList
|
editMirror, delMirror, getUserMirrorList, addVirtualMirror, getImageTypeList, createImageType
|
||||||
} from '@/utils/acs/mirror'
|
} from '@/utils/acs/mirror'
|
||||||
import { uploadFile, getFileList } from '@/utils/acs/message'
|
import { uploadFile, getFileList } from '@/utils/acs/message'
|
||||||
import { mainUrl } from '@/utils/request'
|
import { mainUrl } from '@/utils/request'
|
||||||
@@ -298,13 +321,15 @@ const imageForm = reactive({
|
|||||||
image_ico: '',
|
image_ico: '',
|
||||||
server_id: '',
|
server_id: '',
|
||||||
path: '',
|
path: '',
|
||||||
class_id: ''
|
class_id: '',
|
||||||
|
class_name: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
name: [{ required: true, message: '请输入镜像名称', trigger: 'blur' }],
|
// 根据接口文档,所有字段都是可选的
|
||||||
path: [{ required: true, message: '请输入镜像文件路径', trigger: 'blur' }],
|
// name: [{ required: true, message: '请输入镜像名称', trigger: 'blur' }],
|
||||||
show_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' }]
|
// plan_id: [{ required: true, message: '请选择套餐', trigger: 'change' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +345,7 @@ const picList = ref([])
|
|||||||
const total = ref(10)
|
const total = ref(10)
|
||||||
const currentIndex = ref(null)
|
const currentIndex = ref(null)
|
||||||
const categoryList = ref([])
|
const categoryList = ref([])
|
||||||
|
const showNewCategoryInput = ref(false)
|
||||||
|
|
||||||
// 获取操作系统图标
|
// 获取操作系统图标
|
||||||
const getOsIcon = (os) => {
|
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) => {
|
const toLoad = async (data) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@@ -456,6 +523,7 @@ const toLoad = async (data) => {
|
|||||||
})
|
})
|
||||||
imageForm.server_id = data
|
imageForm.server_id = data
|
||||||
nowserver_id.value = data
|
nowserver_id.value = data
|
||||||
|
showNewCategoryInput.value = true // 添加镜像时默认显示新分类输入框
|
||||||
try {
|
try {
|
||||||
// 获取套餐列表
|
// 获取套餐列表
|
||||||
let res = await getServerPlan({ server_id: data })
|
let res = await getServerPlan({ server_id: data })
|
||||||
@@ -515,6 +583,7 @@ const handleEdit = async (row) => {
|
|||||||
|
|
||||||
editOr.value = true
|
editOr.value = true
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
showNewCategoryInput.value = false // 编辑时默认不显示新分类输入框
|
||||||
for (const key in row) {
|
for (const key in row) {
|
||||||
if (row.hasOwnProperty(key)) {
|
if (row.hasOwnProperty(key)) {
|
||||||
imageForm[key] = row[key]
|
imageForm[key] = row[key]
|
||||||
@@ -553,6 +622,7 @@ const handleCreateVM = (row) => {
|
|||||||
// 关闭对话框
|
// 关闭对话框
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
|
showNewCategoryInput.value = false
|
||||||
if (imageFormRef.value) {
|
if (imageFormRef.value) {
|
||||||
imageFormRef.value.resetFields()
|
imageFormRef.value.resetFields()
|
||||||
}
|
}
|
||||||
@@ -564,10 +634,27 @@ const submitForm = async () => {
|
|||||||
|
|
||||||
await imageFormRef.value.validate((valid) => {
|
await imageFormRef.value.validate((valid) => {
|
||||||
if (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) {
|
if (editOr.value) {
|
||||||
imageForm.image_id = imageForm.id
|
submitData.image_id = submitData.id
|
||||||
delete imageForm.id
|
delete submitData.id
|
||||||
editMirror(imageForm).then(res => {
|
editMirror(submitData).then(res => {
|
||||||
if (res.data.code == 200) {
|
if (res.data.code == 200) {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
ElMessage.success('编辑成功')
|
ElMessage.success('编辑成功')
|
||||||
@@ -579,7 +666,7 @@ const submitForm = async () => {
|
|||||||
ElMessage.error('编辑失败')
|
ElMessage.error('编辑失败')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
addVirtualMirror(imageForm).then(res => {
|
addVirtualMirror(submitData).then(res => {
|
||||||
if (res.data.code == 200) {
|
if (res.data.code == 200) {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
ElMessage.success('添加成功')
|
ElMessage.success('添加成功')
|
||||||
@@ -882,6 +969,23 @@ onMounted(() => {
|
|||||||
border-radius: 4px;
|
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) {
|
@media (max-width: 768px) {
|
||||||
.image-info-cell {
|
.image-info-cell {
|
||||||
|
|||||||
@@ -203,8 +203,28 @@
|
|||||||
<el-form-item v-if="serverForm.server_type === 'dockerContainer'" label="Auth-ID">
|
<el-form-item v-if="serverForm.server_type === 'dockerContainer'" label="Auth-ID">
|
||||||
<el-input v-model="serverForm.auth_id" placeholder="服务器管理id" />
|
<el-input v-model="serverForm.auth_id" placeholder="服务器管理id" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="serverForm.server_type === 'hyperV'" label="Guacamole-ID">
|
<el-form-item v-if="serverForm.server_type === 'hyperV'" label="Guacamole配置">
|
||||||
<el-input v-model="serverForm.guacamole_id" placeholder="guacamole服务id" />
|
<el-select
|
||||||
|
v-model="serverForm.guacamole_id"
|
||||||
|
placeholder="请选择Guacamole配置"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
:loading="guacamoleLoading"
|
||||||
|
@change="handleGuacamoleChange"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in guacamoleList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="`${item.url} (${item.username})`"
|
||||||
|
:value="item.id"
|
||||||
|
>
|
||||||
|
<div class="guacamole-option">
|
||||||
|
<div class="option-main">{{ item.url }}</div>
|
||||||
|
<div class="option-sub">用户: {{ item.username }} | 创建时间: {{ item.created_at }}</div>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="serverForm.server_type === 'hyperV'" label="登录用户名">
|
<el-form-item v-if="serverForm.server_type === 'hyperV'" label="登录用户名">
|
||||||
<el-input v-model="serverForm.username" placeholder="服务器登录用户名" />
|
<el-input v-model="serverForm.username" placeholder="服务器登录用户名" />
|
||||||
@@ -283,6 +303,7 @@ import {
|
|||||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
||||||
import { getServer, addServer, editServer, deleteServer, getServerStatus } from '@/utils/acs/server'
|
import { getServer, addServer, editServer, deleteServer, getServerStatus } from '@/utils/acs/server'
|
||||||
import { copyDomText } from "@/utils/hide"
|
import { copyDomText } from "@/utils/hide"
|
||||||
|
import {getGuacamoleList} from '@/utils/acs/guacamole'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -334,6 +355,10 @@ const handleSizeChange = (val) => {
|
|||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const dialogType = ref('add') // 'add', 'edit'
|
const dialogType = ref('add') // 'add', 'edit'
|
||||||
|
|
||||||
|
// Guacamole 相关数据
|
||||||
|
const guacamoleList = ref([])
|
||||||
|
const guacamoleLoading = ref(false)
|
||||||
|
|
||||||
// 表单对象和规则
|
// 表单对象和规则
|
||||||
const serverFormRef = ref(null)
|
const serverFormRef = ref(null)
|
||||||
const serverForm = reactive({
|
const serverForm = reactive({
|
||||||
@@ -442,6 +467,49 @@ const updateStats = () => {
|
|||||||
serverStats.offline = serverData.value.length - serverStats.online
|
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 = () => {
|
const handleAdd = () => {
|
||||||
dialogType.value = 'add'
|
dialogType.value = 'add'
|
||||||
@@ -460,6 +528,11 @@ const handleAdd = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 如果是虚拟机类型,获取 Guacamole 列表
|
||||||
|
if (serverType.value === 'hyperV') {
|
||||||
|
fetchGuacamoleList()
|
||||||
|
}
|
||||||
|
|
||||||
// 确保在下一个 tick 重置表单验证状态
|
// 确保在下一个 tick 重置表单验证状态
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (serverFormRef.value) {
|
if (serverFormRef.value) {
|
||||||
@@ -480,6 +553,11 @@ const handleEdit = (row) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 如果是虚拟机类型,获取 Guacamole 列表
|
||||||
|
if (row.server_type === 'hyperV') {
|
||||||
|
fetchGuacamoleList()
|
||||||
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (serverFormRef.value) {
|
if (serverFormRef.value) {
|
||||||
serverFormRef.value.clearValidate()
|
serverFormRef.value.clearValidate()
|
||||||
@@ -1027,6 +1105,24 @@ onMounted(async () => {
|
|||||||
line-height: 1.2;
|
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 {
|
.form-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
|||||||
Reference in New Issue
Block a user