初始化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,845 @@
<template>
<div class="dev-page">
<!-- 顶部欢迎横幅 -->
<div class="dev-hero">
<div class="dev-hero-content">
<div class="dev-hero-left">
<div class="dev-hero-greeting">🛠 欢迎回来{{ userDisplayName }}</div>
<h1 class="dev-hero-title">开发者控制台</h1>
<p class="dev-hero-desc">管理你的应用API Key 和源码权限快速构建 AI 原生产品</p>
<a-space class="mt-4">
<a-button type="primary" @click="navigateTo('/developer/apikeys')">
🔑 获取 API Key
</a-button>
<a-button @click="navigateTo('/developer-center')">📖 查看文档</a-button>
</a-space>
</div>
<div class="dev-hero-right">
<div class="dev-code-snippet">
<div class="code-header">
<div class="code-dots">
<span class="dot red" /><span class="dot yellow" /><span class="dot green" />
</div>
<span class="code-filename">quickstart.ts</span>
</div>
<pre class="code-body"><code><span class="c">// 初始化 SDK</span>
<span class="kw">import</span> { <span class="cls">WebsopyClient</span> } <span class="kw">from</span> <span class="str">'@websopy/sdk'</span>
<span class="kw">const</span> <span class="var">client</span> = <span class="kw">new</span> <span class="cls">WebsopyClient</span>({
<span class="prop">apiKey</span>: <span class="str">'sk-xxxxxxxxxxxxxxxx'</span>
})
<span class="c">// 调用 AI 智能体</span>
<span class="kw">const</span> <span class="var">reply</span> = <span class="kw">await</span> <span class="var">client</span>.<span class="fn">agent</span>.<span class="fn">chat</span>({
<span class="prop">message</span>: <span class="str">'帮我分析本月销售数据'</span>
})</code></pre>
</div>
</div>
</div>
</div>
<div class="dev-body">
<!-- 数据统计卡片头部 -->
<div class="mb-4 flex items-center justify-between">
<div class="flex items-center gap-2">
<h3 class="text-base font-semibold text-gray-800">📊 数据概览</h3>
<a-tag v-if="!loading && statsData.totalUsage > 0" color="blue">
本月调用: {{ statsData.totalUsage }}
</a-tag>
<a-tag v-if="!loading && statsData.activeKeys > 0" color="green">
活跃 Key: {{ statsData.activeKeys }}
</a-tag>
</div>
<a-button size="small" :loading="loading" @click="loadStats">
<template #icon><reload-outlined /></template>
刷新数据
</a-button>
</div>
<!-- 数据统计卡片 -->
<a-row :gutter="[16, 16]" class="mb-6">
<a-col :xs="12" :md="6" v-for="stat in stats" :key="stat.label">
<a-spin :spinning="loading">
<div class="stat-card" :class="stat.color">
<div class="stat-icon">{{ stat.icon }}</div>
<div class="stat-info">
<div class="stat-value">{{ stat.value }}</div>
<div class="stat-label">{{ stat.label }}</div>
</div>
</div>
</a-spin>
</a-col>
</a-row>
<a-row :gutter="[16, 16]">
<!-- 快捷入口 -->
<a-col :xs="24" :lg="14">
<div class="panel">
<div class="panel-header">
<span class="panel-title"> 快捷入口</span>
</div>
<div class="quick-entries">
<div
v-for="entry in quickEntries"
:key="entry.label"
class="quick-entry"
@click="navigateTo(entry.to)"
>
<div class="quick-entry-icon" :class="entry.iconClass">{{ entry.icon }}</div>
<div class="quick-entry-text">
<div class="quick-entry-label">{{ entry.label }}</div>
<div class="quick-entry-desc">{{ entry.desc }}</div>
</div>
<div class="quick-entry-arrow"></div>
</div>
</div>
</div>
<!-- 最近动态时间线 -->
<div class="panel mt-4">
<div class="panel-header">
<span class="panel-title">🕐 最近动态</span>
<a-button size="small" type="link" @click="navigateTo('/developer/apps')">查看全部</a-button>
</div>
<div class="timeline-list">
<div v-if="recentActivities.length === 0" class="timeline-empty">
<div class="empty-icon">🗒</div>
<div class="empty-text">暂无操作记录</div>
</div>
<div v-for="(act, i) in recentActivities" :key="i" class="timeline-item">
<div class="timeline-dot" :class="act.type" />
<div class="timeline-line" v-if="i < recentActivities.length - 1" />
<div class="timeline-body">
<div class="timeline-title">{{ act.title }}</div>
<div class="timeline-meta">
<span class="timeline-app">{{ act.app }}</span>
<span class="timeline-sep">·</span>
<span class="timeline-time">{{ act.time }}</span>
</div>
</div>
</div>
</div>
</div>
</a-col>
<!-- 最新动态 / 开发者公告 -->
<a-col :xs="24" :lg="10">
<div class="panel">
<div class="panel-header">
<span class="panel-title">📢 开发者公告</span>
<a-tag color="red">NEW</a-tag>
</div>
<div class="notice-list">
<div v-for="notice in notices" :key="notice.title" class="notice-item">
<div class="notice-dot" :class="notice.type" />
<div class="notice-content">
<div class="notice-title">{{ notice.title }}</div>
<div class="notice-date">{{ notice.date }}</div>
</div>
</div>
</div>
</div>
<!-- 快速帮助 -->
<div class="panel mt-4">
<div class="panel-header">
<span class="panel-title">🆘 快速帮助</span>
</div>
<div class="help-links">
<a
v-for="link in helpLinks"
:key="link.label"
class="help-link"
@click="navigateTo(link.to)"
>
<span>{{ link.icon }}</span> {{ link.label }}
</a>
</div>
</div>
<!-- 服务状态 -->
<div class="panel mt-4">
<div class="panel-header">
<span class="panel-title">📡 服务状态</span>
<a-tag color="green"> 全部正常</a-tag>
</div>
<div class="srv-list">
<div v-for="srv in serviceStatus" :key="srv.name" class="srv-item">
<div class="srv-dot" :class="srv.status" />
<span class="srv-name">{{ srv.name }}</span>
<span class="srv-latency">{{ srv.latency }}</span>
</div>
</div>
</div>
</a-col>
</a-row>
<!-- SDK 支持状态 -->
<div class="panel mt-4">
<div class="panel-header">
<span class="panel-title">📦 SDK 支持状态</span>
<a-button size="small" type="link" @click="navigateTo('/developer/docs')">查看文档</a-button>
</div>
<div class="sdk-grid">
<div v-for="sdk in sdkStatus" :key="sdk.lang" class="sdk-item">
<span class="sdk-emoji">{{ sdk.emoji }}</span>
<div class="sdk-info">
<div class="sdk-lang">{{ sdk.lang }}</div>
<div class="sdk-desc">{{ sdk.desc }}</div>
</div>
<a-tag :color="sdk.tagColor" class="sdk-tag">{{ sdk.status }}</a-tag>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { getUserInfo } from '@/api/layout'
import { getToken } from '@/utils/token-util'
import { setAuthzFromUser } from '@/utils/permission'
// TODO: 后端接口就绪后解除注释
// import { getDeveloperStats } from '@/api/developer'
import { ReloadOutlined } from '@ant-design/icons-vue'
import type { User } from '@/api/system/user/model'
definePageMeta({ layout: 'developer' })
useHead({ title: '概览 - 开发者中心' })
const user = ref<User | null>(null)
const loading = ref(false)
const statsData = ref({
appCount: 0,
apiKeyCount: 0,
pendingRequests: 0,
repositoryAccess: 0,
totalUsage: 0,
activeKeys: 0
})
const userDisplayName = computed(() => {
const u = user.value
return u?.nickname?.trim() || u?.username?.trim() || u?.phone?.trim() || '开发者'
})
// 加载统计数据TODO: 后端接口就绪后替换 Mock
const loadStats = async () => {
loading.value = true
try {
// Mock 数据,后端接口就绪后替换为:
// const response = await getDeveloperStats()
// if (response.data?.success) { statsData.value = response.data.data }
statsData.value = { appCount: 12, apiKeyCount: 8, pendingRequests: 3, repositoryAccess: 5, totalUsage: 456200, activeKeys: 6 }
} catch (error) {
console.error('加载统计数据失败:', error)
} finally {
loading.value = false
}
}
onMounted(async () => {
const token = getToken()
if (!token) return
try {
const me = await getUserInfo()
user.value = me
setAuthzFromUser(me)
// 加载统计数据
await loadStats()
} catch { /* ignore */ }
})
const stats = computed(() => [
{
icon: '📦',
label: '可开发应用',
value: statsData.value.appCount > 0 ? statsData.value.appCount.toString() : '-',
color: 'blue'
},
{
icon: '🔑',
label: 'API Key 数量',
value: statsData.value.apiKeyCount > 0 ? statsData.value.apiKeyCount.toString() : '-',
color: 'purple'
},
{
icon: '📋',
label: '待处理申请',
value: statsData.value.pendingRequests > 0 ? statsData.value.pendingRequests.toString() : '-',
color: 'orange'
},
{
icon: '💻',
label: '已访问仓库',
value: statsData.value.repositoryAccess > 0 ? statsData.value.repositoryAccess.toString() : '-',
color: 'green'
},
])
const quickEntries = [
{
icon: '🔑', iconClass: 'purple',
label: 'API Key 管理',
desc: '创建、查看和管理你的 API Key',
to: '/developer/apikeys',
},
{
icon: '📦', iconClass: 'blue',
label: '应用中心',
desc: '查看订阅的应用与后台入口',
to: '/developer/apps',
},
{
icon: '💻', iconClass: 'cyan',
label: '源码与仓库',
desc: '申请仓库权限,获取完整源代码',
to: '/developer/source',
},
{
icon: '📚', iconClass: 'orange',
label: '开发文档',
desc: 'API 参考、SDK 使用、AI 功能与部署指南',
to: '/developer/docs',
},
{
icon: '🐙', iconClass: 'gray',
label: 'Git 账号绑定',
desc: '绑定 Gitea 账号,获取仓库访问权限',
to: '/developer/git',
},
{
icon: '💬', iconClass: 'green',
label: '支持与反馈',
desc: '遇到问题?提交工单或联系我们',
to: '/developer/support',
},
]
const notices = [
{ type: 'blue', title: 'TypeScript SDK v1.2.0 正式发布', date: '2026-03-25' },
{ type: 'green', title: 'OpenAPI 3.0 文档已全面更新', date: '2026-03-20' },
{ type: 'orange', title: 'AI Agent API 新增流式输出支持', date: '2026-03-15' },
{ type: 'gray', title: 'API Rate Limit 规则调整公告', date: '2026-03-10' },
]
const helpLinks = [
{ icon: '📘', label: '快速开始指南', to: '/developer/docs/getting-started/quickstart' },
{ icon: '🔌', label: 'REST API 参考', to: '/developer/docs/api/rest-api' },
{ icon: '🐙', label: '绑定 Git 账号', to: '/developer/git' },
{ icon: '📋', label: '权限申请流程', to: '/developer/source' },
]
const sdkStatus = [
{ emoji: '🟦', lang: 'TypeScript / JavaScript', desc: '官方维护,完整类型定义', status: '稳定版', tagColor: 'green' },
{ emoji: '🐍', lang: 'Python', desc: '支持 asyncio适合 AI 场景', status: '稳定版', tagColor: 'green' },
{ emoji: '☕', lang: 'Java', desc: '企业级 Spring Boot 接入', status: 'Beta', tagColor: 'orange' },
{ emoji: '🐹', lang: 'Go', desc: '高性能微服务场景', status: 'Beta', tagColor: 'orange' },
{ emoji: '🐘', lang: 'PHP', desc: 'Laravel / ThinkPHP 框架', status: '规划中', tagColor: 'default' },
]
// 最近动态(可替换为真实 API 数据)
const recentActivities = [
{ type: 'blue', title: '创建了 API Key', app: '人事管理系统', time: '10 分钟前' },
{ type: 'green', title: '应用上架审核通过', app: '电商平台', time: '2 小时前' },
{ type: 'orange', title: '提交了源码访问申请', app: '全局', time: '昨天 14:30' },
{ type: 'purple', title: '发布了新版本 v1.2.0', app: 'OA 系统', time: '2天前' },
{ type: 'gray', title: '绑定了 Gitea 账号', app: '全局', time: '3天前' },
]
// 服务状态
const serviceStatus = [
{ name: 'REST API', status: 'ok', latency: '28ms' },
{ name: 'AI Agent API', status: 'ok', latency: '312ms' },
{ name: 'Gitea 仓库', status: 'ok', latency: '45ms' },
{ name: 'SDK CDN', status: 'ok', latency: '18ms' },
{ name: 'Webhook', status: 'ok', latency: '—' },
]
</script>
<style scoped>
.dev-page {
min-height: 100%;
}
/* Hero 区域 */
.dev-hero {
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 40%, #1e3a5f 100%);
padding: 32px 28px 28px;
position: relative;
overflow: hidden;
}
.dev-hero::before {
content: '';
position: absolute;
top: -80px;
right: -80px;
width: 300px;
height: 300px;
border-radius: 50%;
background: rgba(99, 102, 241, 0.2);
filter: blur(60px);
}
.dev-hero::after {
content: '';
position: absolute;
bottom: -60px;
left: 60px;
width: 200px;
height: 200px;
border-radius: 50%;
background: rgba(139, 92, 246, 0.15);
filter: blur(50px);
}
.dev-hero-content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 24px;
}
.dev-hero-left {
flex: 1;
}
.dev-hero-greeting {
color: rgba(165, 180, 252, 0.9);
font-size: 14px;
margin-bottom: 8px;
}
.dev-hero-title {
color: #fff;
font-size: 26px;
font-weight: 700;
margin: 0 0 8px;
line-height: 1.3;
}
.dev-hero-desc {
color: rgba(199, 210, 254, 0.8);
font-size: 14px;
margin: 0;
line-height: 1.6;
}
.dev-hero-right {
flex-shrink: 0;
width: 360px;
}
@media (max-width: 900px) {
.dev-hero-right { display: none; }
}
/* 代码片段 */
.dev-code-snippet {
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(15, 15, 35, 0.8);
backdrop-filter: blur(10px);
}
.code-header {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.2);
}
.code-dots { display: flex; gap: 5px; }
.dot {
width: 10px; height: 10px; border-radius: 50%;
}
.dot.red { background: #ff5f57; }
.dot.yellow { background: #febc2e; }
.dot.green { background: #28c840; }
.code-filename {
font-family: monospace;
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
}
.code-body {
padding: 14px 16px;
margin: 0;
font-family: 'Fira Code', 'JetBrains Mono', monospace;
font-size: 12px;
line-height: 1.7;
overflow-x: auto;
color: #e2e8f0;
}
.code-body .c { color: #64748b; }
.code-body .kw { color: #93c5fd; }
.code-body .cls { color: #fde68a; }
.code-body .str { color: #86efac; }
.code-body .var { color: #a5b4fc; }
.code-body .prop { color: #cbd5e1; }
.code-body .fn { color: #fde68a; }
/* 主体内容 */
.dev-body {
padding: 20px 24px 24px;
}
/* 统计卡片 */
.stat-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border-radius: 12px;
border: 1px solid transparent;
transition: all 0.2s;
}
.stat-card:hover { transform: translateY(-1px); }
.stat-card.blue { background: #eff6ff; border-color: #dbeafe; }
.stat-card.purple { background: #f5f3ff; border-color: #e9d5ff; }
.stat-card.orange { background: #fff7ed; border-color: #fed7aa; }
.stat-card.green { background: #f0fdf4; border-color: #bbf7d0; }
.stat-icon {
font-size: 28px;
flex-shrink: 0;
}
.stat-value {
font-size: 22px;
font-weight: 700;
color: rgba(0, 0, 0, 0.85);
line-height: 1.2;
}
.stat-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
margin-top: 2px;
}
/* 面板通用 */
.panel {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 12px;
overflow: hidden;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
border-bottom: 1px solid #f5f5f5;
}
.panel-title {
font-size: 14px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
/* 快捷入口 */
.quick-entries {
padding: 8px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.quick-entry {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
border-radius: 10px;
cursor: pointer;
transition: all 0.15s;
border: 1px solid transparent;
}
.quick-entry:hover {
background: #f5f7ff;
border-color: #e0e7ff;
}
.quick-entry-icon {
width: 36px;
height: 36px;
flex-shrink: 0;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.quick-entry-icon.blue { background: #eff6ff; }
.quick-entry-icon.purple { background: #f5f3ff; }
.quick-entry-icon.cyan { background: #ecfeff; }
.quick-entry-icon.orange { background: #fff7ed; }
.quick-entry-icon.gray { background: #f9fafb; }
.quick-entry-icon.green { background: #f0fdf4; }
.quick-entry-text { flex: 1; min-width: 0; }
.quick-entry-label {
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 2px;
}
.quick-entry-desc {
font-size: 12px;
color: rgba(0, 0, 0, 0.4);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.quick-entry-arrow {
color: rgba(0, 0, 0, 0.25);
font-size: 14px;
flex-shrink: 0;
transition: transform 0.15s;
}
.quick-entry:hover .quick-entry-arrow {
color: #4f46e5;
transform: translateX(3px);
}
/* 公告列表 */
.notice-list {
padding: 8px 16px;
}
.notice-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 0;
border-bottom: 1px solid #f9f9f9;
}
.notice-item:last-child { border-bottom: none; }
.notice-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 5px;
}
.notice-dot.blue { background: #4f46e5; }
.notice-dot.green { background: #16a34a; }
.notice-dot.orange { background: #ea580c; }
.notice-dot.gray { background: #9ca3af; }
.notice-title {
font-size: 13px;
color: rgba(0, 0, 0, 0.75);
line-height: 1.4;
}
.notice-date {
font-size: 11px;
color: rgba(0, 0, 0, 0.35);
margin-top: 2px;
}
/* 帮助链接 */
.help-links {
padding: 10px 14px 14px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.help-link {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 10px;
border-radius: 8px;
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
cursor: pointer;
transition: all 0.15s;
}
.help-link:hover {
background: #f5f7ff;
color: #4f46e5;
}
/* SDK 状态 */
.sdk-grid {
padding: 12px 16px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 10px;
}
.sdk-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
border-radius: 10px;
border: 1px solid #f0f0f0;
background: #fafafa;
transition: all 0.15s;
}
.sdk-item:hover {
border-color: #e0e7ff;
background: #f5f7ff;
}
.sdk-emoji {
font-size: 24px;
flex-shrink: 0;
}
.sdk-info { flex: 1; min-width: 0; }
.sdk-lang {
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
.sdk-desc {
font-size: 12px;
color: rgba(0, 0, 0, 0.4);
margin-top: 2px;
}
.sdk-tag {
flex-shrink: 0;
font-size: 11px;
}
/* 最近动态时间线 */
.timeline-list {
padding: 12px 18px 16px;
position: relative;
}
.timeline-empty {
display: flex;
flex-direction: column;
align-items: center;
padding: 24px;
text-align: center;
}
.timeline-item {
display: flex;
align-items: flex-start;
gap: 12px;
position: relative;
padding-bottom: 14px;
}
.timeline-item:last-child { padding-bottom: 0; }
.timeline-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 5px;
position: relative;
z-index: 1;
}
.timeline-dot.blue { background: #4f46e5; }
.timeline-dot.green { background: #16a34a; }
.timeline-dot.orange { background: #ea580c; }
.timeline-dot.purple { background: #7c3aed; }
.timeline-dot.gray { background: #9ca3af; }
.timeline-line {
position: absolute;
left: 3px;
top: 14px;
bottom: 0;
width: 2px;
background: #f0f0f0;
}
.timeline-body { flex: 1; min-width: 0; }
.timeline-title {
font-size: 13px;
color: rgba(0, 0, 0, 0.8);
font-weight: 500;
margin-bottom: 3px;
}
.timeline-meta {
display: flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: rgba(0, 0, 0, 0.38);
}
.timeline-sep { opacity: 0.5; }
.empty-icon { font-size: 28px; margin-bottom: 8px; }
.empty-text { font-size: 13px; color: rgba(0, 0, 0, 0.4); }
/* 服务状态 */
.srv-list {
padding: 8px 16px 12px;
}
.srv-item {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 0;
border-bottom: 1px solid #f9f9f9;
}
.srv-item:last-child { border-bottom: none; }
.srv-dot {
width: 7px;
height: 7px;
border-radius: 50%;
flex-shrink: 0;
}
.srv-dot.ok { background: #16a34a; }
.srv-dot.warn { background: #f59e0b; }
.srv-dot.down { background: #dc2626; }
.srv-name {
flex: 1;
font-size: 13px;
color: rgba(0, 0, 0, 0.72);
}
.srv-latency {
font-size: 12px;
color: rgba(0, 0, 0, 0.35);
font-family: monospace;
}
</style>