From 471669c6cf87378186f77c335dbf160a8055c3fb Mon Sep 17 00:00:00 2001 From: WangDL Date: Sun, 24 May 2026 18:06:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20M4-06=20=E2=80=94=20project=20center=20?= =?UTF-8?q?page=20with=20repos/issues/milestones/releases/runners?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace GiteaEmbed iframe with full ProjectCenter page - Tabs: repos, issues, milestones, releases, runners, Gitea embed - Rename menu 代码仓库 → 项目中心 Co-Authored-By: Claude Opus 4.7 --- src/App.tsx | 4 +- src/config/menu.tsx | 2 +- src/pages/GiteaEmbed.tsx | 139 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 136 insertions(+), 9 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d9a8f8c..ef6bd3d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import AdminLayout from './layouts/AdminLayout' const Login = lazy(() => import('./pages/Login')) const KnowledgeBasesPage = lazy(() => import('./pages/KnowledgeBases')) const BillingPage = lazy(() => import('./pages/Billing')) -const GiteaEmbed = lazy(() => import('./pages/GiteaEmbed')) +const ProjectCenter = lazy(() => import('./pages/GiteaEmbed')) const ConfigPage = lazy(() => import("./pages/Config")) const SecurityEventsPage = lazy(() => import('./pages/SecurityEvents')) const ThrottlePage = lazy(() => import('./pages/Throttle')) @@ -131,7 +131,7 @@ function App() { }> + }> } /> , requiredRole: 'ADMIN' }, { path: '/audit', name: '审计日志', icon: , requiredRole: 'ADMIN' }, { path: '/billing', name: 'API 用量', icon: , requiredRole: 'SUPER_ADMIN' }, - { path: '/git', name: '代码仓库', icon: }, + { path: '/git', name: '项目中心', icon: }, { path: '/ops', name: '系统运维', icon: , requiredRole: 'SUPER_ADMIN', children: [ { path: '/throttle', name: '限流管理' }, { path: '/security-events', name: '安全事件' }, diff --git a/src/pages/GiteaEmbed.tsx b/src/pages/GiteaEmbed.tsx index d3bb4c6..802f1e8 100644 --- a/src/pages/GiteaEmbed.tsx +++ b/src/pages/GiteaEmbed.tsx @@ -1,10 +1,137 @@ -export default function GiteaEmbed() { +import { useState } from 'react' +import { Table, Tag, Tabs, Typography, Card, Row, Col, Spin } from 'antd' +import { CodeOutlined, BranchesOutlined, RocketOutlined } from '@ant-design/icons' +import { useQuery } from '@tanstack/react-query' +import { api } from '@/services/http-client' + +const { Title, Text } = Typography + +export default function ProjectCenter() { + const [selectedRepo, setSelectedRepo] = useState(null) + const [tab, setTab] = useState('repos') + + const { data: repos, isLoading } = useQuery({ + queryKey: ['gitea', 'repos'], + queryFn: () => api.get('/admin-api/projects/repos').then(d => d ?? []), + }) + + const repoParts = selectedRepo?.split('/') || [] + + const { data: issues } = useQuery({ + queryKey: ['gitea', 'issues', selectedRepo], + queryFn: () => api.get(`/admin-api/projects/repos/${repoParts[0]}/${repoParts[1]}/issues?state=all`).then(d => d ?? []), + enabled: tab === 'issues' && repoParts.length === 2, + }) + + const { data: milestones } = useQuery({ + queryKey: ['gitea', 'milestones', selectedRepo], + queryFn: () => api.get(`/admin-api/projects/repos/${repoParts[0]}/${repoParts[1]}/milestones`).then(d => d ?? []), + enabled: tab === 'milestones' && repoParts.length === 2, + }) + + const { data: releases } = useQuery({ + queryKey: ['gitea', 'releases', selectedRepo], + queryFn: () => api.get(`/admin-api/projects/repos/${repoParts[0]}/${repoParts[1]}/releases`).then(d => d ?? []), + enabled: tab === 'releases' && repoParts.length === 2, + }) + + const { data: runners } = useQuery({ + queryKey: ['gitea', 'runners'], + queryFn: () => api.get('/admin-api/projects/runners').then(d => d ?? []), + enabled: tab === 'runners', + }) + + const repoColumns = [ + { title: '仓库', dataIndex: 'fullName', width: 200, render: (n: string, r: any) => { setSelectedRepo(n); setTab('issues') }}>{n} }, + { title: '描述', dataIndex: 'description', width: 200, ellipsis: true, render: (d: string) => d || '-' }, + { title: 'Issues', dataIndex: 'openIssues', width: 70 }, + { title: 'PRs', dataIndex: 'openPulls', width: 70 }, + { title: '里程碑', dataIndex: 'milestones', width: 70 }, + { title: 'Stars', dataIndex: 'stars', width: 60 }, + { title: '分支', dataIndex: 'defaultBranch', width: 80, render: (b: string) => {b} }, + { title: '更新', dataIndex: 'updatedAt', width: 100, render: (d: string) => d ? new Date(d).toLocaleDateString() : '-' }, + ] + + const issueColumns = [ + { title: '#', dataIndex: 'number', width: 60 }, + { title: '标题', dataIndex: 'title', width: 300, ellipsis: true }, + { title: '状态', dataIndex: 'state', width: 70, render: (s: string) => {s} }, + { title: '创建者', dataIndex: ['user', 'login'], width: 100 }, + { title: '更新', dataIndex: 'updated_at', width: 100, render: (d: string) => d ? new Date(d).toLocaleDateString() : '-' }, + ] + return ( -
-