846 lines
22 KiB
Vue
846 lines
22 KiB
Vue
<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>
|