2026-05-01 21:27:39 +08:00
|
|
|
<template>
|
|
|
|
|
<div v-if="detail">
|
|
|
|
|
<el-page-header @back="$router.back()" :content="detail.app.name" style="margin-bottom: 20px" />
|
|
|
|
|
|
|
|
|
|
<el-card style="margin-bottom: 16px">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="header-row">
|
|
|
|
|
<span>应用信息</span>
|
|
|
|
|
<el-tag :type="detail.enabledServiceCount > 0 ? 'success' : 'info'">
|
|
|
|
|
已开通 {{ detail.enabledServiceCount }} 项服务
|
|
|
|
|
</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<el-descriptions :column="2" border>
|
|
|
|
|
<el-descriptions-item label="应用名称">{{ detail.app.name }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="包名">{{ detail.app.packageName }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="AppKey">{{ detail.app.appKey }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="AppSecret">{{ mask(detail.app.appSecret) }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="租户ID">{{ detail.app.tenantId }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="创建时间">{{ fmt(detail.app.createdAt) }}</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<el-card style="margin-bottom: 16px">
|
|
|
|
|
<template #header>所属租户</template>
|
|
|
|
|
<el-descriptions :column="2" border>
|
|
|
|
|
<el-descriptions-item label="租户昵称">{{ detail.tenant?.nickname || '-' }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="租户用户名">{{ detail.tenant?.username || '-' }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="租户邮箱">{{ detail.tenant?.email || '-' }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="租户状态">{{ detail.tenant?.status === 'ACTIVE' ? '正常' : '禁用' }}</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
2026-05-15 22:39:43 +08:00
|
|
|
<el-card style="margin-bottom: 16px">
|
2026-05-01 21:27:39 +08:00
|
|
|
<template #header>功能服务</template>
|
|
|
|
|
<el-table :data="detail.services" border stripe>
|
|
|
|
|
<el-table-column prop="serviceType" label="服务类型" width="140" />
|
|
|
|
|
<el-table-column label="状态" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.enabled ? 'success' : 'info'">
|
|
|
|
|
{{ row.enabled ? '已开通' : '未开通' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="createdAt" label="创建时间" width="180">
|
|
|
|
|
<template #default="{ row }">{{ fmt(row.createdAt) }}</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="config" label="配置" min-width="240" show-overflow-tooltip />
|
|
|
|
|
</el-table>
|
|
|
|
|
</el-card>
|
2026-05-15 22:39:43 +08:00
|
|
|
|
|
|
|
|
<el-card v-if="licenseInfo?.exists">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="header-row">
|
|
|
|
|
<span>License 授权管理</span>
|
|
|
|
|
<el-tag :type="licenseInfo.active ? 'success' : 'danger'" size="small">
|
|
|
|
|
{{ licenseInfo.active ? '正常' : '停用' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<el-descriptions :column="2" border>
|
|
|
|
|
<el-descriptions-item label="已注册设备">{{ licenseInfo.registeredDevices ?? '-' }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="过期时间">
|
|
|
|
|
{{ licenseInfo.expiresAt ? fmt(licenseInfo.expiresAt) : '永久' }}
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="最大设备数">
|
|
|
|
|
<div v-if="!editingMaxDevices" class="max-devices-display">
|
|
|
|
|
<span>{{ licenseInfo.maxDevices ?? '-' }}</span>
|
|
|
|
|
<el-button link type="primary" size="small" @click="startEditMaxDevices" style="margin-left:8px">
|
|
|
|
|
修改
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="max-devices-edit">
|
|
|
|
|
<el-input-number v-model="editMaxDevicesValue" :min="1" :max="999999" size="small" />
|
|
|
|
|
<el-button type="primary" size="small" :loading="savingMaxDevices" @click="saveMaxDevices" style="margin-left:8px">
|
|
|
|
|
保存
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button size="small" @click="editingMaxDevices = false" style="margin-left:4px">取消</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
</el-card>
|
2026-05-01 21:27:39 +08:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { onMounted, ref } from 'vue'
|
|
|
|
|
import { useRoute } from 'vue-router'
|
2026-05-15 22:39:43 +08:00
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { opsApi, type AppDetail, type LicenseStatusInfo } from '@/api/ops'
|
2026-05-01 21:27:39 +08:00
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const detail = ref<AppDetail | null>(null)
|
2026-05-15 22:39:43 +08:00
|
|
|
const licenseInfo = ref<LicenseStatusInfo | null>(null)
|
|
|
|
|
const editingMaxDevices = ref(false)
|
|
|
|
|
const editMaxDevicesValue = ref(1)
|
|
|
|
|
const savingMaxDevices = ref(false)
|
2026-05-01 21:27:39 +08:00
|
|
|
|
|
|
|
|
async function loadDetail() {
|
2026-05-15 22:39:43 +08:00
|
|
|
const appKey = route.params.appKey as string
|
|
|
|
|
const res = await opsApi.getApp(appKey)
|
2026-05-01 21:27:39 +08:00
|
|
|
detail.value = res.data.data
|
2026-05-15 22:39:43 +08:00
|
|
|
loadLicense(appKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadLicense(appKey: string) {
|
|
|
|
|
try {
|
|
|
|
|
const res = await opsApi.getAppLicense(appKey)
|
|
|
|
|
licenseInfo.value = res.data.data
|
|
|
|
|
} catch {
|
|
|
|
|
licenseInfo.value = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startEditMaxDevices() {
|
|
|
|
|
editMaxDevicesValue.value = licenseInfo.value?.maxDevices ?? 1
|
|
|
|
|
editingMaxDevices.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveMaxDevices() {
|
|
|
|
|
if (!detail.value) return
|
|
|
|
|
savingMaxDevices.value = true
|
|
|
|
|
try {
|
|
|
|
|
await opsApi.updateMaxDevices(detail.value.app.appKey, editMaxDevicesValue.value)
|
|
|
|
|
ElMessage.success('最大设备数已更新')
|
|
|
|
|
editingMaxDevices.value = false
|
|
|
|
|
loadLicense(detail.value.app.appKey)
|
|
|
|
|
} catch {
|
|
|
|
|
ElMessage.error('更新失败')
|
|
|
|
|
} finally {
|
|
|
|
|
savingMaxDevices.value = false
|
|
|
|
|
}
|
2026-05-01 21:27:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fmt(value: string) {
|
|
|
|
|
return value ? new Date(value).toLocaleString('zh-CN') : '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mask(value: string) {
|
|
|
|
|
if (!value) return '-'
|
|
|
|
|
return `${value.slice(0, 4)}********${value.slice(-4)}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(loadDetail)
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.header-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
2026-05-15 22:39:43 +08:00
|
|
|
.max-devices-display {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.max-devices-edit {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
2026-05-01 21:27:39 +08:00
|
|
|
</style>
|