feat:对接ACS容器和虚拟机
This commit is contained in:
@@ -195,6 +195,12 @@
|
||||
<el-form-item label="展示名称" prop="show_name">
|
||||
<el-input v-model="form.show_name" placeholder="请输入展示名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类ID" prop="class_id">
|
||||
<el-input v-model="form.class_id" placeholder="请输入分类ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类名称" prop="class_name">
|
||||
<el-input v-model="form.class_name" placeholder="请输入分类名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标">
|
||||
<div class="image-icon-upload">
|
||||
<img v-if="form.image_ico" :src="mainUrl + form.image_ico" class="preview-icon" />
|
||||
@@ -441,7 +447,9 @@ const form = reactive({
|
||||
image_ico: '',
|
||||
tag: '',
|
||||
server_id: '',
|
||||
env: ''
|
||||
env: '',
|
||||
class_id: '',
|
||||
class_name: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
@@ -449,7 +457,9 @@ const rules = {
|
||||
show_name: [{ required: true, message: '请输入展示名称', trigger: 'blur' }],
|
||||
tag: [{ required: true, message: '请输入版本号', trigger: 'blur' }],
|
||||
plan_id: [{ required: true, message: '请选择套餐', trigger: 'change' }],
|
||||
description: [{ required: true, message: '请输入镜像描述', trigger: 'blur' }]
|
||||
description: [{ required: true, message: '请输入镜像描述', trigger: 'blur' }],
|
||||
class_id: [{ required: false, message: '请输入分类ID', trigger: 'blur' }],
|
||||
class_name: [{ required: false, message: '请输入分类名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const netform = reactive({
|
||||
|
||||
@@ -141,6 +141,11 @@
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
|
||||
<el-button
|
||||
type="primary" @click="handleOpen">
|
||||
<el-icon><VideoPlay /></el-icon>开通
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
:disabled="vmInfo.state == 2 || vmInfo.state == 0 || vmInfo.state == 1 || vmInfo.state == 4 || vmInfo.state == 5 || vmInfo.state == 6"
|
||||
@@ -155,6 +160,20 @@
|
||||
>
|
||||
<el-icon><VideoPause /></el-icon>关机
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
:disabled="vmInfo.state != 2"
|
||||
@click="handlePause"
|
||||
>
|
||||
<el-icon><VideoPause /></el-icon>暂停
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
:disabled="vmInfo.state != 7"
|
||||
@click="handleUnpause"
|
||||
>
|
||||
<el-icon><VideoPlay /></el-icon>恢复
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
:disabled="vmInfo.state != 2"
|
||||
@@ -189,13 +208,20 @@
|
||||
>
|
||||
<el-icon><Warning /></el-icon>退出救援模式
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="vmInfo.state == 0 || vmInfo.state == 1 || vmInfo.state == 4 || vmInfo.state == 5 || vmInfo.state == 6"
|
||||
@click="handleDelete"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>删除虚拟机
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="content-wrapper">
|
||||
<el-tabs type="border-card" class="main-tabs">
|
||||
<el-tabs type="border-card" class="main-tabs" v-model="activeTabName" @tab-click="handleTabClick">
|
||||
<!-- 虚拟机操作日志 -->
|
||||
<el-tab-pane label="虚拟机操作日志">
|
||||
<el-tab-pane label="虚拟机操作日志" name="0">
|
||||
<div class="tab-header">
|
||||
<h3 class="tab-title">操作日志</h3>
|
||||
</div>
|
||||
@@ -229,7 +255,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 实例监控 -->
|
||||
<el-tab-pane label="实例监控">
|
||||
<el-tab-pane label="实例监控" name="1">
|
||||
<div class="tab-header">
|
||||
<h3 class="tab-title">实例监控</h3>
|
||||
<div class="date-filter">
|
||||
@@ -294,7 +320,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 虚拟机网络管理 -->
|
||||
<el-tab-pane label="虚拟机网络管理">
|
||||
<el-tab-pane label="虚拟机网络管理" name="2">
|
||||
<div class="tab-header">
|
||||
<h3 class="tab-title">网络管理</h3>
|
||||
<el-button
|
||||
@@ -360,7 +386,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 端口管理 -->
|
||||
<el-tab-pane label="端口管理">
|
||||
<el-tab-pane label="端口管理" name="3">
|
||||
<div class="tab-header">
|
||||
<h3 class="tab-title">端口列表</h3>
|
||||
<el-button
|
||||
@@ -414,7 +440,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 快照列表 -->
|
||||
<el-tab-pane label="快照列表">
|
||||
<el-tab-pane label="快照列表" name="4">
|
||||
<div class="tab-header">
|
||||
<h3 class="tab-title">快照管理</h3>
|
||||
<el-button
|
||||
@@ -463,7 +489,7 @@
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 数据卷信息 -->
|
||||
<el-tab-pane label="数据卷信息">
|
||||
<el-tab-pane label="数据卷信息" name="5">
|
||||
<div class="tab-header">
|
||||
<h3 class="tab-title">数据卷列表</h3>
|
||||
<el-button
|
||||
@@ -774,7 +800,11 @@ import {
|
||||
addVolume,
|
||||
deleteVolume,
|
||||
updateVolume,
|
||||
getInstanceList
|
||||
getInstanceList,
|
||||
openInstance,
|
||||
pauseInstance,
|
||||
unpauseInstance,
|
||||
deleteInstance
|
||||
} from '@/utils/acs/server';
|
||||
import {
|
||||
Mirrorinfo,
|
||||
@@ -798,6 +828,14 @@ const route = useRoute();
|
||||
const vmInfo = ref({});
|
||||
const loading = ref(false);
|
||||
|
||||
// 标签页相关
|
||||
const activeTabName = ref('0'); // 默认选中第一个标签
|
||||
|
||||
// 处理标签页点击
|
||||
const handleTabClick = (tab) => {
|
||||
localStorage.setItem('vmDetailActiveTab', tab.index);
|
||||
};
|
||||
|
||||
// 端口管理
|
||||
const portsList = ref([]);
|
||||
const portsLoading = ref(false);
|
||||
@@ -1004,6 +1042,17 @@ const reinstallRules = {
|
||||
const reinstalling = ref(false);
|
||||
const imagesList = ref([]);
|
||||
|
||||
//开通虚拟机
|
||||
const handleOpen = async () => {
|
||||
const res = await openInstance(route.query.instance_id)
|
||||
console.log("开通虚拟机",res)
|
||||
if (res.data.code === 200) {
|
||||
ElMessage.success(res.data.msg);
|
||||
} else {
|
||||
ElMessage.error(res.data.msg);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取镜像列表
|
||||
const fetchImagesList = async () => {
|
||||
try {
|
||||
@@ -1111,6 +1160,12 @@ const fetchDataVolumesList = async () => {
|
||||
// 初始化数据
|
||||
onMounted(() => {
|
||||
if (route.query.instance_id) {
|
||||
// 恢复上次选中的标签页
|
||||
const savedTab = localStorage.getItem('vmDetailActiveTab');
|
||||
if (savedTab) {
|
||||
activeTabName.value = savedTab;
|
||||
}
|
||||
|
||||
fetchVmInfo();
|
||||
fetchPortsList();
|
||||
fetchLogsList();
|
||||
@@ -1424,6 +1479,83 @@ const handleExitRescue = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 暂停虚拟机
|
||||
const handlePause = async () => {
|
||||
try {
|
||||
ElMessageBox.confirm('确定要暂停该虚拟机吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const formData = new FormData();
|
||||
const res = await pauseInstance(formData, route.query.instance_id);
|
||||
if (res && res.data && res.data.code === 200) {
|
||||
ElMessage.success('暂停指令已发送');
|
||||
setTimeout(() => {
|
||||
fetchVmInfo();
|
||||
}, 2000);
|
||||
} else {
|
||||
ElMessage.error('暂停失败: ' + (res.data.message || '未知错误'));
|
||||
}
|
||||
}).catch(() => {});
|
||||
} catch (error) {
|
||||
console.error('暂停虚拟机出错:', error);
|
||||
ElMessage.error('暂停虚拟机出错');
|
||||
}
|
||||
};
|
||||
|
||||
// 恢复虚拟机
|
||||
const handleUnpause = async () => {
|
||||
try {
|
||||
ElMessageBox.confirm('确定要恢复该虚拟机吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await unpauseInstance(route.query.instance_id);
|
||||
if (res && res.data && res.data.code === 200) {
|
||||
ElMessage.success('恢复指令已发送');
|
||||
setTimeout(() => {
|
||||
fetchVmInfo();
|
||||
}, 2000);
|
||||
} else {
|
||||
ElMessage.error('恢复失败: ' + (res.data.message || '未知错误'));
|
||||
}
|
||||
}).catch(() => {});
|
||||
} catch (error) {
|
||||
console.error('恢复虚拟机出错:', error);
|
||||
ElMessage.error('恢复虚拟机出错');
|
||||
}
|
||||
};
|
||||
|
||||
// 删除虚拟机
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
ElMessageBox.confirm('确定要删除该虚拟机吗?此操作不可恢复!', '警告', {
|
||||
confirmButtonText: '确定删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error',
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: '<div style="color: red; font-weight: bold;">⚠️ 警告:删除虚拟机将永久删除所有数据,此操作不可恢复!</div>'
|
||||
}).then(async () => {
|
||||
const formData = new FormData();
|
||||
const res = await deleteInstance(route.query.instance_id, formData);
|
||||
if (res && res.data && res.data.code === 200) {
|
||||
ElMessage.success('虚拟机已删除');
|
||||
// 删除成功后返回虚拟机列表页面
|
||||
setTimeout(() => {
|
||||
router.push('/acs/nodes');
|
||||
}, 2000);
|
||||
} else {
|
||||
ElMessage.error('删除失败: ' + (res.data.message || '未知错误'));
|
||||
}
|
||||
}).catch(() => {});
|
||||
} catch (error) {
|
||||
console.error('删除虚拟机出错:', error);
|
||||
ElMessage.error('删除虚拟机出错');
|
||||
}
|
||||
};
|
||||
|
||||
function statusMap(data) {
|
||||
const statusMap = {
|
||||
0: "未支付",
|
||||
@@ -1432,7 +1564,8 @@ function statusMap(data) {
|
||||
3: "关机",
|
||||
4: "重装中",
|
||||
5: "正在创建快照",
|
||||
6: "正在恢复快照"
|
||||
6: "正在恢复快照",
|
||||
7: "已暂停"
|
||||
};
|
||||
return statusMap[data] || "未知状态";
|
||||
}
|
||||
@@ -1454,7 +1587,8 @@ function statusColor(data) {
|
||||
3: "secondary", // 关机
|
||||
4: "info", // 重装中
|
||||
5: "primary", // 正在创建快照
|
||||
6: "info" // 正在恢复快照
|
||||
6: "info", // 正在恢复快照
|
||||
7: "warning" // 已暂停
|
||||
};
|
||||
return statusMap[data] || "default"; // 默认颜色类名
|
||||
}
|
||||
@@ -1571,6 +1705,8 @@ const getStatusType = (state) => {
|
||||
return 'success';
|
||||
} else if (state == 4 || state == 5 || state == 6) {
|
||||
return 'warning';
|
||||
} else if (state == 7) {
|
||||
return 'info'; // 暂停状态
|
||||
} else {
|
||||
return 'danger';
|
||||
}
|
||||
@@ -1593,6 +1729,8 @@ const getStatusText = (state) => {
|
||||
return '创建快照中';
|
||||
case 6:
|
||||
return '恢复快照中';
|
||||
case 7:
|
||||
return '已暂停';
|
||||
default:
|
||||
return '未知状态';
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div id="container-terminal"></div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
body{
|
||||
padding: 0%;
|
||||
margin:5px;
|
||||
background-color: #000;
|
||||
height: 100%;
|
||||
}
|
||||
#container-terminal{
|
||||
width:100%;
|
||||
height:85%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Terminal } from 'xterm'
|
||||
import 'xterm/css/xterm.css'
|
||||
import {useRoute,useRouter} from "vue-router"
|
||||
import {message} from "../../../utils/message"
|
||||
import {onMounted,onUnmounted} from "vue"
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
onMounted(() =>{
|
||||
checkProtocol()
|
||||
stopHeartBeat()
|
||||
})
|
||||
|
||||
let heartbeatTimerId = null;
|
||||
function startHeartBeat(data:any){
|
||||
heartbeatTimerId = setInterval(() => {
|
||||
data.send("")
|
||||
},10000);
|
||||
}
|
||||
function stopHeartBeat(){
|
||||
clearInterval(heartbeatTimerId)
|
||||
}
|
||||
//判断协议
|
||||
function checkProtocol(){
|
||||
if(window.location.protocol === "https:" &&
|
||||
String(route.query.server_addr).split(":")[0]!== "ws"){
|
||||
console.log("当前页面使用的是https协议")
|
||||
router.back();
|
||||
}else{
|
||||
console.log("当前页面使用的是HTTP协议")
|
||||
function resizeTerminal(term:any){
|
||||
const container = document.getElementById("container-terminal")
|
||||
if(!container) {
|
||||
console.error("Container terminal not found")
|
||||
return;
|
||||
}
|
||||
const style = window.getComputedStyle(container)
|
||||
const charWidth = parseInt(style.getPropertyValue("--xterm-char-width"),10) || 10;
|
||||
const charHeight = parseInt(style.getPropertyValue("--xterm-char-height"),10) || 20;
|
||||
const width = container.offsetWidth;
|
||||
const height = container.offsetHeight;
|
||||
const cols = Math.floor(width /charWidth);
|
||||
const rows = Math.floor(height / charHeight);
|
||||
term.resize(cols,rows);
|
||||
}
|
||||
function getQueryVal(variable:any){
|
||||
const query = window.location.search.substring(1);
|
||||
const vars = query.split("&");
|
||||
for(let i = 0;i< vars.length;i++){
|
||||
const pair = vars[i].split("=");
|
||||
if(pair[0] === variable){
|
||||
return pair[1]
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
let token = route.query.token;
|
||||
if(!token){
|
||||
throw new Error("token not found")
|
||||
}
|
||||
let server_addr = route.query.server_addr
|
||||
if(!server_addr){
|
||||
throw new Error("server_addr not found")
|
||||
}
|
||||
let command = getQueryVal("cmd")
|
||||
if(!command) {
|
||||
command = "/bin/bash"
|
||||
}
|
||||
let websocket = new WebSocket(
|
||||
"ws://" + server_addr + "/console/" + "exec/" + token + "," + window.btoa(command)
|
||||
)
|
||||
websocket.onerror = function(event:any){
|
||||
console.error("WebSocket error:",event)
|
||||
message("网络波动",{type:"error"});
|
||||
router.back();
|
||||
}
|
||||
websocket.onopen = function(){
|
||||
let term = new Terminal({
|
||||
cols: 300,
|
||||
rows:30,
|
||||
cursorBlink:true
|
||||
})
|
||||
console.log("打开终端")
|
||||
startHeartBeat(websocket)
|
||||
term.onData(function(data:any){
|
||||
websocket.send(data)
|
||||
})
|
||||
term.onTitleChange(function(title:any){
|
||||
document.title = title
|
||||
})
|
||||
term.open(document.getElementById("container-terminal")!);
|
||||
resizeTerminal(term);
|
||||
window.addEventListener("resize",()=>{
|
||||
resizeTerminal(term)
|
||||
})
|
||||
websocket.onmessage = function (evt) {
|
||||
term.write(evt.data)
|
||||
console.log("发送消息",evt)
|
||||
}
|
||||
websocket.onclose = function(){
|
||||
term.write("session terminated");
|
||||
term.dispose()
|
||||
}
|
||||
websocket.onerror = function (evt){
|
||||
if(evt) {
|
||||
message("网络波动",{type:"error"})
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
@@ -217,30 +217,36 @@
|
||||
<div class="time-info">{{ scope.row.become_time }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" prop="plan_id" width="80" />
|
||||
<el-table-column label="规格" prop="plan_name" width="80" />
|
||||
<el-table-column label="用户ID" prop="user_id" width="80" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.container_state == 0 || scope.row.container_state == 1
|
||||
? 'info'
|
||||
: scope.row.container_state == 2
|
||||
? 'danger'
|
||||
: scope.row.container_state == 3
|
||||
? 'danger'
|
||||
: 'success'"
|
||||
:type="scope.row.container_state == 0
|
||||
? 'warning'
|
||||
: scope.row.container_state == 1
|
||||
? 'info'
|
||||
: scope.row.container_state == 2
|
||||
? 'success'
|
||||
: scope.row.container_state == 3
|
||||
? 'info'
|
||||
: scope.row.container_state == 4
|
||||
? 'danger'
|
||||
: 'info'"
|
||||
effect="light"
|
||||
>
|
||||
{{
|
||||
scope.row.container_state == 0
|
||||
? "未构建"
|
||||
? "未支付"
|
||||
: scope.row.container_state == 1
|
||||
? "已构建"
|
||||
? "未构建"
|
||||
: scope.row.container_state == 2
|
||||
? "构建失败"
|
||||
? "已构建"
|
||||
: scope.row.container_state == 3
|
||||
? "已删除"
|
||||
: "未知状态"
|
||||
? "未知"
|
||||
: scope.row.container_state == 4
|
||||
? "已删除"
|
||||
: "未知状态"
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -251,7 +257,7 @@
|
||||
type="primary"
|
||||
size="small"
|
||||
:icon="Setting"
|
||||
@click="$router.push('/servers/containers?container_id=' + scope.row.container_id)"
|
||||
@click="$router.push('/servers/container?container_id=' + scope.row.container_id)"
|
||||
>
|
||||
管理
|
||||
</el-button>
|
||||
@@ -610,7 +616,8 @@ import {
|
||||
getFloatingIpList,
|
||||
addFloatingIp,
|
||||
delFloatingIp,
|
||||
addFloatingIpBatch
|
||||
addFloatingIpBatch,
|
||||
selectServerPlan
|
||||
} from "@/utils/acs/server";
|
||||
import { ElMessage, ElNotification } from 'element-plus';
|
||||
import { copyDomText } from "@/utils/hide";
|
||||
@@ -671,31 +678,43 @@ const initData = async () => {
|
||||
try {
|
||||
const userInfoRes = await getUserInfo();
|
||||
console.log("获取用户信息", userInfoRes);
|
||||
if (userInfoRes && userInfoRes.data && userInfoRes.data.data) {
|
||||
// 根据API返回的用户信息设置用户类型
|
||||
const userType = userInfoRes.data.is_admin == true ? "1" : "0";
|
||||
localStorage.setItem("user_id", userInfoRes.real_name.UserId);
|
||||
localStorage.setItem("user_type", userType);
|
||||
console.log("获取到用户类型", userType);
|
||||
if (userInfoRes && userInfoRes.data ) {
|
||||
console.log("获取用户信息", userInfoRes);
|
||||
localStorage.setItem("user_id", userInfoRes.data.user_id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取用户信息失败:", error);
|
||||
// 如果获取失败,默认设置为管理员权限确保功能可用
|
||||
localStorage.setItem("user_type", "1");
|
||||
}
|
||||
|
||||
// 获取所有容器
|
||||
let usertype = localStorage.getItem("user_type");
|
||||
console.log("用户类型", usertype);
|
||||
if (usertype == "1") {
|
||||
containerBox.server_id = route.query.server_id;
|
||||
containerBox.user_id = localStorage.getItem("user_id");
|
||||
let cons = await getContainer(containerBox);
|
||||
console.log("获取容器列表", cons);
|
||||
if (cons && cons.data) {
|
||||
user_servers.value = cons.data.data || [];
|
||||
// user_servers.value = cons.data.data.forEach(async item => {
|
||||
// item.plan_name = await selectServerPlan({
|
||||
// server_type: 'dockerContainer',
|
||||
// plan_id: item.plan_id
|
||||
// })
|
||||
// }
|
||||
// )
|
||||
for (const item of user_servers.value){
|
||||
try{
|
||||
const res = await selectServerPlan({
|
||||
server_type: 'dockerContainer',
|
||||
plan_id: item.plan_id
|
||||
})
|
||||
console.log("获取容器规格:", res);
|
||||
item.plan_name = res.data.data.name;
|
||||
}catch(error){
|
||||
console.error("获取容器列表失败:", error);
|
||||
ElMessage.error("获取容器列表失败");
|
||||
}
|
||||
}
|
||||
console.log("获取容器列表", user_servers.value);
|
||||
total.value = cons.data.count || 0;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("初始化数据失败:", error);
|
||||
ElMessage.error("加载数据失败,请刷新页面重试");
|
||||
@@ -727,7 +746,6 @@ const GetSpecs = async () => {
|
||||
server_id: route.query.server_id,
|
||||
count: 30
|
||||
});
|
||||
console.log("服务器实例规格", plans.data.data);
|
||||
spec_list.value = plans.data.data;
|
||||
} catch (error) {
|
||||
console.error("获取实例规格列表失败:", error);
|
||||
@@ -929,8 +947,9 @@ const editSpec = async () => {
|
||||
server_id: route.query.server_id,
|
||||
server_type:TypeData.value
|
||||
});
|
||||
console.log("添加实例规格:",res)
|
||||
|
||||
if (res.code == 200) {
|
||||
if (res.data.code == 200) {
|
||||
ElNotification({
|
||||
title: '添加成功',
|
||||
message: `已成功添加规格 "${spec_form.name}"`,
|
||||
@@ -939,7 +958,7 @@ const editSpec = async () => {
|
||||
});
|
||||
centerDialogVisible.value = false;
|
||||
} else {
|
||||
ElMessage.error(res.data.message || "添加失败");
|
||||
ElMessage.error(res.data.msg || "添加失败");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user