This commit is contained in:
2026-06-09 21:19:30 +08:00
parent 8b41ff9360
commit 0484e79978
66 changed files with 4043 additions and 533 deletions
+155 -5
View File
@@ -1,16 +1,166 @@
<template>
<AppShell>
<div class="space-y-4">
<h2 class="text-xl font-semibold">
欢迎{{ auth.user?.display_name }}
</h2>
<p>这是占位 dashboard后续 plan 会接入项目/工作区/Issue</p>
<div class="space-y-6">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold">
欢迎{{ auth.user?.display_name }}
</h2>
<NButton
type="primary"
@click="router.push({ name: 'project-list' })"
>
新建 / 管理项目
</NButton>
</div>
<!-- 统计卡片 -->
<NGrid
:cols="3"
:x-gap="12"
>
<NGi>
<NCard>
<NStatistic
label="活跃项目"
:value="activeCount"
/>
</NCard>
</NGi>
<NGi>
<NCard>
<NStatistic
label="归档项目"
:value="archivedCount"
/>
</NCard>
</NGi>
<NGi>
<NCard>
<NStatistic
label="分配给我的待办议题"
:value="myIssues.length"
/>
</NCard>
</NGi>
</NGrid>
<!-- 分配给我的议题 -->
<NCard title="分配给我的议题(未关闭)">
<NSpin :show="loading">
<div
v-if="myIssues.length === 0"
class="text-sm text-gray-400 py-2"
>
暂无分配给你的未关闭议题
</div>
<ul
v-else
class="divide-y"
>
<li
v-for="row in myIssues"
:key="row.issue.id"
class="py-2 flex items-center justify-between cursor-pointer hover:bg-gray-50 px-1"
@click="goIssue(row)"
>
<span class="text-sm">
<span class="font-mono text-gray-500 mr-2">{{ row.projectSlug }}#{{ row.issue.number }}</span>
{{ row.issue.title }}
</span>
<span class="text-xs text-gray-400">{{ row.projectName }}</span>
</li>
</ul>
</NSpin>
</NCard>
<!-- 最近项目 -->
<NCard title="项目">
<NSpin :show="loading">
<div
v-if="activeProjects.length === 0"
class="text-sm text-gray-400 py-2"
>
还没有项目去创建一个吧
</div>
<ul
v-else
class="divide-y"
>
<li
v-for="p in activeProjects"
:key="p.id"
class="py-2 flex items-center justify-between cursor-pointer hover:bg-gray-50 px-1"
@click="router.push({ name: 'project-home', params: { slug: p.slug } })"
>
<span class="text-sm font-medium">{{ p.name }}</span>
<span class="font-mono text-xs text-gray-400">{{ p.slug }}</span>
</li>
</ul>
</NSpin>
</NCard>
</div>
</AppShell>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { NButton, NCard, NGrid, NGi, NStatistic, NSpin } from 'naive-ui'
import AppShell from '@/layouts/AppShell.vue'
import { useAuthStore } from '@/stores/auth'
import { projectsApi, type Project, type Issue } from '@/api/projects'
const auth = useAuthStore()
const router = useRouter()
const projects = ref<Project[]>([])
const loading = ref(false)
interface MyIssueRow {
issue: Issue
projectSlug: string
projectName: string
}
const myIssues = ref<MyIssueRow[]>([])
const activeProjects = computed(() => projects.value.filter((p) => !p.archived_at))
const activeCount = computed(() => activeProjects.value.length)
const archivedCount = computed(() => projects.value.filter((p) => p.archived_at).length)
onMounted(async () => {
loading.value = true
try {
const out = await projectsApi.list(true)
projects.value = out.items
const uid = auth.user?.id
if (uid) {
// 跨活跃项目聚合“分配给我且未关闭”的议题。项目数通常较小,直接并发查询。
const lists = await Promise.all(
activeProjects.value.map(async (p) => {
try {
const res = await projectsApi.listIssues(p.slug, { assignee: uid, status: 'open' })
return res.items.map((issue) => ({
issue,
projectSlug: p.slug,
projectName: p.name,
}))
} catch {
return []
}
}),
)
myIssues.value = lists.flat()
}
} finally {
loading.value = false
}
})
function goIssue(row: MyIssueRow) {
router.push({
name: 'issue-detail',
params: { slug: row.projectSlug, number: row.issue.number },
})
}
</script>