Files
tiantian-system/app/pages/developer/docs/index.vue
2026-04-08 17:10:58 +08:00

423 lines
9.8 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.

<template>
<div class="dev-page">
<div class="page-header">
<div>
<h2 class="page-title">📚 开发文档</h2>
<p class="page-desc">从快速上手到深度定制全面的开发指引与 API 参考</p>
</div>
<a-input-search
v-model:value="searchKeyword"
placeholder="搜索文档..."
style="width: 240px"
allow-clear
/>
</div>
<div class="page-body">
<!-- 精选文档 -->
<div class="featured-section">
<div class="section-label">🚀 推荐开始</div>
<a-row :gutter="[16, 16]">
<a-col :xs="24" :md="8" v-for="item in featuredDocs" :key="item.title">
<div class="featured-card" @click="navigateTo(item.to)">
<div class="featured-badge" :class="item.badgeColor">{{ item.badge }}</div>
<div class="featured-icon">{{ item.icon }}</div>
<h3 class="featured-title">{{ item.title }}</h3>
<p class="featured-desc">{{ item.desc }}</p>
</div>
</a-col>
</a-row>
</div>
<a-row :gutter="[16, 0]" class="mt-5">
<!-- 左侧分类目录 -->
<a-col :xs="24" :lg="6">
<div class="toc-panel">
<div class="toc-header">📂 文档分类</div>
<div
v-for="cat in categories"
:key="cat.key"
class="toc-category"
:class="{ active: activeCategory === cat.key }"
@click="activeCategory = cat.key"
>
<span class="toc-icon">{{ cat.icon }}</span>
<span class="toc-label">{{ cat.label }}</span>
<span class="toc-count">{{ cat.count }}</span>
</div>
</div>
</a-col>
<!-- 右侧文档列表 -->
<a-col :xs="24" :lg="18">
<div class="doc-list">
<div
v-for="doc in filteredDocs"
:key="doc.stem?.path || doc.title"
class="doc-item"
@click="navigateTo(doc.stem?.path || '')"
>
<div class="doc-icon">{{ getCategoryIcon(doc.category) }}</div>
<div class="doc-content">
<div class="doc-title-row">
<span class="doc-title">{{ doc.title }}</span>
</div>
<div class="doc-desc">{{ doc.description }}</div>
<div class="doc-meta">
<span>{{ getCategoryLabel(doc.category) }}</span>
</div>
</div>
<div class="doc-arrow"></div>
</div>
<div v-if="filteredDocs.length === 0" class="empty-state">
<div class="empty-icon">🔍</div>
<div class="empty-title">没有找到相关文档</div>
<div class="empty-desc">换个关键词试试吧</div>
</div>
</div>
</a-col>
</a-row>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({ layout: 'developer' })
useHead({ title: '开发文档 - 开发者中心' })
const searchKeyword = ref('')
const activeCategory = ref('all')
const router = useRouter()
// 查询所有文档
const { data: docs } = await useAsyncData('docs-list', () =>
queryCollection('docs')
.order('order', 'ASC')
.all()
)
// 分类定义
const categoryMap: Record<string, { label: string; icon: string }> = {
'getting-started': { label: '快速开始', icon: '⚡' },
'api': { label: 'API 参考', icon: '🔌' },
'ai': { label: 'AI 功能', icon: '🤖' },
'deploy': { label: '部署运维', icon: '🚢' },
}
function getCategoryLabel(cat: string) {
return categoryMap[cat]?.label || cat
}
function getCategoryIcon(cat: string) {
return categoryMap[cat]?.icon || '📄'
}
// 分类列表(带数量)
const categories = computed(() => {
const cats: { key: string; icon: string; label: string; count: number }[] = [
{ key: 'all', icon: '🌐', label: '全部文档', count: docs.value?.length || 0 },
]
for (const [key, val] of Object.entries(categoryMap)) {
const count = docs.value?.filter((d: any) => d.category === key).length || 0
cats.push({ key, icon: val.icon, label: val.label, count })
}
return cats
})
// 精选文档
const featuredDocs = [
{
icon: '⚡',
badge: '入门必读',
badgeColor: 'blue',
title: '5 分钟快速上手',
desc: '安装 SDK获取 API Key发送第一个请求立即体验平台能力。',
to: '/developer/docs/getting-started/quickstart',
},
{
icon: '🤖',
badge: 'AI 推荐',
badgeColor: 'purple',
title: 'AI 智能体接入',
desc: '集成 AI Agent实现知识库问答、工作流触发与多模型切换。',
to: '/developer/docs/ai/agent',
},
{
icon: '🚢',
badge: '生产就绪',
badgeColor: 'green',
title: '私有化部署指南',
desc: 'Docker Compose 一键部署HTTPS 配置、备份策略与版本升级。',
to: '/developer/docs/deploy/private-deploy',
},
]
// 过滤文档
const filteredDocs = computed(() => {
let list = docs.value || []
if (activeCategory.value !== 'all') {
list = list.filter((d: any) => d.category === activeCategory.value)
}
const kw = searchKeyword.value.trim().toLowerCase()
if (kw) {
list = list.filter(
(d: any) =>
(d.title as string)?.toLowerCase().includes(kw) ||
(d.description as string)?.toLowerCase().includes(kw)
)
}
return list
})
function navigateTo(path: string) {
// Nuxt Content 的 path 是 /docs/xxx需要转换为 /developer/docs/xxx
const target = path.replace(/^\/docs/, '/developer/docs')
router.push(target)
}
</script>
<style scoped>
.dev-page { min-height: 100%; }
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 28px 16px;
border-bottom: 1px solid #f0f0f0;
gap: 16px;
}
.page-title {
font-size: 20px;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
margin: 0 0 4px;
}
.page-desc {
font-size: 14px;
color: rgba(0, 0, 0, 0.45);
margin: 0;
}
.page-body {
padding: 20px 24px 28px;
}
/* 精选文档 */
.featured-section { margin-bottom: 0; }
.section-label {
font-size: 13px;
font-weight: 600;
color: rgba(0, 0, 0, 0.5);
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.featured-card {
padding: 20px;
border-radius: 12px;
border: 1px solid #f0f0f0;
background: #fff;
cursor: pointer;
transition: all 0.2s;
position: relative;
overflow: hidden;
}
.featured-card:hover {
border-color: #c7d2fe;
box-shadow: 0 4px 20px rgba(79, 70, 229, 0.1);
transform: translateY(-2px);
}
.featured-badge {
position: absolute;
top: 0;
right: 0;
padding: 4px 10px;
font-size: 11px;
font-weight: 600;
border-radius: 0 12px 0 10px;
}
.featured-badge.blue { background: #eff6ff; color: #3b82f6; }
.featured-badge.purple { background: #f5f3ff; color: #7c3aed; }
.featured-badge.green { background: #f0fdf4; color: #16a34a; }
.featured-icon {
font-size: 32px;
margin-bottom: 12px;
}
.featured-title {
font-size: 15px;
font-weight: 700;
color: rgba(0, 0, 0, 0.85);
margin: 0 0 8px;
}
.featured-desc {
font-size: 13px;
color: rgba(0, 0, 0, 0.5);
line-height: 1.6;
margin: 0 0 12px;
}
/* 目录面板 */
.toc-panel {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 12px;
overflow: hidden;
}
.toc-header {
padding: 14px 16px;
font-size: 13px;
font-weight: 600;
color: rgba(0, 0, 0, 0.55);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #f5f5f5;
}
.toc-category {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
cursor: pointer;
transition: all 0.15s;
border-bottom: 1px solid #f9f9f9;
}
.toc-category:last-child { border-bottom: none; }
.toc-category:hover { background: #f5f7ff; }
.toc-category.active {
background: #f0f0ff;
color: #4f46e5;
}
.toc-icon { font-size: 16px; flex-shrink: 0; }
.toc-label {
flex: 1;
font-size: 13px;
color: rgba(0, 0, 0, 0.72);
}
.toc-category.active .toc-label {
color: #4f46e5;
font-weight: 500;
}
.toc-count {
font-size: 11px;
background: #f0f0f0;
color: rgba(0, 0, 0, 0.45);
padding: 1px 7px;
border-radius: 20px;
}
.toc-category.active .toc-count {
background: #e0e7ff;
color: #4f46e5;
}
/* 文档列表 */
.doc-list {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 12px;
overflow: hidden;
}
.doc-item {
display: flex;
align-items: center;
gap: 14px;
padding: 16px 18px;
border-bottom: 1px solid #f9f9f9;
cursor: pointer;
transition: all 0.15s;
}
.doc-item:last-child { border-bottom: none; }
.doc-item:hover { background: #f9faff; }
.doc-icon {
font-size: 24px;
flex-shrink: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 10px;
}
.doc-content { flex: 1; min-width: 0; }
.doc-title-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.doc-title {
font-size: 14px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
.doc-desc {
font-size: 12px;
color: rgba(0, 0, 0, 0.4);
margin-bottom: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.doc-meta {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: rgba(0, 0, 0, 0.35);
}
.doc-arrow {
font-size: 20px;
color: rgba(0, 0, 0, 0.2);
flex-shrink: 0;
transition: all 0.15s;
}
.doc-item:hover .doc-arrow {
color: #4f46e5;
transform: translateX(3px);
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 48px 24px;
text-align: center;
}
.empty-icon { font-size: 40px; margin-bottom: 10px; }
.empty-title { font-size: 15px; font-weight: 600; color: rgba(0, 0, 0, 0.7); }
.empty-desc { font-size: 13px; color: rgba(0, 0, 0, 0.4); margin-top: 4px; }
</style>