Merge pull request 'fix: 虚拟机网络管理改为解绑逻辑,不再删除底层网络' (#27) from master into deploy
Build and Deploy Vue3 / build (push) Successful in 6m20s
Build and Deploy Vue3 / deploy (push) Successful in 41s

Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
2026-07-07 18:03:00 +08:00
9 changed files with 214 additions and 28 deletions
+6
View File
@@ -105,6 +105,12 @@ export const getUserOperationRecord = (data) => {
export const mockUserLogin = (data) => { export const mockUserLogin = (data) => {
return http2.get('/api/v1/admin/user/user/simulation_login?user_id='+data.user_id) return http2.get('/api/v1/admin/user/user/simulation_login?user_id='+data.user_id)
} }
/**通过IDC代理商uid获取用户信息 */
export const getUserByIdcAgentUid = (params) => {
return http2.get('/api/v1/admin/user/idc_agent/user', { params })
}
/**新建任务 */ /**新建任务 */
export const createTask = (data) => { export const createTask = (data) => {
return http2.post('/api/v1/admin/user/user/create', data,{ return http2.post('/api/v1/admin/user/user/create', data,{
+1
View File
@@ -109,6 +109,7 @@ export const getUserGoodsDetail = (params) => http2.get(`${GOODS_BASE}/detail`,
export const createUserGoods = (data) => http2.post(`${GOODS_BASE}/create`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } }) export const createUserGoods = (data) => http2.post(`${GOODS_BASE}/create`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } })
export const updateUserGoods = (data) => http2.post(`${GOODS_BASE}/update`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } }) export const updateUserGoods = (data) => http2.post(`${GOODS_BASE}/update`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } })
export const deleteUserGoods = (params) => http2.delete(`${GOODS_BASE}/delete`, { params }) export const deleteUserGoods = (params) => http2.delete(`${GOODS_BASE}/delete`, { params })
export const renewUserGoods = (data) => http2.post(`${GOODS_BASE}/renew`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } })
export const getUserVmMetricsHistory = (params) => http2.get(`${BASE}/metrics_history`, { params }) export const getUserVmMetricsHistory = (params) => http2.get(`${BASE}/metrics_history`, { params })
export const getUserVmTrafficHourly = (params) => http2.get(`${BASE}/traffic_hourly`, { params }) export const getUserVmTrafficHourly = (params) => http2.get(`${BASE}/traffic_hourly`, { params })
+1 -1
View File
@@ -1285,7 +1285,7 @@ const clearUser = () => {
const handleProductSelect = (product) => { const handleProductSelect = (product) => {
orderForm.commodity_id = product.id orderForm.commodity_id = product.id
selectedProductInfo.value = product selectedProductInfo.value = product
if (product.table) orderForm.table = product.table if (orderForm.type !== 'create' && product.table) orderForm.table = product.table
orderForm.plan_id = null orderForm.plan_id = null
planFixedArgs.value = [] planFixedArgs.value = []
// 自动生成订单名称:商品名-时间戳 // 自动生成订单名称:商品名-时间戳
+68 -1
View File
@@ -13,6 +13,9 @@
<el-button plain @click="loadDetail" :loading="loading"> <el-button plain @click="loadDetail" :loading="loading">
<el-icon><Refresh /></el-icon> 刷新 <el-icon><Refresh /></el-icon> 刷新
</el-button> </el-button>
<el-button type="success" plain @click="openRenew" :disabled="!detail || isDeleted">
续费
</el-button>
<el-button type="primary" plain @click="openEdit" :disabled="!detail"> <el-button type="primary" plain @click="openEdit" :disabled="!detail">
<el-icon><Edit /></el-icon> 编辑 <el-icon><Edit /></el-icon> 编辑
</el-button> </el-button>
@@ -233,6 +236,35 @@
</template> </template>
</div> </div>
<!-- 续费弹窗 -->
<el-dialog v-model="renewVisible" title="用户商品续费" width="480px" destroy-on-close>
<el-form :model="renewForm" label-width="110px">
<el-form-item label="续费月数">
<el-input-number v-model="renewForm.purchase_duration" :min="1" :max="120" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="续费单价">
<div class="unit-input-row">
<el-input-number v-model="renewForm.renew_price" :min="0" :precision="2" controls-position="right" style="flex:1" />
<span class="unit-text"></span>
</div>
<div style="font-size: 12px; color: #909399; margin-top: 4px">不填则使用商品默认续费价格</div>
</el-form-item>
<el-form-item label="总计费用">
<span style="font-size: 16px; font-weight: 600; color: #409eff">¥{{ renewTotalPrice }}</span>
</el-form-item>
<el-form-item label="直接完成支付">
<el-switch v-model="renewForm.complete_pay" />
</el-form-item>
<el-form-item v-if="renewForm.complete_pay" label="扣除用户余额">
<el-switch v-model="renewForm.deduct_balance" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="renewVisible = false">取消</el-button>
<el-button type="primary" :loading="renewLoading" @click="handleRenew">确认续费</el-button>
</template>
</el-dialog>
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<el-dialog v-model="editVisible" title="编辑用户商品" width="520px" destroy-on-close> <el-dialog v-model="editVisible" title="编辑用户商品" width="520px" destroy-on-close>
<el-form :model="editForm" label-width="110px"> <el-form :model="editForm" label-width="110px">
@@ -289,7 +321,7 @@ import { ref, reactive, computed, onMounted, onActivated, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowLeft, ArrowDown, Refresh, Edit, Delete } from '@element-plus/icons-vue' import { ArrowLeft, ArrowDown, Refresh, Edit, Delete } from '@element-plus/icons-vue'
import { getUserGoodsDetail, updateUserGoods, deleteUserGoods } from '@/api/admin/userVm' import { getUserGoodsDetail, updateUserGoods, deleteUserGoods, renewUserGoods } from '@/api/admin/userVm'
import { extractApiError } from '@/utils/kvmErrorUtil' import { extractApiError } from '@/utils/kvmErrorUtil'
import VmSelectorPopup from '@/components/admin/VmSelectorPopup.vue' import VmSelectorPopup from '@/components/admin/VmSelectorPopup.vue'
import KvmServiceSelector from '@/components/admin/KvmServiceSelector.vue' import KvmServiceSelector from '@/components/admin/KvmServiceSelector.vue'
@@ -417,6 +449,41 @@ const goToOrder = () => {
router.push({ path: '/order/list', query: { order_id: oid } }) router.push({ path: '/order/list', query: { order_id: oid } })
} }
// ---- ----
const renewVisible = ref(false)
const renewLoading = ref(false)
const renewForm = reactive({ purchase_duration: 1, renew_price: 0, complete_pay: false, deduct_balance: false })
const renewTotalPrice = computed(() => {
const price = renewForm.renew_price || (detail.value?.renewPrice || detail.value?.renew_price || 0) / 100
return (price * renewForm.purchase_duration).toFixed(2)
})
const openRenew = () => {
const rawRenew = detail.value?.renewPrice || detail.value?.renew_price || 0
Object.assign(renewForm, { purchase_duration: 1, renew_price: rawRenew / 100, complete_pay: false, deduct_balance: false })
renewVisible.value = true
}
const handleRenew = async () => {
renewLoading.value = true
try {
const data = { id: goodsId.value, purchase_duration: renewForm.purchase_duration }
if (renewForm.renew_price) data.renew_price = Math.round(renewForm.renew_price * 100)
if (renewForm.complete_pay) data.complete_pay = true
if (renewForm.complete_pay && renewForm.deduct_balance) data.deduct_balance = true
const res = await renewUserGoods(data)
if (res?.data?.code === 200) {
ElMessage.success('续费成功')
renewVisible.value = false
loadDetail()
} else {
ElMessage.error(extractApiError(res?.data, '续费失败'))
}
} catch (e) {
ElMessage.error(extractApiError(e?.response?.data, '续费失败'))
} finally {
renewLoading.value = false
}
}
const editVisible = ref(false) const editVisible = ref(false)
const editForm = reactive({ note: '', renew_price: 0, base_price: 0, expire_time: '', item_id: 0, _serviceId: 0, _serviceName: '', _itemName: '' }) const editForm = reactive({ note: '', renew_price: 0, base_price: 0, expire_time: '', item_id: 0, _serviceId: 0, _serviceName: '', _itemName: '' })
const showVmSelector = ref(false) const showVmSelector = ref(false)
+18 -12
View File
@@ -344,7 +344,7 @@
<template #default="{ row }"> <template #default="{ row }">
<el-button v-if="!row.is_primary" link type="warning" size="small" @click="handleSetVmNetworkPrimary(row)">设为主IP</el-button> <el-button v-if="!row.is_primary" link type="warning" size="small" @click="handleSetVmNetworkPrimary(row)">设为主IP</el-button>
<el-button link type="danger" size="small" :loading="deletingNetworkId === row.id" @click="handleDeleteVmNetwork(row)"> <el-button link type="danger" size="small" :loading="deletingNetworkId === row.id" @click="handleDeleteVmNetwork(row)">
<el-icon :size="14"><Delete /></el-icon>删除 <el-icon :size="14"><Delete /></el-icon>解绑
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@@ -1807,21 +1807,27 @@ const resolveNetworkServiceHost = async (row) => {
} }
const handleDeleteVmNetwork = (row) => { const handleDeleteVmNetwork = (row) => {
ElMessageBox.confirm( ElMessageBox.confirm(
`将删除底层网络「${row.name}」(ID:${row.id}),该操作会影响所有绑定该网络的虚拟机,是否继续`, `确定从该虚拟机解绑网络「${row.name}」(ID:${row.id})`,
'删除网络', '解绑网络',
{ confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning' } { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
).then(async () => { ).then(async () => {
deletingNetworkId.value = row.id deletingNetworkId.value = row.id
try { try {
const { serviceId, hostId } = await resolveNetworkServiceHost(row) const payload = { user_goods_id: userGoodsId.value }
if (!serviceId) { ElMessage.error('无法获取该网络所属服务ID,删除失败'); return } const remainingNetworks = vmNetworks.value.filter(n => n.id !== row.id)
const params = { service_id: serviceId, network_id: row.id } const bridgeIds = remainingNetworks.filter(n => n.type === 'bridge').map(n => n.id)
if (hostId) params.host_id = hostId const natNet = remainingNetworks.find(n => n.type === 'nat')
const res = await deletePointNetwork(params) payload.network_ids = bridgeIds
if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadDetail() } if (natNet) payload.internet_network_id = natNet.id
else ElMessage.error(extractApiError(res?.data, '删除失败')) if (vm.value?.rx_bandwidth) payload.rx_bandwidth = vm.value.rx_bandwidth
if (vm.value?.tx_bandwidth) payload.tx_bandwidth = vm.value.tx_bandwidth
if (vm.value?.ssh_port) payload.ssh_port = vm.value.ssh_port
if (inPortGroup.value?.id) payload.port_group_id = inPortGroup.value.id
const res = await updateUserVm(payload)
if (res?.data?.code === 200) { ElMessage.success('解绑网络成功'); loadDetail() }
else ElMessage.error(extractApiError(res?.data, '解绑网络失败'))
} catch (e) { } catch (e) {
ElMessage.error(extractApiError(e?.response?.data, '删除失败')) ElMessage.error(extractApiError(e?.response?.data, '解绑网络失败'))
} finally { } finally {
deletingNetworkId.value = 0 deletingNetworkId.value = 0
} }
+24 -1
View File
@@ -116,6 +116,20 @@
<el-descriptions-item label="认证类型">{{ getRealNameTypeText(userInfo.RealName?.Type) }}</el-descriptions-item> <el-descriptions-item label="认证类型">{{ getRealNameTypeText(userInfo.RealName?.Type) }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-card> </el-card>
<el-card v-if="userInfo.idc_agent" class="info-section-card" shadow="never" style="margin-top: 20px;">
<template #header>
<div class="card-header">
<span class="title">IDC平台信息</span>
</div>
</template>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="代理商UID">{{ userInfo.idc_agent.Uid || '-' }}</el-descriptions-item>
<el-descriptions-item label="代理等级">{{ userInfo.idc_agent.AgentLevelName || '-' }}</el-descriptions-item>
<el-descriptions-item label="等级编号">{{ userInfo.idc_agent.AgentLevent ?? '-' }}</el-descriptions-item>
<el-descriptions-item label="绑定时间">{{ formatDate(userInfo.idc_agent.CreatedAt) }}</el-descriptions-item>
</el-descriptions>
</el-card>
</div> </div>
<!-- 右侧记录栏 --> <!-- 右侧记录栏 -->
@@ -257,7 +271,11 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="已购商品" name="5"> <el-tab-pane label="已购商品" name="5">
<el-table :data="userGoodsList" v-loading="goodsListLoading" stripe style="width: 100%" table-layout="auto"> <el-table :data="userGoodsList" v-loading="goodsListLoading" stripe style="width: 100%" table-layout="auto">
<el-table-column prop="id" label="ID" width="70" /> <el-table-column prop="id" label="ID" width="70">
<template #default="{row}">
<el-link type="primary" :underline="false" @click="router.push(`/user-goods/detail/${row.id}`)">{{ row.id }}</el-link>
</template>
</el-table-column>
<el-table-column label="商品名称" min-width="120" show-overflow-tooltip> <el-table-column label="商品名称" min-width="120" show-overflow-tooltip>
<template #default="{row}">{{ row.good?.name || '-' }}</template> <template #default="{row}">{{ row.good?.name || '-' }}</template>
</el-table-column> </el-table-column>
@@ -280,6 +298,11 @@
<el-table-column label="购买时间" min-width="140"> <el-table-column label="购买时间" min-width="140">
<template #default="{row}">{{ formatDate(row.CreatedAt) }}</template> <template #default="{row}">{{ formatDate(row.CreatedAt) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="80" fixed="right">
<template #default="{row}">
<el-button type="primary" link size="small" @click="router.push(`/user-goods/detail/${row.id}`)">详情</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<div class="pagination-wrapper" v-if="goodsListTotal > 0"> <div class="pagination-wrapper" v-if="goodsListTotal > 0">
<el-pagination <el-pagination
+46 -1
View File
@@ -32,10 +32,23 @@
@keyup.enter="handleJumpToUser" @keyup.enter="handleJumpToUser"
/> />
</div> </div>
<div class="search-group">
<span class="search-label">代理商ID</span>
<el-input
v-model="agentUid"
placeholder="007idc代理商uid"
clearable
class="search-input-small"
@keyup.enter="handleSearchByAgentUid"
/>
</div>
<div class="search-buttons"> <div class="search-buttons">
<el-button type="warning" @click="handleJumpToUser"> <el-button type="warning" @click="handleJumpToUser">
<el-icon><Position /></el-icon><span class="btn-text">跳转</span> <el-icon><Position /></el-icon><span class="btn-text">跳转</span>
</el-button> </el-button>
<el-button type="info" @click="handleSearchByAgentUid" :loading="agentSearchLoading">
<el-icon><Search /></el-icon><span class="btn-text">代理商查询</span>
</el-button>
<el-button type="primary" @click="handleQuery"> <el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon><span class="btn-text">查询</span> <el-icon><Search /></el-icon><span class="btn-text">查询</span>
</el-button> </el-button>
@@ -653,7 +666,8 @@ import {
updateUserGroup, updateUserGroup,
updateUserRealName, updateUserRealName,
getUserLoginRecord, getUserLoginRecord,
getUserOperationRecord getUserOperationRecord,
getUserByIdcAgentUid
} from '@/api/admin/user' } from '@/api/admin/user'
import { getFileDetail } from '@/api/admin/file' import { getFileDetail } from '@/api/admin/file'
import { closeAllMessage } from '../../utils/message' import { closeAllMessage } from '../../utils/message'
@@ -663,6 +677,10 @@ const router = useRouter()
// ID // ID
const jumpUserId = ref('') const jumpUserId = ref('')
// ID
const agentUid = ref('')
const agentSearchLoading = ref(false)
// //
const queryParams = reactive({ const queryParams = reactive({
key: '', key: '',
@@ -887,6 +905,33 @@ const handleJumpToUser = () => {
}) })
} }
// ID
const handleSearchByAgentUid = async () => {
if (!agentUid.value || !agentUid.value.trim()) {
ElMessage.warning('请输入代理商ID')
return
}
agentSearchLoading.value = true
try {
const res = await getUserByIdcAgentUid({ uid: agentUid.value.trim() })
if (res?.data?.code === 200 && res?.data?.data) {
const userId = res.data.data.UserId
if (userId) {
router.push({ path: '/user/detail', query: { user_id: userId } })
} else {
ElMessage.warning('未找到绑定该代理商ID的用户')
}
} else {
ElMessage.warning(res?.data?.message || '未找到绑定该代理商ID的用户')
}
} catch (e) {
const msg = e?.response?.data?.message || '查询失败'
ElMessage.error(msg)
} finally {
agentSearchLoading.value = false
}
}
// //
const handleSelectionChange = (selection) => { const handleSelectionChange = (selection) => {
selectedRows.value = selection selectedRows.value = selection
+20 -6
View File
@@ -302,7 +302,7 @@
<el-button link type="primary" size="small" @click="handleNetDetail(row)">详情</el-button> <el-button link type="primary" size="small" @click="handleNetDetail(row)">详情</el-button>
<el-button link type="primary" size="small" @click="handleNetEdit(row)">编辑</el-button> <el-button link type="primary" size="small" @click="handleNetEdit(row)">编辑</el-button>
<el-button v-if="!row.is_primary" link type="warning" size="small" @click="handleSetPrimary(row)">设为主IP</el-button> <el-button v-if="!row.is_primary" link type="warning" size="small" @click="handleSetPrimary(row)">设为主IP</el-button>
<el-button link type="danger" size="small" @click="handleNetDelete(row)">删除</el-button> <el-button link type="danger" size="small" @click="handleNetDelete(row)">解绑</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -2962,13 +2962,27 @@ const submitNetForm = () => {
} }
const handleNetDetail = (row) => { netDetailData.value = row; netDetailVisible.value = true } const handleNetDetail = (row) => { netDetailData.value = row; netDetailVisible.value = true }
const handleNetDelete = (row) => { const handleNetDelete = (row) => {
ElMessageBox.confirm(`确定要删除网络「${row.name}」(ID: ${row.id}) 吗?`, '删除网络', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) ElMessageBox.confirm(`确定从该虚拟机解绑网络「${row.name}」(ID: ${row.id}) 吗?`, '解绑网络', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => { .then(async () => {
actionLoading.value = true
try { try {
const res = await deleteNetwork({ service_id: serviceId.value, network_id: row.id, host_id: row.host_id }) const fd = new FormData()
if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadDetail() } fd.append('service_id', serviceId.value)
else ElMessage.error(extractApiError(res?.data, '删除失败')) fd.append('vm_id', vmId.value)
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) } const remainingNetworks = vmNetworks.value.filter(n => n.id !== row.id)
const bridgeIds = remainingNetworks.filter(n => n.type === 'bridge').map(n => n.id)
const natNet = remainingNetworks.find(n => n.type === 'nat')
bridgeIds.forEach(id => fd.append('network_ids', id))
if (natNet) fd.append('internet_network_id', natNet.id)
if (detail.value?.rx_bandwidth) fd.append('rx_bandwidth', detail.value.rx_bandwidth)
if (detail.value?.tx_bandwidth) fd.append('tx_bandwidth', detail.value.tx_bandwidth)
if (detail.value?.ssh_port) fd.append('ssh_port', detail.value.ssh_port)
if (vmPortGroup.value?.id) fd.append('port_group_id', vmPortGroup.value.id)
const res = await updateVm(fd)
if (res?.data?.code === 200) { ElMessage.success('解绑网络成功'); loadDetail() }
else ElMessage.error(extractApiError(res?.data, '解绑网络失败'))
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '解绑网络失败')) }
finally { actionLoading.value = false }
}).catch(() => {}) }).catch(() => {})
} }
+30 -6
View File
@@ -39,6 +39,18 @@
<span class="host-addr">{{ row.ip || '-' }}:{{ row.port || '-' }}</span> <span class="host-addr">{{ row.ip || '-' }}:{{ row.port || '-' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="访问方案" width="110">
<template #default="{ row }">
<el-tag v-if="row.access_mode === 'token'" type="warning" size="small">Token传参</el-tag>
<el-tag v-else size="small">域名解析</el-tag>
</template>
</el-table-column>
<el-table-column label="SSL" width="70">
<template #default="{ row }">
<el-tag v-if="row.use_ssl" type="success" size="small"></el-tag>
<span v-else class="text-muted"></span>
</template>
</el-table-column>
<el-table-column label="Token" min-width="140" show-overflow-tooltip> <el-table-column label="Token" min-width="140" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.token" class="token-mask">{{ maskToken(row.token) }}</span> <span v-if="row.token" class="token-mask">{{ maskToken(row.token) }}</span>
@@ -81,7 +93,16 @@
<el-input v-model="formData.port" placeholder="如 6080" /> <el-input v-model="formData.port" placeholder="如 6080" />
</el-form-item> </el-form-item>
<el-form-item label="Token"> <el-form-item label="Token">
<el-input v-model="formData.token" placeholder="认证Token(可选)" show-password /> <el-input v-model="formData.token" placeholder="节点鉴权Token(可选)" show-password />
</el-form-item>
<el-form-item label="访问方案">
<el-select v-model="formData.access_mode" style="width: 100%">
<el-option label="域名解析 (domain)" value="domain" />
<el-option label="Token传参 (token)" value="token" />
</el-select>
</el-form-item>
<el-form-item label="使用SSL">
<el-switch v-model="formData.use_ssl" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
@@ -224,7 +245,7 @@ const handleSearch = () => { queryParams.page = 1; loadList() }
const formDialogVisible = ref(false) const formDialogVisible = ref(false)
const formType = ref('add') const formType = ref('add')
const formRef = ref(null) const formRef = ref(null)
const formData = reactive({ id: undefined, name: '', ip: '', port: '', token: '' }) const formData = reactive({ id: undefined, name: '', ip: '', port: '', token: '', access_mode: 'domain', use_ssl: false })
const formRules = { const formRules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }], name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
ip: [{ required: true, message: '请输入IP地址', trigger: 'blur' }], ip: [{ required: true, message: '请输入IP地址', trigger: 'blur' }],
@@ -233,14 +254,15 @@ const formRules = {
const handleAdd = () => { const handleAdd = () => {
formType.value = 'add' formType.value = 'add'
Object.assign(formData, { id: undefined, name: '', ip: '', port: '', token: '' }) Object.assign(formData, { id: undefined, name: '', ip: '', port: '', token: '', access_mode: 'domain', use_ssl: false })
formDialogVisible.value = true formDialogVisible.value = true
} }
const handleEdit = (row) => { const handleEdit = (row) => {
formType.value = 'edit' formType.value = 'edit'
Object.assign(formData, { Object.assign(formData, {
id: row.id, name: row.name || '', ip: row.ip || '', port: row.port || '', token: row.token || '' id: row.id, name: row.name || '', ip: row.ip || '', port: row.port || '', token: row.token || '',
access_mode: row.access_mode || 'domain', use_ssl: !!row.use_ssl
}) })
formDialogVisible.value = true formDialogVisible.value = true
} }
@@ -251,10 +273,12 @@ const handleSubmit = () => {
submitLoading.value = true submitLoading.value = true
try { try {
let res let res
const payload = { service_id: serviceId.value, name: formData.name, ip: formData.ip, port: formData.port, token: formData.token, access_mode: formData.access_mode, use_ssl: formData.use_ssl }
if (formType.value === 'add') { if (formType.value === 'add') {
res = await addVncNode({ service_id: serviceId.value, name: formData.name, ip: formData.ip, port: formData.port, token: formData.token }) res = await addVncNode(payload)
} else { } else {
res = await updateVncNode({ service_id: serviceId.value, id: formData.id, name: formData.name, ip: formData.ip, port: formData.port, token: formData.token }) payload.id = formData.id
res = await updateVncNode(payload)
} }
if (res?.data?.code === 200) { if (res?.data?.code === 200) {
ElMessage.success(formType.value === 'add' ? '创建成功' : '修改成功') ElMessage.success(formType.value === 'add' ? '创建成功' : '修改成功')