Merge pull request 'feat:添加服务器新建容器' (#4) from master into deploy
Reviewed-on: lin/ApiServer-Web-admin_dashboard_pc#4
This commit was merged in pull request #4.
This commit is contained in:
@@ -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 => {
|
||||
return http2.post("/v1/user/container/delete_connect", data, {
|
||||
|
||||
+4
-4
@@ -93,10 +93,10 @@ const loginRules = {
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
|
||||
]
|
||||
// password: [
|
||||
// { required: true, message: '请输入密码', trigger: 'blur' },
|
||||
// { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
|
||||
// ]
|
||||
}
|
||||
|
||||
const forgetPassword = () => {
|
||||
|
||||
@@ -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="Search"
|
||||
clearable
|
||||
/>
|
||||
<div class="header-actions">
|
||||
<el-input
|
||||
v-model="containerBox.key"
|
||||
placeholder="搜索容器..."
|
||||
class="search-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: 240px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -2451,6 +3000,54 @@ import { ElMessageBox } from 'element-plus';
|
||||
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) {
|
||||
.server-info {
|
||||
|
||||
Reference in New Issue
Block a user