generated from kevisual/vite-react-template
feat: 添加返回按钮和优化仓库卡片,移除未使用的 RepoInfoCard 组件
This commit is contained in:
@@ -1,13 +1,12 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRepoStore } from "../store";
|
import { useRepoStore } from "../store";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { toast } from "sonner";
|
|
||||||
import CodeMirror from "@uiw/react-codemirror";
|
import CodeMirror from "@uiw/react-codemirror";
|
||||||
import { yaml } from "@codemirror/lang-yaml";
|
import { yaml } from "@codemirror/lang-yaml";
|
||||||
import { useLayoutStore } from "@/pages/auth/store";
|
import { useLayoutStore } from "@/pages/auth/store";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { ArrowLeft, Workflow } from "lucide-react";
|
||||||
import { Workflow } from "lucide-react";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const BuildConfig = () => {
|
export const BuildConfig = () => {
|
||||||
const repoStore = useRepoStore(useShallow((state) => ({
|
const repoStore = useRepoStore(useShallow((state) => ({
|
||||||
@@ -21,6 +20,7 @@ export const BuildConfig = () => {
|
|||||||
buildWorkspace: state.buildWorkspace,
|
buildWorkspace: state.buildWorkspace,
|
||||||
})));
|
})));
|
||||||
const repo = repoStore.editRepo!;
|
const repo = repoStore.editRepo!;
|
||||||
|
const navigate = useNavigate();
|
||||||
const me = useLayoutStore((state) => state.me);
|
const me = useLayoutStore((state) => state.me);
|
||||||
const [localConfig, setLocalConfig] = useState(repoStore.buildConfig?.config || "");
|
const [localConfig, setLocalConfig] = useState(repoStore.buildConfig?.config || "");
|
||||||
|
|
||||||
@@ -59,6 +59,12 @@ export const BuildConfig = () => {
|
|||||||
{/* 左侧边栏 - 配置信息 */}
|
{/* 左侧边栏 - 配置信息 */}
|
||||||
<div className="w-64 shrink-0 space-y-4">
|
<div className="w-64 shrink-0 space-y-4">
|
||||||
<div className="text-xl font-bold border-b pb-2 mb-4 flex">
|
<div className="text-xl font-bold border-b pb-2 mb-4 flex">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate({ to: '/' })}
|
||||||
|
className="cursor-pointer flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 transition-colors"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 text-neutral-600" />
|
||||||
|
</button>
|
||||||
<span className="text-lg font-semibold">构建配置</span>
|
<span className="text-lg font-semibold">构建配置</span>
|
||||||
<button
|
<button
|
||||||
onClick={repoStore.buildWorkspace}
|
onClick={repoStore.buildWorkspace}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover'
|
} from '@/components/ui/popover'
|
||||||
import { Star, GitFork, FileText, Edit, FolderGit2, MoreVertical, FileText as IssueIcon, Settings, Play, Trash2, RefreshCw, BookOpen, Copy, Clock, Info, Eye, Square, LinkIcon, ExternalLink } from 'lucide-react'
|
import { Star, GitFork, FileText, Edit, FolderGit2, MoreVertical, FileText as IssueIcon, Settings, Play, Trash2, RefreshCw, BookOpen, Copy, Clock, Info, Eye, Square, LinkIcon, ExternalLink, ArrowLeft } from 'lucide-react'
|
||||||
import { useRepoStore } from '../store'
|
import { useRepoStore } from '../store'
|
||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useShallow } from 'zustand/shallow'
|
import { useShallow } from 'zustand/shallow'
|
||||||
@@ -24,26 +24,27 @@ import { useNavigate } from '@tanstack/react-router'
|
|||||||
|
|
||||||
interface RepoCardProps {
|
interface RepoCardProps {
|
||||||
repo: any
|
repo: any
|
||||||
onStartWorkspace: (repo: any) => void
|
showReturn?: boolean
|
||||||
onEdit: (repo: any) => void
|
|
||||||
onIssue: (repo: any) => void
|
|
||||||
onSettings: (repo: any) => void
|
|
||||||
onDelete: (repo: any) => void
|
|
||||||
onSync?: (repo: any) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings, onDelete, onSync }: RepoCardProps) {
|
export function RepoCard({ showReturn = false, repo }: RepoCardProps) {
|
||||||
const [deletePopoverOpen, setDeletePopoverOpen] = useState(false)
|
const [deletePopoverOpen, setDeletePopoverOpen] = useState(false)
|
||||||
const { workspaceList, getWorkspaceDetail, getList, buildUpdate, stopWorkspace } = useRepoStore(useShallow((state) => ({
|
const store = useRepoStore(useShallow((state) => ({
|
||||||
workspaceList: state.workspaceList,
|
workspaceList: state.workspaceList,
|
||||||
getWorkspaceDetail: state.getWorkspaceDetail,
|
getWorkspaceDetail: state.getWorkspaceDetail,
|
||||||
getList: state.getList,
|
getList: state.getList,
|
||||||
buildUpdate: state.buildUpdate,
|
buildUpdate: state.buildUpdate,
|
||||||
stopWorkspace: state.stopWorkspace,
|
stopWorkspace: state.stopWorkspace,
|
||||||
|
setSelectedSyncRepo: state.setSelectedSyncRepo,
|
||||||
|
setSyncDialogOpen: state.setSyncDialogOpen,
|
||||||
|
startWorkspace: state.startWorkspace,
|
||||||
|
setEditRepo: state.setEditRepo,
|
||||||
|
setShowEditDialog: state.setShowEditDialog,
|
||||||
|
deleteItem: state.deleteItem,
|
||||||
})));
|
})));
|
||||||
const workspace = useMemo(() => {
|
const workspace = useMemo(() => {
|
||||||
return workspaceList.find(ws => ws.slug === repo.path)
|
return store.workspaceList.find(ws => ws.slug === repo.path)
|
||||||
}, [workspaceList, repo.path])
|
}, [store.workspaceList, repo.path])
|
||||||
const isWorkspaceActive = !!workspace
|
const isWorkspaceActive = !!workspace
|
||||||
const owner = repo.path.split('/')[0]
|
const owner = repo.path.split('/')[0]
|
||||||
const isMine = myOrgs.includes(owner)
|
const isMine = myOrgs.includes(owner)
|
||||||
@@ -53,7 +54,7 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
const res = await app.run({ path: 'cnb', key: 'build-knowledge-base', payload: { repo: repo.path } })
|
const res = await app.run({ path: 'cnb', key: 'build-knowledge-base', payload: { repo: repo.path } })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
toast.success("知识库创建中")
|
toast.success("知识库创建中")
|
||||||
getList({}, true)
|
store.getList({}, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onClone = async () => {
|
const onClone = async () => {
|
||||||
@@ -65,18 +66,38 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const onUpdate = async () => {
|
const onUpdate = async () => {
|
||||||
await buildUpdate({ path: repo.path });
|
await store.buildUpdate({ path: repo.path });
|
||||||
}
|
}
|
||||||
|
const handleIssue = (repo: any) => {
|
||||||
|
window.open(`https://cnb.cool/${repo.path}/-/issues`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSettings = (repo: any) => {
|
||||||
|
window.open(`https://cnb.cool/${repo.path}/-/settings`)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="relative p-0 overflow-hidden border border-neutral-200 bg-white hover:shadow-xl hover:border-neutral-300 transition-all duration-300 group pb-14">
|
<Card className="relative p-0 overflow-hidden border border-neutral-200 bg-white hover:shadow-xl hover:border-neutral-300 transition-all duration-300 group pb-14">
|
||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
|
{showReturn && (
|
||||||
|
<button
|
||||||
|
onClick={() => navigate({ to: '/' })}
|
||||||
|
className="cursor-pointer flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 transition-colors"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 text-neutral-600" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className="text-lg font-bold text-neutral-900 hover:text-neutral-600 transition-colors line-clamp-1 group-hover:underline"
|
className="text-lg font-bold text-neutral-900 hover:text-neutral-600 transition-colors line-clamp-1 group-hover:underline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (!showReturn) {
|
||||||
navigate({ to: `/repo?repo=${repo.path}` })
|
navigate({ to: `/repo?repo=${repo.path}` })
|
||||||
|
} else {
|
||||||
|
window.open(`https://cnb.cool/${repo.path}`, '_blank')
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{repo.path}
|
{repo.path}
|
||||||
@@ -114,7 +135,7 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
stopWorkspace(workspace)
|
store.stopWorkspace(workspace)
|
||||||
}}
|
}}
|
||||||
className="h-8 w-8 p-0 border-neutral-200 hover:border-red-600 hover:bg-red-600 hover:text-white transition-all cursor-pointer"
|
className="h-8 w-8 p-0 border-neutral-200 hover:border-red-600 hover:bg-red-600 hover:text-white transition-all cursor-pointer"
|
||||||
>
|
>
|
||||||
@@ -137,9 +158,9 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isWorkspaceActive) {
|
if (!isWorkspaceActive) {
|
||||||
onStartWorkspace(repo)
|
store.startWorkspace(repo)
|
||||||
} else {
|
} else {
|
||||||
getWorkspaceDetail(workspace)
|
store.getWorkspaceDetail(workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
@@ -167,7 +188,10 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<DropdownMenuContent align="end" className="w-40">
|
<DropdownMenuContent align="end" className="w-40">
|
||||||
<DropdownMenuItem onClick={() => onEdit(repo)} className="cursor-pointer">
|
<DropdownMenuItem onClick={() => {
|
||||||
|
store.setEditRepo(repo)
|
||||||
|
store.setShowEditDialog(true)
|
||||||
|
}} className="cursor-pointer">
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
编辑
|
编辑
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -202,11 +226,11 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
<ExternalLink className="w-4 h-4 mr-2" />
|
<ExternalLink className="w-4 h-4 mr-2" />
|
||||||
访问仓库
|
访问仓库
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => onIssue(repo)} className="cursor-pointer">
|
<DropdownMenuItem onClick={() => handleIssue(repo)} className="cursor-pointer">
|
||||||
<IssueIcon className="w-4 h-4 mr-2" />
|
<IssueIcon className="w-4 h-4 mr-2" />
|
||||||
访问问题
|
访问问题
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => onSettings(repo)} className="cursor-pointer">
|
<DropdownMenuItem onClick={() => handleSettings(repo)} className="cursor-pointer">
|
||||||
<Settings className="w-4 h-4 mr-2" />
|
<Settings className="w-4 h-4 mr-2" />
|
||||||
访问设置
|
访问设置
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -247,7 +271,8 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
className="bg-red-600 text-white border-red-600 hover:bg-red-700 hover:border-red-700"
|
className="bg-red-600 text-white border-red-600 hover:bg-red-700 hover:border-red-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onDelete(repo)
|
if (repo.path)
|
||||||
|
store.deleteItem(repo.path)
|
||||||
setDeletePopoverOpen(false)
|
setDeletePopoverOpen(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -309,7 +334,7 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
</span>
|
</span>
|
||||||
{isWorkspaceActive && <span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
{isWorkspaceActive && <span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
getWorkspaceDetail(workspace)
|
store.getWorkspaceDetail(workspace)
|
||||||
}}>
|
}}>
|
||||||
<Play className="w-3.5 h-3.5" />
|
<Play className="w-3.5 h-3.5" />
|
||||||
<span className="font-medium">运行中</span>
|
<span className="font-medium">运行中</span>
|
||||||
@@ -317,7 +342,10 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
|||||||
{isMine && (
|
{isMine && (
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
||||||
onClick={() => onSync?.(repo)}
|
onClick={() => {
|
||||||
|
store.setSelectedSyncRepo(repo)
|
||||||
|
store.setSyncDialogOpen(true)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-3.5 h-3.5" />
|
<RefreshCw className="w-3.5 h-3.5" />
|
||||||
<span className="font-medium">同步</span>
|
<span className="font-medium">同步</span>
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
import { useNavigate } from "@tanstack/react-router";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
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 { Button } from "@/components/ui/button";
|
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
||||||
import { Star, GitFork, FileText, ExternalLink, Calendar, User, Copy, ArrowLeft, Play, Square, Eye, BookOpen, RefreshCw } from "lucide-react";
|
|
||||||
import { myOrgs } from "../store/build";
|
|
||||||
|
|
||||||
export const RepoInfoCard = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { workspaceList, getWorkspaceDetail, stopWorkspace, editRepo, setSelectedSyncRepo, setSyncDialogOpen } = useRepoStore(useShallow((state) => ({
|
|
||||||
workspaceList: state.workspaceList,
|
|
||||||
getWorkspaceDetail: state.getWorkspaceDetail,
|
|
||||||
stopWorkspace: state.stopWorkspace,
|
|
||||||
editRepo: state.editRepo,
|
|
||||||
setSelectedSyncRepo: state.setSelectedSyncRepo,
|
|
||||||
setSyncDialogOpen: state.setSyncDialogOpen,
|
|
||||||
})));
|
|
||||||
const repo = editRepo!;
|
|
||||||
|
|
||||||
const workspace = useMemo(() => {
|
|
||||||
return workspaceList.find(ws => ws.slug === repo.path)
|
|
||||||
}, [workspaceList, repo.path])
|
|
||||||
const isWorkspaceActive = !!workspace
|
|
||||||
const owner = repo.path.split('/')[0]
|
|
||||||
const isMine = myOrgs.includes(owner)
|
|
||||||
const isKnowledge = repo?.flags === "KnowledgeBase"
|
|
||||||
|
|
||||||
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">
|
|
||||||
<button
|
|
||||||
onClick={() => navigate({ to: '/' })}
|
|
||||||
className="cursor-pointer flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 transition-colors"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-4 h-4 text-neutral-600" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
{isKnowledge && (
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger
|
|
||||||
render={
|
|
||||||
<div className="shrink-0">
|
|
||||||
<BookOpen className="w-5 h-5 text-neutral-700" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>知识库</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
)}
|
|
||||||
{isWorkspaceActive && (
|
|
||||||
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
|
||||||
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500"></span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
|
||||||
{isWorkspaceActive && (
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
stopWorkspace(workspace)
|
|
||||||
}}
|
|
||||||
className="h-8 w-8 p-0 border-neutral-200 hover:border-red-600 hover:bg-red-600 hover:text-white transition-all cursor-pointer"
|
|
||||||
>
|
|
||||||
<Square className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>停止工作区</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
)}
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
if (!isWorkspaceActive) {
|
|
||||||
// TODO: 启动工作区
|
|
||||||
} else {
|
|
||||||
getWorkspaceDetail(workspace)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="h-8 w-8 p-0 border-neutral-200 hover:border-neutral-900 hover:bg-neutral-900 hover:text-white transition-all cursor-pointer"
|
|
||||||
>
|
|
||||||
{isWorkspaceActive ? <Eye className="w-4 h-4" /> : <Play className="w-4 h-4" />}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{isWorkspaceActive ? '查看工作区' : '启动工作区'}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 描述 */}
|
|
||||||
{repo.description && (
|
|
||||||
<p className="text-sm text-neutral-600 h-12 overflow-hidden truncate">
|
|
||||||
{repo.description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 主题标签和知识库 */}
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{/* 主题标签 */}
|
|
||||||
{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>
|
|
||||||
|
|
||||||
{/* 统计信息 */}
|
|
||||||
<div className="flex items-center gap-6 text-xs text-neutral-500">
|
|
||||||
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
|
||||||
<Star className="w-3.5 h-3.5" />
|
|
||||||
<span className="font-medium">{repo.star_count}</span>
|
|
||||||
</span>
|
|
||||||
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
|
||||||
<GitFork className="w-3.5 h-3.5" />
|
|
||||||
<span className="font-medium">{repo.fork_count}</span>
|
|
||||||
</span>
|
|
||||||
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
|
||||||
<FileText className="w-3.5 h-3.5" />
|
|
||||||
<span className="font-medium">{repo.open_issue_count}</span>
|
|
||||||
</span>
|
|
||||||
{isWorkspaceActive && (
|
|
||||||
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer">
|
|
||||||
<Play className="w-3.5 h-3.5" />
|
|
||||||
<span className="font-medium">运行中</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{isMine && (
|
|
||||||
<span
|
|
||||||
className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedSyncRepo(repo)
|
|
||||||
setSyncDialogOpen(true)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RefreshCw className="w-3.5 h-3.5" />
|
|
||||||
<span className="font-medium">同步</span>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -13,21 +13,12 @@ import Fuse from 'fuse.js'
|
|||||||
import { useNavigate } from '@tanstack/react-router'
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const { list, refresh, loading, editRepo, setEditRepo, workspaceList, showEditDialog, setShowEditDialog, showCreateDialog, setShowCreateDialog, startWorkspace, deleteItem, setSelectedSyncRepo, setSyncDialogOpen } = useRepoStore(useShallow((state) => ({
|
const { list, refresh, loading, workspaceList, setShowCreateDialog } = useRepoStore(useShallow((state) => ({
|
||||||
list: state.list,
|
list: state.list,
|
||||||
refresh: state.refresh,
|
refresh: state.refresh,
|
||||||
loading: state.loading,
|
loading: state.loading,
|
||||||
editRepo: state.editRepo,
|
|
||||||
setEditRepo: state.setEditRepo,
|
|
||||||
workspaceList: state.workspaceList,
|
workspaceList: state.workspaceList,
|
||||||
showEditDialog: state.showEditDialog,
|
|
||||||
setShowEditDialog: state.setShowEditDialog,
|
|
||||||
showCreateDialog: state.showCreateDialog,
|
|
||||||
setShowCreateDialog: state.setShowCreateDialog,
|
setShowCreateDialog: state.setShowCreateDialog,
|
||||||
startWorkspace: state.startWorkspace,
|
|
||||||
deleteItem: state.deleteItem,
|
|
||||||
setSelectedSyncRepo: state.setSelectedSyncRepo,
|
|
||||||
setSyncDialogOpen: state.setSyncDialogOpen,
|
|
||||||
})))
|
})))
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -35,28 +26,6 @@ export const App = () => {
|
|||||||
refresh({ showTips: false })
|
refresh({ showTips: false })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleEdit = (repo: any) => {
|
|
||||||
setEditRepo(repo)
|
|
||||||
setShowEditDialog(true)
|
|
||||||
}
|
|
||||||
const handleIssue = (repo: any) => {
|
|
||||||
window.open(`https://cnb.cool/${repo.path}/-/issues`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSettings = (repo: any) => {
|
|
||||||
window.open(`https://cnb.cool/${repo.path}/-/settings`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = (repo: any) => {
|
|
||||||
if (repo.path)
|
|
||||||
deleteItem(repo.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSync = (repo: any) => {
|
|
||||||
setSelectedSyncRepo(repo)
|
|
||||||
setSyncDialogOpen(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const appList = useMemo(() => {
|
const appList = useMemo(() => {
|
||||||
// 首先按活动状态排序
|
// 首先按活动状态排序
|
||||||
const sortedList = [...list].sort((a, b) => {
|
const sortedList = [...list].sort((a, b) => {
|
||||||
@@ -133,12 +102,6 @@ export const App = () => {
|
|||||||
<RepoCard
|
<RepoCard
|
||||||
key={repo.id}
|
key={repo.id}
|
||||||
repo={repo}
|
repo={repo}
|
||||||
onStartWorkspace={startWorkspace}
|
|
||||||
onEdit={handleEdit}
|
|
||||||
onIssue={handleIssue}
|
|
||||||
onSettings={handleSettings}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
onSync={handleSync}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import { useSearch } from "@tanstack/react-router";
|
|||||||
import { useRepoStore } from "../store";
|
import { useRepoStore } from "../store";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { RepoInfoCard } from "../components/RepoInfoCard";
|
|
||||||
import BuildConfig from "../components/BuildConfig";
|
import BuildConfig from "../components/BuildConfig";
|
||||||
import { CommonRepoDialog } from "../page";
|
import { CommonRepoDialog } from "../page";
|
||||||
|
import { RepoCard } from "../components/RepoCard";
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const params = useSearch({ strict: false }) as { repo?: string };
|
const params = useSearch({ strict: false }) as { repo?: string, tab?: string };
|
||||||
const repoStore = useRepoStore(useShallow((state) => ({
|
const repoStore = useRepoStore(useShallow((state) => ({
|
||||||
getItem: state.getItem,
|
getItem: state.getItem,
|
||||||
editRepo: state.editRepo,
|
editRepo: state.editRepo,
|
||||||
refresH: state.refresh,
|
refresH: state.refresh,
|
||||||
})));
|
})));
|
||||||
const [activeTab, setActiveTab] = useState("build");
|
const [activeTab, setActiveTab] = useState(params.tab || "build");
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: "build", label: "构建配置" },
|
{ key: "build", label: "构建配置" },
|
||||||
{ key: "info", label: "基本信息" },
|
{ key: "info", label: "基本信息" },
|
||||||
@@ -31,16 +31,16 @@ export const App = () => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="p-2 flex-col flex gap-2 h-full">
|
<div className="p-2 flex-col flex gap-2 h-full">
|
||||||
<div className="px-4">
|
<div className="px-4 h-full scrollbar flex-col flex gap-4 overflow-hidden">
|
||||||
<RepoInfoCard />
|
|
||||||
</div>
|
|
||||||
<div className="px-4 h-[calc(100%-200px)] scrollbar flex-col flex gap-4 overflow-hidden">
|
|
||||||
<div className="flex border-b mb-4">
|
<div className="flex border-b mb-4">
|
||||||
{tabs.map(tab => (
|
{tabs.map(tab => (
|
||||||
<div
|
<div
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
className={`px-4 py-2 cursor-pointer ${activeTab === tab.key ? 'border-b-2 border-gray-500' : ''}`}
|
className={`px-4 py-2 cursor-pointer ${activeTab === tab.key ? 'border-b-2 border-gray-500' : ''}`}
|
||||||
onClick={() => setActiveTab(tab.key)}
|
onClick={() => {
|
||||||
|
setActiveTab(tab.key)
|
||||||
|
history.replaceState(null, '', `?repo=${params.repo}&tab=${tab.key}`)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</div>
|
</div>
|
||||||
@@ -48,9 +48,12 @@ export const App = () => {
|
|||||||
</div>
|
</div>
|
||||||
{activeTab === 'build' && <BuildConfig />}
|
{activeTab === 'build' && <BuildConfig />}
|
||||||
{activeTab === 'info' && (
|
{activeTab === 'info' && (
|
||||||
|
<div className="flex flex-col gap-4 h-full">
|
||||||
|
<RepoCard repo={repoStore.editRepo} showReturn />
|
||||||
<div className="p-4 border rounded bg-white h-full overflow-auto scrollbar">
|
<div className="p-4 border rounded bg-white h-full overflow-auto scrollbar">
|
||||||
<pre className="whitespace-pre-wrap break-all">{JSON.stringify(repoStore.editRepo, null, 2)}</pre>
|
<pre className="whitespace-pre-wrap break-all">{JSON.stringify(repoStore.editRepo, null, 2)}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<CommonRepoDialog />
|
<CommonRepoDialog />
|
||||||
|
|||||||
Reference in New Issue
Block a user