feat:添加服务器新建容器 #4
@@ -53,7 +53,14 @@ export const selectServerPlan = data => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
/**新增容器 */
|
||||||
|
export const addContainer = data => {
|
||||||
|
return http2.post("/v1/admin/container/add_container", data, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
/**删除容器网络 */
|
/**删除容器网络 */
|
||||||
export const deleteContainerNetwork = data => {
|
export const deleteContainerNetwork = data => {
|
||||||
return http2.post("/v1/user/container/delete_connect", data, {
|
return http2.post("/v1/user/container/delete_connect", data, {
|
||||||
|
|||||||
+4
-4
@@ -93,10 +93,10 @@ const loginRules = {
|
|||||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
|
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
password: [
|
// password: [
|
||||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
// { required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
|
// { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
|
||||||
]
|
// ]
|
||||||
}
|
}
|
||||||
|
|
||||||
const forgetPassword = () => {
|
const forgetPassword = () => {
|
||||||
|
|||||||
@@ -541,13 +541,22 @@
|
|||||||
<el-tab-pane v-if="TypeData == 'dockerContainer'" label="容器列表">
|
<el-tab-pane v-if="TypeData == 'dockerContainer'" label="容器列表">
|
||||||
<div class="tab-header">
|
<div class="tab-header">
|
||||||
<h3 class="tab-title">容器管理</h3>
|
<h3 class="tab-title">容器管理</h3>
|
||||||
<el-input
|
<div class="header-actions">
|
||||||
v-model="containerBox.key"
|
<el-input
|
||||||
placeholder="搜索容器..."
|
v-model="containerBox.key"
|
||||||
class="search-input"
|
placeholder="搜索容器..."
|
||||||
:prefix-icon="Search"
|
class="search-input"
|
||||||
clearable
|
:prefix-icon="Search"
|
||||||
/>
|
clearable
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="showAddContainerDialog"
|
||||||
|
:icon="Plus"
|
||||||
|
>
|
||||||
|
添加容器
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table
|
<el-table
|
||||||
@@ -956,11 +965,261 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, onMounted, watch } from "vue";
|
import { reactive, ref, onMounted, watch, computed } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import {
|
import {
|
||||||
selectServer,
|
selectServer,
|
||||||
@@ -977,8 +1236,10 @@ import {
|
|||||||
getDiskInfo,
|
getDiskInfo,
|
||||||
getRealDisk,
|
getRealDisk,
|
||||||
getTraffic,
|
getTraffic,
|
||||||
getTotalTraffic
|
getTotalTraffic,
|
||||||
|
addContainer
|
||||||
} from "@/utils/acs/server";
|
} from "@/utils/acs/server";
|
||||||
|
import { getMirrorList } from "@/utils/acs/mirror";
|
||||||
import { ElMessage, ElNotification } from 'element-plus';
|
import { ElMessage, ElNotification } from 'element-plus';
|
||||||
import { copyDomText } from "@/utils/hide";
|
import { copyDomText } from "@/utils/hide";
|
||||||
import { getUserInfoV1 } from "@/utils/acs/user";
|
import { getUserInfoV1 } from "@/utils/acs/user";
|
||||||
@@ -1204,6 +1465,7 @@ const initData = async () => {
|
|||||||
|
|
||||||
// 并行获取服务器硬件和流量信息(不阻塞主要功能)
|
// 并行获取服务器硬件和流量信息(不阻塞主要功能)
|
||||||
Promise.allSettled([
|
Promise.allSettled([
|
||||||
|
|
||||||
getDiskInfoData(),
|
getDiskInfoData(),
|
||||||
getRealDiskData(),
|
getRealDiskData(),
|
||||||
getTrafficData(),
|
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';
|
import { ElMessageBox } from 'element-plus';
|
||||||
</script>
|
</script>
|
||||||
@@ -2362,6 +2905,12 @@ import { ElMessageBox } from 'element-plus';
|
|||||||
width: 240px;
|
width: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.action-btns {
|
.action-btns {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -2451,6 +3000,54 @@ import { ElMessageBox } from 'element-plus';
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 服务器状态信息样式 */
|
||||||
|
.server-status-info {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-details {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-details p {
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-details strong {
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #E6A23C;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: rgba(230, 162, 60, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #E6A23C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-warning .el-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 价格计算显示样式 */
|
||||||
|
.price-calculation {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-calculation .text-muted {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
/* 响应式设计 */
|
/* 响应式设计 */
|
||||||
@media screen and (max-width: 1200px) {
|
@media screen and (max-width: 1200px) {
|
||||||
.server-info {
|
.server-info {
|
||||||
|
|||||||
Reference in New Issue
Block a user