Files
agentic-coding-workflow/web/src/views/HomeView.vue
T
2026-06-12 14:36:41 +08:00

146 lines
4.4 KiB
Vue

<template>
<AppShell>
<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="goToProjects"> 新建 / 管理项目 </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="goToProject(p)"
>
<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 },
})
}
function goToProjects() {
router.push({ name: 'project-list' })
}
function goToProject(p: Project) {
router.push({ name: 'project-home', params: { slug: p.slug } })
}
</script>