feat: 添加仓库信息卡片和仓库页面,优化仓库路由

This commit is contained in:
2026-02-25 23:12:39 +08:00
parent bbb762db97
commit 5a769a6748
7 changed files with 177 additions and 18 deletions

View File

@@ -78,12 +78,19 @@ export const useLayoutStore = create<LayoutStore>((set, get) => ({
const token = await queryLogin.getToken();
if (token) {
set({ me: {} })
const me = await queryLogin.getMe();
// const user = await queryLogin.checkLocalUser() as UserInfo;
const user = me.code === 200 ? me.data : undefined;
if (user) {
set({ me: user });
set({ isAdmin: user.orgs?.includes?.('admin') || false });
let me: UserInfo | undefined = undefined;
const _user = await queryLogin.checkLocalUser() as UserInfo;
if (_user) {
me = _user;
}
if (!me) {
const res = await queryLogin.getMe();
me = res.code === 200 ? res.data : undefined;
}
if (me) {
set({ me: me });
set({ isAdmin: me.orgs?.includes?.('admin') || false });
} else {
set({ me: undefined, isAdmin: false });
}

View File

@@ -0,0 +1,95 @@
import { useRepoStore } from "../store";
import { useShallow } from "zustand/shallow";
import { toast } from "sonner";
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Star, GitFork, FileText, ExternalLink, Calendar, User, Copy } from "lucide-react";
export const RepoInfoCard = () => {
const repoStore = useRepoStore(useShallow((state) => ({
getItem: state.getItem,
editRepo: state.editRepo,
})));
const repo = repoStore.editRepo!;
const onClone = () => {
const url = `git clone https://cnb.cool/${repo.path}`
navigator.clipboard.writeText(url).then(() => {
toast.success('克隆地址已复制到剪贴板')
}).catch(() => {
toast.error('复制失败')
})
}
if (!repo) {
return <div>Loading...</div>
}
return (
<div className="space-y-6">
{/* 顶部仓库信息卡片 */}
<Card className="p-6 border border-neutral-200 bg-white">
<div className="space-y-4">
{/* 标题行 */}
<div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-3 flex-1 min-w-0">
<span className="text-sm text-neutral-500 font-mono">
{repo.path}
</span>
<button
onClick={onClone}
className="flex items-center gap-1 text-xs text-neutral-500 hover:text-neutral-900 transition-colors"
>
<Copy className="w-3.5 h-3.5" />
</button>
<Badge variant="outline" className="shrink-0">
{repo.visibility_level === 'Public' ? '公开' : repo.visibility_level === 'Private' ? '私有' : repo.visibility_level}
</Badge>
</div>
<a
href={repo.web_url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-sm text-neutral-600 hover:text-neutral-900 transition-colors shrink-0"
>
<ExternalLink className="w-4 h-4" />
CNB
</a>
</div>
{/* 描述 */}
{repo.description && (
<p className="text-sm text-neutral-600 max-h-[4.5em] overflow-hidden truncate">
{repo.description}
</p>
)}
{/* 主题标签 */}
{repo.topics && (
<div className="flex flex-wrap gap-2">
{repo.topics.split(',').map((topic: string, idx: number) => (
<Badge key={idx} variant="outline" className="text-xs border-neutral-300 text-neutral-700">
{topic.trim()}
</Badge>
))}
</div>
)}
{/* 语言和更新时间 */}
<div className="flex items-center gap-6 text-xs text-neutral-500">
{repo.last_update_nickname && (
<span className="flex items-center gap-1">
<User className="w-3.5 h-3.5" />
{repo.last_update_nickname}
</span>
)}
{repo.last_updated_at && (
<span className="flex items-center gap-1">
<Calendar className="w-3.5 h-3.5" />
{new Date(repo.last_updated_at).toLocaleDateString('zh-CN')}
</span>
)}
</div>
</div>
</Card>
</div>
)
}

View File

@@ -237,7 +237,6 @@ export function WorkspaceDetailDialog() {
getUrl: (data) => data.codebuddycn
},
].sort((a, b) => (a.order || 0) - (b.order || 0))
console.log('workspaceLink', selectWorkspace)
return (
<Dialog open={showWorkspaceDialog} onOpenChange={setShowWorkspaceDialog}>
<DialogContent className="max-w-md! bg-white">

View File

@@ -0,0 +1,32 @@
import { useSearch } from "@tanstack/react-router";
import { useRepoStore } from "../store";
import { useEffect } from "react";
import { useShallow } from "zustand/shallow";
import { RepoInfoCard } from "../components/RepoInfoCard";
export const App = () => {
const params = useSearch({ strict: false }) as { repo?: string };
const repoStore = useRepoStore(useShallow((state) => ({
getItem: state.getItem,
editRepo: state.editRepo,
})));
useEffect(() => {
if (params.repo) {
repoStore.getItem(params.repo);
} else {
console.log('no repo param')
}
}, [params])
if (!repoStore.editRepo) {
return <div>Loading...</div>
}
return (
<div className="p-2">
<div className="px-4">
<RepoInfoCard />
</div>
</div>
)
}
export default App;

View File

@@ -88,6 +88,7 @@ type State = {
setSelectedSyncRepo: (repo: Data | null) => void;
buildSync: (data: Partial<Data>, params: { toRepo?: string, fromRepo?: string }) => Promise<any>;
buildUpdate: (data: Partial<Data>, params?: any) => Promise<any>;
getItem: (repo: string) => Promise<any>;
}
export const useRepoStore = create<State>((set, get) => {
@@ -113,17 +114,14 @@ export const useRepoStore = create<State>((set, get) => {
setSyncDialogOpen: (open) => set({ syncDialogOpen: open }),
selectedSyncRepo: null,
setSelectedSyncRepo: (repo) => set({ selectedSyncRepo: repo }),
getItem: async (id) => {
getItem: async (repo: string) => {
const { setLoading } = get();
setLoading(true);
try {
const res = await query.post({
path: 'demo',
key: 'item',
data: { id }
})
const res = await cnb.repo.getRepo(repo)
if (res.code === 200) {
return res;
const data = res.data!;
set({ editRepo: data })
} else {
toast.error(res.message || '请求失败');
}
@@ -376,6 +374,7 @@ export const useRepoStore = create<State>((set, get) => {
toast.error(res.message || '更新失败')
}
}
}
})

View File

@@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as LoginRouteImport } from './routes/login'
import { Route as IndexRouteImport } from './routes/index'
import { Route as RepoIndexRouteImport } from './routes/repo/index'
import { Route as ConfigIndexRouteImport } from './routes/config/index'
import { Route as ConfigGiteaRouteImport } from './routes/config/gitea'
@@ -24,6 +25,11 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const RepoIndexRoute = RepoIndexRouteImport.update({
id: '/repo/',
path: '/repo/',
getParentRoute: () => rootRouteImport,
} as any)
const ConfigIndexRoute = ConfigIndexRouteImport.update({
id: '/config/',
path: '/config/',
@@ -40,12 +46,14 @@ export interface FileRoutesByFullPath {
'/login': typeof LoginRoute
'/config/gitea': typeof ConfigGiteaRoute
'/config/': typeof ConfigIndexRoute
'/repo/': typeof RepoIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/login': typeof LoginRoute
'/config/gitea': typeof ConfigGiteaRoute
'/config': typeof ConfigIndexRoute
'/repo': typeof RepoIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
@@ -53,13 +61,14 @@ export interface FileRoutesById {
'/login': typeof LoginRoute
'/config/gitea': typeof ConfigGiteaRoute
'/config/': typeof ConfigIndexRoute
'/repo/': typeof RepoIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/login' | '/config/gitea' | '/config/'
fullPaths: '/' | '/login' | '/config/gitea' | '/config/' | '/repo/'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/login' | '/config/gitea' | '/config'
id: '__root__' | '/' | '/login' | '/config/gitea' | '/config/'
to: '/' | '/login' | '/config/gitea' | '/config' | '/repo'
id: '__root__' | '/' | '/login' | '/config/gitea' | '/config/' | '/repo/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
@@ -67,6 +76,7 @@ export interface RootRouteChildren {
LoginRoute: typeof LoginRoute
ConfigGiteaRoute: typeof ConfigGiteaRoute
ConfigIndexRoute: typeof ConfigIndexRoute
RepoIndexRoute: typeof RepoIndexRoute
}
declare module '@tanstack/react-router' {
@@ -85,6 +95,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/repo/': {
id: '/repo/'
path: '/repo'
fullPath: '/repo/'
preLoaderRoute: typeof RepoIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/config/': {
id: '/config/'
path: '/config'
@@ -107,6 +124,7 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRoute,
ConfigGiteaRoute: ConfigGiteaRoute,
ConfigIndexRoute: ConfigIndexRoute,
RepoIndexRoute: RepoIndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)

View File

@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router'
import App from '@/pages/repos/repo/page'
export const Route = createFileRoute('/repo/')({
component: RouteComponent,
})
function RouteComponent() {
return <App />
}