@@ -541,13 +541,22 @@
< el-tab-pane v-if = "TypeData == 'dockerContainer'" label="容器列表" >
< div class = "tab-header" >
< h3 class = "tab-title" > 容器管理 < / h3 >
< el-input
v-model = "containerBox.key"
placeholder = "搜索容器..."
class = "search-input "
:prefix-icon = "S earch"
clearable
/ >
< div class = "header-actions" >
< el-input
v-model = "containerBox.key"
placeholder = "搜索容器... "
class = "s earch-input "
:prefix-icon = "Search"
clearable
/ >
< el-button
type = "primary"
@click ="showAddContainerDialog"
:icon = "Plus"
>
添加容器
< / el-button >
< / div >
< / div >
< el-table
@@ -956,11 +965,261 @@
< / div >
< / template >
< / el-dialog >
<!-- 添加容器对话框 -- >
< el-dialog
v-model = "addContainerDialogVisible"
title = "添加容器"
width = "750px"
destroy -on -close
class = "container-dialog"
>
<!-- 服务器状态信息 -- >
< div class = "server-status-info" >
< el-alert
: title = "`服务器状态: ${serverMessage.state == 0 ? '离线' : '在线'}`"
: type = "serverMessage.state == 0 ? 'warning' : 'success'"
:closable = "false"
show -icon
>
< template # default >
< div class = "status-details" >
< p > < strong > 服务器名称 : < / strong > { { serverMessage . name || '未设置' } } < / p >
< p > < strong > 服务器IP : < / strong > { { serverMessage . server _ip || '未设置' } } < / p >
< p > < strong > 服务器ID : < / strong > { { serverMessage . server _id || '未知' } } < / p >
< p v-if = "serverMessage.state == 0" class="status-warning" >
< el -icon > < WarningFilled / > < / el-icon >
注意 : 服务器离线时创建的容器可能无法正常启动
< / p >
< / div >
< / template >
< / el-alert >
< / div >
< el-form
ref = "addContainerFormRef"
:model = "addContainerForm"
:rules = "addContainerRules"
label -width = " 140px "
>
< div class = "form-section" >
< div class = "section-title" > 基本信息 < / div >
< el-row :gutter = "20" >
< el-col :span = "12" >
< el-form-item label = "用户ID" prop = "user_id" >
< el-input
v-model = "addContainerForm.user_id"
placeholder = "请输入用户ID"
clearable
/ >
< / el-form-item >
< / el-col >
< el-col :span = "12" >
< el-form-item label = "容器套餐" prop = "plan_id" >
< el-select
v-model = "addContainerForm.plan_id"
placeholder = "请选择容器套餐"
style = "width: 100%"
clearable
:loading = "containerPlanLoading"
@focus ="fetchContainerPlanList"
@change ="handlePlanChange"
>
< el-option
v-for = "plan in containerPlanList"
:key = "plan.plan_id"
:label = "plan.name"
:value = "plan.plan_id"
>
< span style = "float: left" > { { plan . name } } < / span >
< span style = "float: right; color: #8492a6; font-size: 13px" >
¥ { { plan . price } } / 月
< / span >
< / el-option >
< / el-select >
< / el-form-item >
< / el-col >
< / el-row >
< / div >
< div class = "form-section" >
< div class = "section-title" > 价格与时间 < / div >
< el-row :gutter = "20" >
< el-col :span = "12" >
< el-form-item label = "购买价格" prop = "pay" >
< el-input-number
v-model = "addContainerForm.pay"
:min = "0"
:max = "999999"
:precision = "2"
placeholder = "请输入购买价格"
style = "width: 100%"
controls -position = " right "
readonly
>
< template # prepend > ¥ < / template >
< / el-input-number >
< div v-if = "selectedPlanPrice && addContainerForm.pay_months" class="price-calculation" >
< small class = "text-muted" >
{ { selectedPlanPrice } } / 月 × { { addContainerForm . pay _months } } 月 = ¥ { { addContainerForm . pay } }
< / small >
< / div >
< / el-form-item >
< / el-col >
< el-col :span = "12" >
< el-form-item label = "购买时间(月)" prop = "pay_months" >
< el-input-number
v-model = "addContainerForm.pay_months"
:min = "1"
:max = "120"
placeholder = "请输入购买月数"
style = "width: 100%"
controls -position = " right "
@change ="calculateExpireTime"
/ >
< / el-form-item >
< / el-col >
< el-col :span = "24" >
< el-form-item label = "到期时间" prop = "expire_time" >
< el-date-picker
v-model = "addContainerForm.expire_time"
type = "datetime"
placeholder = "请选择到期时间"
style = "width: 100%"
format = "YYYY-MM-DD HH:mm:ss"
value -format = " X "
/ >
< / el-form-item >
< / el-col >
< / el-row >
< / div >
< div class = "form-section" >
< div class = "section-title" > 镜像与支付 < / div >
< el-row :gutter = "20" >
< el-col :span = "12" >
< el-form-item label = "镜像" prop = "image_id" >
< el-select
v-model = "addContainerForm.image_id"
placeholder = "请选择镜像"
style = "width: 100%"
clearable
:loading = "containerMirrorLoading"
@focus ="fetchContainerMirrorList"
>
< el-option
v-for = "mirror in containerMirrorList"
:key = "mirror.id"
:label = "mirror.name"
:value = "mirror.image_id"
>
< span style = "float: left" > { { mirror . name } } < / span >
< span style = "float: right; color: #8492a6; font-size: 13px" >
{ { mirror . size } } MB
< / span >
< / el-option >
< / el-select >
< / el-form-item >
< / el-col >
< el-col :span = "12" >
< el-form-item label = "支付类型" prop = "pay_type" >
< el-select
v-model = "addContainerForm.pay_type"
placeholder = "请选择支付类型"
style = "width: 100%"
clearable
>
< el-option label = "余额" value = "0" / >
<!-- < el-option label = "支付宝" value = "alipay" / >
< el-option label = "微信支付" value = "wechat" / >
< el-option label = "银行卡" value = "bank" / > -- >
< / el-select >
< / el-form-item >
< / el-col >
< / el-row >
< / div >
< div class = "form-section" >
< div class = "section-title" > 网络配置 < / div >
< el-row :gutter = "20" >
< el-col :span = "12" >
< el-form-item label = "网络类型" prop = "networkType" >
< el-select
v-model = "addContainerForm.networkType"
placeholder = "请选择网络类型"
style = "width: 100%"
@change ="handleNetworkTypeChange"
>
< el-option label = "无网络配置" value = "" / >
< el-option label = "端口转发" value = "port_forward" / >
< el-option label = "反向代理" value = "nginx" / >
< el-option label = "浮动IP" value = "floating_ip" / >
< / el-select >
< / el-form-item >
< / el-col >
< el-col :span = "12" v-if = "addContainerForm.networkType === 'port_forward' || addContainerForm.networkType === 'nginx'" >
< el -form -item label = "容器端口" prop = "containerPort" >
< el-input
v-model = "addContainerForm.containerPort"
:min = "1"
:max = "65535"
placeholder = "请输入容器端口"
style = "width: 100%"
controls -position = " right "
/ >
< / el-form-item >
< / el-col >
< el-col :span = "24" v-if = "addContainerForm.networkType === 'nginx'" >
< el -form -item label = "域名" prop = "domain" >
< el-input
v-model = "addContainerForm.domain"
placeholder = "请输入域名"
/ >
< / el-form-item >
< / el-col >
< el-col :span = "24" v-if = "addContainerForm.networkType === 'floating_ip'" >
< el -alert
title = "浮动IP配置"
type = "info"
description = "浮动IP类型无需额外配置参数,系统将自动分配可用的浮动IP"
:closable = "false"
show -icon
/ >
< / el-col >
< / el-row >
< / div >
< div class = "form-section" >
< div class = "section-title" > 环境配置 < / div >
< el-row :gutter = "20" >
< el-col :span = "24" >
< el-form-item label = "环境变量" prop = "env" >
< el-input
v-model = "addContainerForm.env"
type = "textarea"
:rows = "3"
placeholder = "请输入环境变量(可选),格式:KEY1=VALUE1,KEY2=VALUE2"
/ >
< / el-form-item >
< / el-col >
< / el-row >
< / div >
< / el-form >
< template # footer >
< div class = "dialog-footer" >
< el-button @click ="cancelAddContainer" > 取消 < / el -button >
< el-button type = "primary" @click ="confirmAddContainer" :loading = "addContainerLoading" >
确定
< / el-button >
< / div >
< / template >
< / el-dialog >
< / div >
< / template >
< script setup >
import { reactive , ref , onMounted , watch } from "vue" ;
import { reactive , ref , onMounted , watch , computed } from "vue" ;
import { useRouter , useRoute } from "vue-router" ;
import {
selectServer ,
@@ -977,8 +1236,10 @@ import {
getDiskInfo ,
getRealDisk ,
getTraffic ,
getTotalTraffic
getTotalTraffic ,
addContainer
} from "@/utils/acs/server" ;
import { getMirrorList } from "@/utils/acs/mirror" ;
import { ElMessage , ElNotification } from 'element-plus' ;
import { copyDomText } from "@/utils/hide" ;
import { getUserInfoV1 } from "@/utils/acs/user" ;
@@ -1204,6 +1465,7 @@ const initData = async () => {
// 并行获取服务器硬件和流量信息(不阻塞主要功能)
Promise . allSettled ( [
getDiskInfoData ( ) ,
getRealDiskData ( ) ,
getTrafficData ( ) ,
@@ -2015,6 +2277,287 @@ const getit = async () => {
}
} ;
// 添加容器相关状态
const addContainerDialogVisible = ref ( false ) ;
const addContainerLoading = ref ( false ) ;
const addContainerFormRef = ref ( null ) ;
const addContainerForm = ref ( {
user _id : '' ,
server _id : '' ,
plan _id : '' ,
pay : null ,
expire _time : null ,
image _id : '' ,
pay _months : null ,
proxy : '' ,
pay _type : '' ,
env : '' ,
// 网络配置相关字段
networkType : '' ,
containerPort : null ,
domain : ''
} ) ;
// 容器套餐和镜像数据
const containerPlanList = ref ( [ ] ) ;
const containerMirrorList = ref ( [ ] ) ;
const containerPlanLoading = ref ( false ) ;
const containerMirrorLoading = ref ( false ) ;
// 获取选中套餐的价格
const selectedPlanPrice = computed ( ( ) => {
if ( ! addContainerForm . value . plan _id ) return null ;
const selectedPlan = containerPlanList . value . find ( plan => plan . plan _id === addContainerForm . value . plan _id ) ;
return selectedPlan ? parseFloat ( selectedPlan . price ) : null ;
} ) ;
// 容器表单验证规则 - 使用computed实现动态验证
const addContainerRules = computed ( ( ) => {
const rules = {
user _id : [
{ required : true , message : '请输入用户ID' , trigger : 'blur' }
] ,
plan _id : [
{ required : true , message : '请选择容器套餐' , trigger : 'change' }
] ,
pay : [
{ required : true , message : '请输入购买价格' , trigger : 'blur' }
] ,
expire _time : [
{ required : true , message : '请选择到期时间' , trigger : 'change' }
]
} ;
// 根据网络类型动态添加验证规则
if ( addContainerForm . value . networkType === 'port_forward' || addContainerForm . value . networkType === 'nginx' ) {
rules . containerPort = [
{ required : true , message : '请输入容器端口' , trigger : 'blur' } ,
{ type : 'number' , min : 1 , max : 65535 , message : '请输入有效的端口(1-65535)' , trigger : 'blur' }
] ;
}
if ( addContainerForm . value . networkType === 'nginx' ) {
rules . domain = [
{ required : true , message : '请输入域名' , trigger : 'blur' } ,
{ pattern : /^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/ , message : '请输入有效的域名' , trigger : 'blur' }
] ;
}
return rules ;
} ) ;
// 获取容器套餐列表
const fetchContainerPlanList = async ( ) => {
if ( containerPlanList . value . length > 0 ) return ; // 已加载过,不重复加载
containerPlanLoading . value = true ;
try {
const response = await getServerPlan ( {
server _id : route . query . server _id ,
count : 100
} ) ;
console . log ( "获取容器套餐列表1111: " , response ) ;
if ( response && response . data && response . data . code === 200 ) {
containerPlanList . value = response . data . data || [ ] ;
} else {
ElMessage . error ( '获取容器套餐列表失败' ) ;
}
} catch ( error ) {
console . error ( '获取容器套餐列表出错:' , error ) ;
ElMessage . error ( '获取容器套餐列表出错' ) ;
} finally {
containerPlanLoading . value = false ;
}
} ;
// 获取容器镜像列表
const fetchContainerMirrorList = async ( ) => {
if ( containerMirrorList . value . length > 0 ) return ; // 已加载过,不重复加载
containerMirrorLoading . value = true ;
try {
const response = await getMirrorList ( route . query . server _id ) ;
console . log ( "获取镜像列表1111: " , response ) ;
if ( response && response . data && response . data . code === 200 ) {
containerMirrorList . value = response . data . data || [ ] ;
} else {
ElMessage . error ( '获取镜像列表失败' ) ;
}
} catch ( error ) {
console . error ( '获取镜像列表出错:' , error ) ;
ElMessage . error ( '获取镜像列表出错' ) ;
} finally {
containerMirrorLoading . value = false ;
}
} ;
// 处理套餐变化
const handlePlanChange = ( planId ) => {
if ( ! planId ) {
addContainerForm . value . pay = null ;
return ;
}
// 计算总价格和到期时间
calculateTotalPrice ( ) ;
if ( addContainerForm . value . pay _months ) {
calculateExpireTime ( ) ;
}
} ;
// 计算总价格
const calculateTotalPrice = ( ) => {
if ( ! selectedPlanPrice . value || ! addContainerForm . value . pay _months ) {
addContainerForm . value . pay = selectedPlanPrice . value || null ;
return ;
}
// 计算总价格 = 套餐价格 × 购买月数
addContainerForm . value . pay = parseFloat ( ( selectedPlanPrice . value * addContainerForm . value . pay _months ) . toFixed ( 2 ) ) ;
} ;
// 计算到期时间
const calculateExpireTime = ( ) => {
if ( ! addContainerForm . value . pay _months ) {
addContainerForm . value . expire _time = null ;
return ;
}
// 获取当前时间
const now = new Date ( ) ;
// 计算到期时间(当前时间 + 购买月数)
const expireDate = new Date ( now ) ;
expireDate . setMonth ( expireDate . getMonth ( ) + addContainerForm . value . pay _months ) ;
// 转换为时间戳(秒)
addContainerForm . value . expire _time = Math . floor ( expireDate . getTime ( ) / 1000 ) ;
// 同时重新计算总价格
calculateTotalPrice ( ) ;
} ;
// 处理网络类型变化
const handleNetworkTypeChange = ( newType ) => {
// 清空网络配置相关字段
addContainerForm . value . containerPort = null ;
addContainerForm . value . domain = '' ;
// 清除验证错误
if ( addContainerFormRef . value ) {
addContainerFormRef . value . clearValidate ( [ 'containerPort' , 'domain' ] ) ;
}
} ;
// 显示添加容器对话框
const showAddContainerDialog = ( ) => {
addContainerDialogVisible . value = true ;
// 重置表单
addContainerForm . value = {
user _id : '' ,
server _id : route . query . server _id ,
plan _id : '' ,
pay : null ,
expire _time : null ,
image _id : '' ,
pay _months : null ,
proxy : '' ,
pay _type : '' ,
env : '' ,
// 网络配置相关字段
networkType : '' ,
containerPort : null ,
domain : ''
} ;
// 清除验证
if ( addContainerFormRef . value ) {
addContainerFormRef . value . clearValidate ( ) ;
}
// 预加载数据
fetchContainerPlanList ( ) ;
fetchContainerMirrorList ( ) ;
} ;
// 取消添加容器
const cancelAddContainer = ( ) => {
addContainerDialogVisible . value = false ;
} ;
// 确认添加容器
const confirmAddContainer = async ( ) => {
if ( ! addContainerFormRef . value ) return ;
try {
// 验证表单
await addContainerFormRef . value . validate ( ) ;
addContainerLoading . value = true ;
// 构建网络配置数据
let proxyData = '' ;
if ( addContainerForm . value . networkType ) {
let proxy _data = [ {
type : addContainerForm . value . networkType
} ] ;
// 根据类型添加对应参数
if ( addContainerForm . value . networkType === 'port_forward' ) {
proxy _data [ 0 ] . container _port = addContainerForm . value . containerPort ;
} else if ( addContainerForm . value . networkType === 'nginx' ) {
proxy _data [ 0 ] . container _port = addContainerForm . value . containerPort ;
proxy _data [ 0 ] . domain = addContainerForm . value . domain ;
}
// floating_ip 类型不需要额外参数
proxyData = JSON . stringify ( proxy _data ) ;
}
// 准备API参数
const apiParams = {
user _id : addContainerForm . value . user _id ,
server _id : addContainerForm . value . server _id ,
plan _id : addContainerForm . value . plan _id ,
pay : addContainerForm . value . pay ,
expire _time : addContainerForm . value . expire _time ,
image _id : addContainerForm . value . image _id || '' ,
pay _months : addContainerForm . value . pay _months ? . toString ( ) || '' ,
proxy : proxyData ,
pay _type : addContainerForm . value . pay _type || '' ,
env : addContainerForm . value . env || ''
} ;
console . log ( '添加容器参数:' , apiParams ) ;
// 调用API
const response = await addContainer ( apiParams ) ;
if ( response && response . data && response . data . code === 200 ) {
ElMessage . success ( '容器创建成功' ) ;
addContainerDialogVisible . value = false ;
// 刷新容器列表
containerBox . page = 1 ;
let cons = await getContainer ( containerBox ) ;
if ( cons && cons . data ) {
user _servers . value = cons . data . data || [ ] ;
total . value = cons . data . count || 0 ;
}
} else {
ElMessage . error ( '创建失败: ' + ( response . data ? . message || '未知错误' ) ) ;
}
} catch ( error ) {
console . error ( '添加容器出错:' , error ) ;
if ( error . message ) {
ElMessage . error ( '表单验证失败: ' + error . message ) ;
} else {
ElMessage . error ( '添加容器出错' ) ;
}
} finally {
addContainerLoading . value = false ;
}
} ;
// 导入其他需要的组件
import { ElMessageBox } from 'element-plus' ;
< / script >
@@ -2362,6 +2905,12 @@ import { ElMessageBox } from 'element-plus';
width : 240 px ;
}
. header - actions {
display : flex ;
align - items : center ;
gap : 12 px ;
}
. action - btns {
display : flex ;
gap : 8 px ;
@@ -2451,6 +3000,54 @@ import { ElMessageBox } from 'element-plus';
margin - top : 16 px ;
}
/* 服务器状态信息样式 */
. server - status - info {
margin - bottom : 20 px ;
}
. status - details {
margin - top : 12 px ;
}
. status - details p {
margin : 8 px 0 ;
font - size : 14 px ;
line - height : 1.5 ;
}
. status - details strong {
color : # 303133 ;
font - weight : 600 ;
}
. status - warning {
display : flex ;
align - items : center ;
gap : 8 px ;
color : # E6A23C ;
font - weight : 500 ;
margin - top : 12 px ;
padding : 8 px 12 px ;
background - color : rgba ( 230 , 162 , 60 , 0.1 ) ;
border - radius : 4 px ;
border - left : 3 px solid # E6A23C ;
}
. status - warning . el - icon {
font - size : 16 px ;
}
/* 价格计算显示样式 */
. price - calculation {
margin - top : 4 px ;
}
. price - calculation . text - muted {
color : # 909399 ;
font - size : 12 px ;
line - height : 1.4 ;
}
/* 响应式设计 */
@ media screen and ( max - width : 1200 px ) {
. server - info {