generated from kevisual/vite-react-template
feat: 添加仓库信息卡片和仓库页面,优化仓库路由
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
|
||||
95
src/pages/repos/components/RepoInfoCard.tsx
Normal file
95
src/pages/repos/components/RepoInfoCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
32
src/pages/repos/repo/page.tsx
Normal file
32
src/pages/repos/repo/page.tsx
Normal 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;
|
||||
@@ -21,7 +21,7 @@ interface Data {
|
||||
freeze: boolean;
|
||||
status: number;
|
||||
// Public, Private
|
||||
visibility_level: string;
|
||||
visibility_level: string;
|
||||
flags: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -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 || '更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user