初始化2

This commit is contained in:
2026-04-08 17:10:58 +08:00
commit 4986d90eb9
532 changed files with 112617 additions and 0 deletions

View File

@@ -0,0 +1,289 @@
<template>
<div class="dev-page">
<!-- 页头 -->
<div class="page-header" style="margin-bottom: 24px">
<h2 style="font-size: 20px; font-weight: 600; margin: 0 0 6px">资源中心</h2>
<p style="color: #888; margin: 0; font-size: 14px">管理应用所需的基础设施资源包括服务器数据库云存储域名和 SSL 证书</p>
</div>
<!-- 资源统计卡片 -->
<a-row :gutter="[16, 16]" style="margin-bottom: 24px">
<a-col v-for="item in resourceCards" :key="item.key" :xs="24" :sm="12" :md="8" :lg="8" :xl="4">
<div class="resource-stat-card" @click="navigateTo(item.to)">
<div class="stat-icon">{{ item.icon }}</div>
<div class="stat-info">
<div class="stat-label">{{ item.label }}</div>
<div class="stat-count">
<span class="count-num">{{ item.count }}</span>
<span class="count-unit"></span>
</div>
</div>
<div class="stat-arrow"></div>
</div>
</a-col>
</a-row>
<!-- 快捷操作 -->
<div class="section-title">快速购买</div>
<a-row :gutter="[16, 16]" style="margin-bottom: 32px">
<a-col v-for="item in buyCards" :key="item.key" :xs="24" :sm="12" :md="8" :lg="6">
<div class="buy-card">
<div class="buy-icon">{{ item.icon }}</div>
<div class="buy-content">
<div class="buy-title">{{ item.title }}</div>
<div class="buy-desc">{{ item.desc }}</div>
</div>
<a-button type="primary" size="small" ghost @click="handleBuy(item.key)">
购买
</a-button>
</div>
</a-col>
</a-row>
<!-- 协作权限说明 -->
<div class="collab-notice">
<LockOutlined class="notice-icon" />
<span>资源信息按权限分级显示<strong>创建者</strong>可查看完整信息含密码/私钥<strong>协作者</strong>可查看基础信息IP端口等敏感信息不可见</span>
</div>
<!-- 最近添加的资源 -->
<div class="section-title">最近添加</div>
<a-table
:columns="recentColumns"
:data-source="recentResources"
:pagination="false"
size="middle"
:loading="loading"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'type'">
<a-tag :color="typeColor[record.resourceType]">{{ typeLabel[record.resourceType] }}</a-tag>
</template>
<template v-if="column.key === 'status'">
<a-badge :status="record.status === 'running' ? 'success' : 'default'" :text="statusLabel[record.status]" />
</template>
<template v-if="column.key === 'permission'">
<a-tag v-if="record.isOwner" color="blue" size="small">创建者</a-tag>
<a-tag v-else color="orange" size="small"><LockOutlined /> 协作者</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-button type="link" size="small" @click="navigateTo(typeRoute[record.resourceType])">管理</a-button>
</template>
</template>
</a-table>
</div>
</template>
<script setup lang="ts">
import { navigateTo } from '#app'
import { LockOutlined } from '@ant-design/icons-vue'
import { statsAppResource, pageAppResource } from '@/api/app/appResource'
import type { AppResource } from '@/api/app/appResource/model'
import { enrichResourcesWithPermission } from '@/composables/useResourceAccess'
definePageMeta({ layout: 'developer' })
useHead({ title: '资源总览 - 开发者中心' })
const loading = ref(false)
const resourceCards = ref([
{ key: 'server', label: '服务器', icon: '🖥️', count: 0, to: '/developer/resources/servers' },
{ key: 'database', label: '数据库', icon: '🗄️', count: 0, to: '/developer/resources/databases' },
{ key: 'storage', label: '云存储', icon: '☁️', count: 0, to: '/developer/resources/storage' },
{ key: 'domain', label: '域名', icon: '🌐', count: 0, to: '/developer/resources/domains' },
{ key: 'ssl', label: 'SSL 证书', icon: '🔒', count: 0, to: '/developer/resources/ssl' },
{ key: 'git', label: '代码仓库', icon: '🐙', count: 0, to: '/developer/resources/git' },
])
const buyCards = [
{ key: 'server', icon: '🖥️', title: '云服务器', desc: '高性能、稳定可靠的弹性计算服务' },
{ key: 'database', icon: '🗄️', title: '云数据库', desc: '支持 MySQL / PostgreSQL / Redis' },
{ key: 'storage', icon: '☁️', title: '对象存储', desc: '海量、安全、低成本的云端存储' },
{ key: 'domain', icon: '🌐', title: '域名注册', desc: '注册您的专属域名,快速备案' },
]
const typeLabel: Record<string, string> = {
server: '服务器',
database: '数据库',
storage: '云存储',
domain: '域名',
ssl: 'SSL证书',
git: '代码仓库',
}
const typeColor: Record<string, string> = {
server: 'blue',
database: 'purple',
storage: 'cyan',
domain: 'green',
ssl: 'orange',
git: 'geekblue',
}
const typeRoute: Record<string, string> = {
server: '/developer/resources/servers',
database: '/developer/resources/databases',
storage: '/developer/resources/storage',
domain: '/developer/resources/domains',
ssl: '/developer/resources/ssl',
git: '/developer/resources/git',
}
const statusLabel: Record<string, string> = {
running: '运行中',
stopped: '已停止',
expired: '已过期',
pending: '配置中',
}
const recentColumns = [
{ title: '资源名称', dataIndex: 'name', key: 'name' },
{ title: '类型', dataIndex: 'resourceType', key: 'type' },
{ title: '所属应用', dataIndex: 'appName', key: 'appName' },
{ title: '状态', dataIndex: 'status', key: 'status' },
{ title: '到期时间', dataIndex: 'expireAt', key: 'expireAt' },
{ title: '权限', key: 'permission', width: 90 },
{ title: '操作', key: 'action' },
]
// 最近添加的资源(最新 10 条)
const recentResources = ref<AppResource[]>([])
async function loadData() {
loading.value = true
try {
const [stats, recentResult] = await Promise.all([
statsAppResource(),
pageAppResource({ page: 1, limit: 10, sort: 'createTime', order: 'desc' }),
])
resourceCards.value.forEach(card => {
card.count = stats[card.key] ?? 0
})
recentResources.value = enrichResourcesWithPermission(recentResult?.list ?? [])
}
catch (e) {
console.error('加载资源数据失败', e)
}
finally {
loading.value = false
}
}
function handleBuy(key: string) {
// TODO: 跳转到购买页或弹出购买引导
console.log('buy', key)
}
onMounted(() => loadData())
</script>
<style scoped>
.dev-page {
padding: 24px;
max-width: 1200px;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-left: 10px;
border-left: 3px solid #1677ff;
}
/* 协作权限说明 */
.collab-notice {
display: flex;
align-items: center;
gap: 8px;
background: linear-gradient(90deg, #fffbe6 0%, #fff7e6 100%);
border: 1px solid #ffd591;
border-radius: 8px;
padding: 10px 16px;
margin-bottom: 20px;
font-size: 13px;
color: #7c4a00;
}
.collab-notice .notice-icon {
color: #faad14;
font-size: 15px;
flex-shrink: 0;
}
.collab-notice strong {
color: #d46b08;
}
/* 统计卡片 */
.resource-stat-card {
display: flex;
align-items: center;
gap: 4px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 10px;
padding: 16px;
cursor: pointer;
transition: all 0.2s;
}
.resource-stat-card:hover {
border-color: #1677ff;
box-shadow: 0 2px 10px rgba(22, 119, 255, 0.1);
}
.stat-icon {
font-size: 28px;
width: 44px;
text-align: center;
}
.stat-info {
flex: 1;
}
.stat-label {
font-size: 12px;
color: #888;
margin-bottom: 2px;
}
.count-num {
font-size: 22px;
font-weight: 700;
color: #1677ff;
}
.count-unit {
font-size: 12px;
color: #aaa;
margin-left: 2px;
}
.stat-arrow {
font-size: 18px;
color: #bbb;
}
/* 购买卡片 */
.buy-card {
display: flex;
align-items: center;
gap: 12px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 10px;
padding: 14px 16px;
}
.buy-icon {
font-size: 24px;
width: 36px;
text-align: center;
}
.buy-content {
flex: 1;
}
.buy-title {
font-size: 14px;
font-weight: 600;
color: #222;
}
.buy-desc {
font-size: 12px;
color: #999;
margin-top: 2px;
}
</style>