Files
ApiServer-Web-admin_dashboa…/src/views/virtualization/KvmService.vue
T
lin b3ed406f84
Build and Deploy Vue3 / build (push) Successful in 1m31s
Build and Deploy Vue3 / deploy (push) Successful in 1m9s
fix: 提交修改
2026-04-15 16:02:36 +08:00

383 lines
10 KiB
Vue

<template>
<div class="kvm-service-container">
<!-- 顶部操作栏 -->
<div class="toolbar">
<div class="toolbar-left">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新建主控服务
</el-button>
<el-button @click="loadList">
<el-icon><Refresh /></el-icon>刷新
</el-button>
</div>
<div class="toolbar-right">
<el-input
v-model="searchKey"
placeholder="搜索服务名称/地址"
style="width: 260px"
clearable
@keyup.enter="handleSearch"
@clear="handleSearch"
>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
</div>
<!-- 服务列表 -->
<el-table :data="serviceList" v-loading="loading" stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="服务名称" min-width="160" show-overflow-tooltip />
<el-table-column label="服务地址" min-width="220">
<template #default="{ row }">
<span class="host-addr">{{ row.host }}:{{ row.port }}</span>
</template>
</el-table-column>
<el-table-column label="认证Token" min-width="160" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.token" class="token-mask">{{ maskToken(row.token) }}</span>
<span v-else class="text-muted">未设置</span>
</template>
</el-table-column>
<el-table-column prop="note" label="备注" min-width="160" show-overflow-tooltip>
<template #default="{ row }">
{{ row.note || '-' }}
</template>
</el-table-column>
<el-table-column label="创建时间" width="170">
<template #default="{ row }">
{{ formatTime(row.CreatedAt || row.created_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button link type="primary" @click="handleViewDetail(row)">详情/管理</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper" v-if="total > 0">
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.page_size"
:page-sizes="[10, 20, 50]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
<!-- 新建/编辑弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新建主控服务' : '编辑主控服务'"
width="520px"
destroy-on-close
>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="服务名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入服务名称" />
</el-form-item>
<el-form-item label="服务地址" prop="host">
<el-input v-model="formData.host" placeholder="请输入服务地址,如 192.168.1.100 或域名" />
</el-form-item>
<el-form-item label="服务端口" prop="port">
<el-input v-model="formData.port" placeholder="请输入服务端口,如 8080" />
</el-form-item>
<el-form-item label="认证Token" prop="token">
<el-input v-model="formData.token" placeholder="请输入认证Token(可选)" show-password />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input v-model="formData.note" type="textarea" :rows="3" placeholder="备注说明(可选)" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
import {
getKvmServiceList,
createKvmService,
updateKvmService,
deleteKvmService
} from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
import dayjs from 'dayjs'
const router = useRouter()
const loading = ref(false)
const submitLoading = ref(false)
const serviceList = ref([])
const total = ref(0)
const searchKey = ref('')
const queryParams = reactive({
page: 1,
page_size: 10,
key: ''
})
// 弹窗控制
const dialogVisible = ref(false)
const dialogType = ref('add')
const formRef = ref(null)
const formData = reactive({
id: undefined,
name: '',
host: '',
port: '',
token: '',
note: ''
})
const formRules = {
name: [{ required: true, message: '请输入服务名称', trigger: 'blur' }],
host: [{ required: true, message: '请输入服务地址', trigger: 'blur' }],
port: [{ required: true, message: '请输入服务端口', trigger: 'blur' }]
}
const normalizeService = (item) => {
if (!item) return item
return {
...item,
id: item.id ?? item.Id,
name: item.name ?? item.Name,
host: item.host ?? item.Host,
port: item.port ?? item.Port,
token: item.token ?? item.Token,
note: item.note ?? item.Note,
CreatedAt: item.CreatedAt ?? item.created_at,
UpdatedAt: item.UpdatedAt ?? item.updated_at,
}
}
// 加载列表
const loadList = async () => {
loading.value = true
try {
const res = await getKvmServiceList(queryParams)
// http2 返回完整 axios 响应,res.data 为 JSON body
const body = res?.data
console.debug('[KvmService] list response body:', JSON.stringify(body))
if (body?.code === 200 && body?.data) {
const inner = body.data // { all_count, data: [...] } 或直接是数组
const items = Array.isArray(inner) ? inner : (inner.data || inner.list || [])
serviceList.value = items.map(normalizeService)
total.value = inner.all_count ?? inner.total ?? items.length
console.debug('[KvmService] normalized list:', serviceList.value)
} else {
serviceList.value = []
total.value = 0
if (body?.message) {
ElMessage.warning(body.message)
}
}
} catch (error) {
console.error('获取主控服务列表失败:', error)
ElMessage.error('获取主控服务列表失败')
} finally {
loading.value = false
}
}
const handleSearch = () => {
queryParams.page = 1
queryParams.key = searchKey.value
loadList()
}
const handleSizeChange = (size) => {
queryParams.page_size = size
queryParams.page = 1
loadList()
}
const handlePageChange = (page) => {
queryParams.page = page
loadList()
}
// 遮掩Token
const maskToken = (token) => {
if (!token) return ''
if (token.length <= 8) return '****'
return token.substring(0, 4) + '****' + token.substring(token.length - 4)
}
// 格式化时间
const formatTime = (t) => {
if (!t) return '-'
return dayjs(t).format('YYYY-MM-DD HH:mm:ss')
}
// 重置表单
const resetForm = () => {
Object.assign(formData, {
id: undefined,
name: '',
host: '',
port: '',
token: '',
note: ''
})
}
// 新建
const handleAdd = () => {
dialogType.value = 'add'
resetForm()
dialogVisible.value = true
}
const handleEdit = (row) => {
dialogType.value = 'edit'
Object.assign(formData, {
id: Number(row.id),
name: row.name,
host: row.host,
port: row.port,
token: row.token ?? '',
note: row.note ?? ''
})
dialogVisible.value = true
}
// 提交表单
const handleSubmit = () => {
formRef.value?.validate(async (valid) => {
if (!valid) return
submitLoading.value = true
try {
const payload = {
name: formData.name,
host: formData.host,
port: formData.port,
token: formData.token,
note: formData.note
}
let res
if (dialogType.value === 'add') {
res = await createKvmService(payload)
} else {
const editId = Number(formData.id)
if (!editId) {
ElMessage.error('无法获取服务ID')
submitLoading.value = false
return
}
res = await updateKvmService(editId, payload)
}
const body = res?.data
if (body?.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '创建成功' : '更新成功')
dialogVisible.value = false
loadList()
} else {
ElMessage.error(extractApiError(body, '操作失败'))
}
} catch (error) {
console.error('操作失败:', error)
ElMessage.error(extractApiError(error?.response?.data, '操作失败'))
} finally {
submitLoading.value = false
}
})
}
const handleDelete = (row) => {
if (!row.id) {
ElMessage.error('无法获取服务ID')
return
}
ElMessageBox.confirm(`确定要删除主控服务「${row.name}」吗?删除后不可恢复。`, '删除确认', {
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteKvmService({ id: Number(row.id) })
const body = res?.data
if (body?.code === 200) {
ElMessage.success('删除成功')
loadList()
} else {
ElMessage.error(extractApiError(body, '删除失败'))
}
} catch (error) {
ElMessage.error(extractApiError(error?.response?.data, '删除失败'))
}
}).catch(() => {})
}
const handleViewDetail = (row) => {
const id = Number(row.id)
const name = row.name
if (!id) {
ElMessage.error('无法获取服务ID,请刷新列表后重试')
return
}
router.push({
path: '/virtualization/kvm-service-detail',
query: { service_id: id, service_name: name }
})
}
onMounted(() => {
loadList()
})
</script>
<style scoped>
.kvm-service-container {
padding: 20px;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.toolbar-left {
display: flex;
gap: 8px;
}
.toolbar-right {
display: flex;
gap: 8px;
}
.host-addr {
font-family: 'Consolas', 'Monaco', monospace;
color: #409eff;
font-size: 13px;
}
.token-mask {
font-family: 'Consolas', 'Monaco', monospace;
color: #909399;
font-size: 13px;
}
</style>