Files
YG_FT/frontend/src/views/tenants/TenantListView.vue
2026-08-03 16:33:08 +08:00

181 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import DataTablePage from '@/components/DataTablePage.vue'
import { createTenant, deleteTenant, getTenants, setTenantQuota, type Tenant } from '@/api/modules/tenant'
const router = useRouter()
const loading = ref(false)
const tenants = ref<Tenant[]>([])
const showCreate = ref(false)
const showQuota = ref(false)
const currentTenant = ref<Tenant | null>(null)
const form = ref({ name: '', code: '', gpu: 0, storage: 0, maxProjects: 0 })
const quotaForm = reactive({ gpu: 0, storage: 0, maxProjects: 0 })
function parseQuota(quota: Record<string, unknown> | undefined | null) {
const q = quota || {}
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(' | ') : '—'
}
async function load() {
loading.value = true
try {
tenants.value = await getTenants()
} finally {
loading.value = false
}
}
function openDetail(id: string) {
router.push(`/tenants/${id}`)
}
function asTenant(row: unknown): Tenant {
return row as Tenant
}
function quotaText(row: unknown): string {
const quota = asTenant(row).quota || {}
return Object.keys(quota).length ? JSON.stringify(quota) : '—'
}
async function submitCreate() {
if (!form.value.name) {
ElMessage.warning('请填写租户名称')
return
}
const quota: Record<string, unknown> = {}
if (form.value.gpu > 0) quota.gpu = form.value.gpu
if (form.value.storage > 0) quota.storage = form.value.storage
if (form.value.maxProjects > 0) quota.max_projects = form.value.maxProjects
await createTenant({ name: form.value.name, code: form.value.code, quota })
ElMessage.success('租户创建成功')
showCreate.value = false
form.value = { name: '', code: '', gpu: 0, storage: 0, maxProjects: 0 }
load()
}
function openQuotaDialog(row: Tenant) {
currentTenant.value = row
const q = parseQuota(row.quota)
quotaForm.gpu = q.gpu
quotaForm.storage = q.storage
quotaForm.maxProjects = q.maxProjects
showQuota.value = true
}
async function submitQuota() {
if (!currentTenant.value) return
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(currentTenant.value.id, quota)
ElMessage.success('配额已更新')
showQuota.value = false
load()
}
async function handleDelete(row: Tenant) {
try {
await ElMessageBox.confirm(
`确定要删除租户「${row.name}」吗?删除后相关数据将无法恢复。`,
'删除确认',
{ confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning' },
)
} catch {
return
}
await deleteTenant(row.id)
ElMessage.success('租户已删除')
load()
}
onMounted(load)
</script>
<template>
<div class="page">
<DataTablePage title="租户管理" :data="tenants" :loading="loading">
<template #toolbar-extra>
<el-button type="primary" :icon="Plus" @click="showCreate = true">新建租户</el-button>
</template>
<template #columns>
<el-table-column prop="name" label="租户名称" min-width="140" />
<el-table-column prop="code" label="用户ID" min-width="100" />
<el-table-column prop="status" label="状态" min-width="100" />
<el-table-column prop="create_time" label="创建时间" min-width="180" />
</template>
<template #actions="{ row }">
<el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</DataTablePage>
<!-- 新建租户弹窗 -->
<el-dialog v-model="showCreate" title="新建租户" width="520px">
<el-form label-width="100px">
<el-form-item label="名称" required>
<el-input v-model="form.name" placeholder="租户名称" />
</el-form-item>
<el-form-item label="用户ID">
<el-input v-model="form.code" placeholder="用户ID" />
</el-form-item>
<el-divider content-position="left">配额设置可选0 表示不限制</el-divider>
<el-form-item label="GPU 数量">
<el-input-number v-model="form.gpu" :min="0" :step="1" placeholder="GPU 卡数" />
</el-form-item>
<el-form-item label="存储配额(GB)">
<el-input-number v-model="form.storage" :min="0" :step="10" placeholder="存储大小" />
</el-form-item>
<el-form-item label="最大项目数">
<el-input-number v-model="form.maxProjects" :min="0" :step="1" placeholder="项目上限" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showCreate = false">取消</el-button>
<el-button type="primary" @click="submitCreate">创建</el-button>
</template>
</el-dialog>
<!-- 设置配额弹窗 -->
<el-dialog v-model="showQuota" title="设置配额" width="480px">
<el-form label-width="100px">
<el-form-item label="GPU 数量">
<el-input-number v-model="quotaForm.gpu" :min="0" :step="1" placeholder="GPU 卡数" />
</el-form-item>
<el-form-item label="存储配额(GB)">
<el-input-number v-model="quotaForm.storage" :min="0" :step="10" placeholder="存储大小" />
</el-form-item>
<el-form-item label="最大项目数">
<el-input-number v-model="quotaForm.maxProjects" :min="0" :step="1" placeholder="项目上限" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showQuota = false">取消</el-button>
<el-button type="primary" @click="submitQuota">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.page { padding: 16px; }
</style>