2026-08-03 09:34:08 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-08-03 16:20:21 +08:00
|
|
|
|
import { onMounted, reactive, ref } from 'vue'
|
2026-08-03 09:34:08 +08:00
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
|
import { getTenant, setTenantQuota, type Tenant } from '@/api/modules/tenant'
|
|
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const tenant = ref<Tenant | null>(null)
|
|
|
|
|
|
const loading = ref(false)
|
2026-08-03 16:20:21 +08:00
|
|
|
|
const quotaForm = reactive({ gpu: 0, storage: 0, maxProjects: 0 })
|
|
|
|
|
|
|
2026-08-17 09:15:36 +08:00
|
|
|
|
function parseQuota(quota: Record<string, unknown> | undefined | null | string) {
|
|
|
|
|
|
// 兼容处理:quota 可能是 JSON 字符串或嵌套 { quota: {...} } 格式
|
|
|
|
|
|
let q: Record<string, unknown> = {}
|
|
|
|
|
|
if (typeof quota === 'string') {
|
|
|
|
|
|
try { q = JSON.parse(quota) || {} } catch { q = {} }
|
|
|
|
|
|
} else if (quota && typeof quota === 'object') {
|
|
|
|
|
|
q = quota
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果是嵌套格式 { quota: { gpu: 4, ... } },提取内层
|
|
|
|
|
|
if (q.quota && typeof q.quota === 'object' && !q.gpu) {
|
|
|
|
|
|
q = q.quota as Record<string, unknown>
|
|
|
|
|
|
}
|
2026-08-03 16:20:21 +08:00
|
|
|
|
return {
|
|
|
|
|
|
gpu: Number(q.gpu || q.gpu_quota || 0),
|
|
|
|
|
|
storage: Number(q.storage || q.storage_quota || 0),
|
|
|
|
|
|
maxProjects: Number(q.max_projects || 0),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatQuota(quota: Record<string, unknown> | undefined | null) {
|
|
|
|
|
|
const q = parseQuota(quota)
|
|
|
|
|
|
const parts: string[] = []
|
|
|
|
|
|
if (q.gpu > 0) parts.push(`GPU ${q.gpu}`)
|
|
|
|
|
|
if (q.storage > 0) parts.push(`存储 ${q.storage}GB`)
|
|
|
|
|
|
if (q.maxProjects > 0) parts.push(`项目 ${q.maxProjects}`)
|
|
|
|
|
|
return parts.length ? parts.join(' | ') : '—'
|
|
|
|
|
|
}
|
2026-08-03 09:34:08 +08:00
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
const id = route.params.id as string
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
tenant.value = await getTenant(id)
|
2026-08-03 16:20:21 +08:00
|
|
|
|
const q = parseQuota(tenant.value?.quota)
|
|
|
|
|
|
quotaForm.gpu = q.gpu
|
|
|
|
|
|
quotaForm.storage = q.storage
|
|
|
|
|
|
quotaForm.maxProjects = q.maxProjects
|
2026-08-03 09:34:08 +08:00
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveQuota() {
|
|
|
|
|
|
if (!tenant.value) return
|
2026-08-03 16:20:21 +08:00
|
|
|
|
const quota: Record<string, unknown> = {}
|
|
|
|
|
|
if (quotaForm.gpu > 0) quota.gpu = quotaForm.gpu
|
|
|
|
|
|
if (quotaForm.storage > 0) quota.storage = quotaForm.storage
|
|
|
|
|
|
if (quotaForm.maxProjects > 0) quota.max_projects = quotaForm.maxProjects
|
|
|
|
|
|
await setTenantQuota(tenant.value.id, quota)
|
|
|
|
|
|
ElMessage.success('配额已保存')
|
|
|
|
|
|
load()
|
2026-08-03 09:34:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(load)
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
|
<el-page-header title="返回" @back="router.back()">
|
|
|
|
|
|
<template #content>
|
|
|
|
|
|
<span class="page-title">租户详情:{{ tenant?.name }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-page-header>
|
|
|
|
|
|
<el-card class="section" v-loading="loading">
|
|
|
|
|
|
<template #header>基本信息</template>
|
|
|
|
|
|
<el-descriptions :column="2" border>
|
|
|
|
|
|
<el-descriptions-item label="名称">{{ tenant?.name }}</el-descriptions-item>
|
2026-08-03 16:20:21 +08:00
|
|
|
|
<el-descriptions-item label="用户ID">{{ tenant?.code }}</el-descriptions-item>
|
2026-08-03 09:34:08 +08:00
|
|
|
|
<el-descriptions-item label="状态">{{ tenant?.status }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="创建时间">{{ tenant?.create_time }}</el-descriptions-item>
|
2026-08-03 16:20:21 +08:00
|
|
|
|
<el-descriptions-item label="配额">{{ formatQuota(tenant?.quota) }}</el-descriptions-item>
|
2026-08-03 09:34:08 +08:00
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
<el-divider />
|
|
|
|
|
|
<div class="quota-edit">
|
2026-08-03 16:20:21 +08:00
|
|
|
|
<span class="label">配额设置(0 表示不限制)</span>
|
|
|
|
|
|
<el-form label-width="100px">
|
|
|
|
|
|
<el-form-item label="GPU 数量">
|
|
|
|
|
|
<el-input-number v-model="quotaForm.gpu" :min="0" :step="1" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="存储配额(GB)">
|
|
|
|
|
|
<el-input-number v-model="quotaForm.storage" :min="0" :step="10" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="最大项目数">
|
|
|
|
|
|
<el-input-number v-model="quotaForm.maxProjects" :min="0" :step="1" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<el-button type="primary" @click="saveQuota">保存配额</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
2026-08-03 09:34:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.page { padding: 16px; }
|
|
|
|
|
|
.section { margin-top: 16px; }
|
|
|
|
|
|
.page-title { font-size: 16px; font-weight: 600; }
|
|
|
|
|
|
.quota-edit { display: flex; flex-direction: column; gap: 12px; max-width: 480px; }
|
|
|
|
|
|
.label { font-size: 13px; color: #606266; }
|
|
|
|
|
|
</style>
|