feat: project list

This commit is contained in:
2025-09-06 01:44:33 +08:00
parent ef473d6084
commit 9b54d18ef3
11 changed files with 333 additions and 56 deletions

View File

@@ -0,0 +1,10 @@
import React, { useEffect } from 'react';
export function useAsyncEffect(
effect: () => Promise<void>,
deps: React.DependencyList,
) {
useEffect(() => {
effect();
}, [...deps]);
}

View File

@@ -1,12 +1,9 @@
import { Avatar, Layout, Menu } from '@arco-design/web-react';
import {
IconApps,
IconBulb,
IconFire,
IconMenuFold,
IconMenuUnfold,
IconRobot,
IconSafe,
IconUser,
} from '@arco-design/web-react/icon';
import { useState } from 'react';
@@ -21,9 +18,15 @@ function Home() {
<Layout.Sider
collapsible
onCollapse={setCollapsed}
trigger={collapsed ? <IconMenuUnfold /> : <IconMenuFold />}
trigger={
collapsed ? (
<IconMenuUnfold fontSize={16} />
) : (
<IconMenuFold fontSize={16} />
)
}
>
<div className="flex flex-row items-center justify-center px-2 py-3">
<div className="flex flex-row items-center justify-center h-[56px]">
<Logo />
{!collapsed && <h2 className="ml-4 text-xl font-medium">Foka CI</h2>}
</div>
@@ -31,16 +34,17 @@ function Home() {
className="flex-1"
defaultOpenKeys={['0']}
defaultSelectedKeys={['0_1']}
collapse={collapsed}
>
<Menu.Item key="0">
<Link to="/project" className="flex flex-row items-center">
<IconApps fontSize={18} />
<Link to="/project">
<IconApps fontSize={16} />
<span></span>
</Link>
</Menu.Item>
<Menu.Item key="1">
<Link to="/env" className="flex flex-row items-center">
<IconRobot fontSize={18} />
<Link to="/env">
<IconRobot fontSize={16} />
</Link>
</Menu.Item>

View File

@@ -1,8 +1,114 @@
import { Card, Grid, Link, Tag, Avatar, Space, Typography, Button } from '@arco-design/web-react';
import { IconBranch, IconCalendar, IconEye } from '@arco-design/web-react/icon';
import { useState } from 'react';
import type { Project } from './types';
import { useAsyncEffect } from '../../hooks/useAsyncEffect';
import { projectService } from './service';
import IconGitea from '@assets/images/gitea.svg?react'
function Project() {
const [projects, setProjects] = useState([]);
return <div>project page</div>;
const { Text, Paragraph } = Typography;
function ProjectPage() {
const [projects, setProjects] = useState<Project[]>([]);
useAsyncEffect(async () => {
const list = await projectService.list();
setProjects(list);
}, []);
return (
<div className="p-6 bg-gray-100 min-h-screen">
<div className="mb-6">
<Typography.Title heading={2} className="!m-0 !text-gray-900">
</Typography.Title>
<Text type="secondary"></Text>
</div>
<Grid.Row gutter={[16, 16]}>
{projects.map((project) => (
<Grid.Col key={project.id} span={8}>
<Card
className="foka-card !rounded-xl border border-gray-200 h-[280px] hover:border-blue-200"
hoverable
bodyStyle={{ padding: '20px' }}
>
{/* 项目头部 */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<Avatar
size={40}
className="bg-blue-600 text-white text-base font-semibold"
>
{project.name.charAt(0).toUpperCase()}
</Avatar>
<div className="ml-3">
<Typography.Title
heading={5}
className="!m-0 !text-base !font-semibold"
>
{project.name}
</Typography.Title>
<Text type="secondary" className="text-xs">
2
</Text>
</div>
</div>
<Tag color="blue" size="small">
</Tag>
</div>
{/* 项目描述 */}
<Paragraph
className="!m-0 !mb-4 !text-gray-600 !text-sm !leading-6 h-[42px] overflow-hidden line-clamp-2"
>
{project.description || '暂无描述'}
</Paragraph>
{/* 项目信息 */}
<div className="mb-4">
<div className="mb-2 flex items-center">
<IconGitea className="mr-1.5 w-4" />
<Text
type="secondary"
className="text-xs"
>
{project.repository}
</Text>
</div>
<Space size={16}>
<div className="flex items-center">
<IconBranch className="mr-1 text-gray-500 text-xs" />
<Text className="text-xs text-gray-500">main</Text>
</div>
<div className="flex items-center">
<IconCalendar className="mr-1 text-gray-500 text-xs" />
<Text className="text-xs text-gray-500">3</Text>
</div>
</Space>
</div>
{/* 操作按钮 */}
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
<Button
type="text"
size="small"
icon={<IconEye />}
className="text-gray-500"
>
</Button>
<Link className="text-xs font-medium">
</Link>
</div>
</Card>
</Grid.Col>
))}
</Grid.Row>
</div>
);
}
export default Project;
export default ProjectPage;

View File

@@ -0,0 +1,16 @@
import { net, type APIResponse } from "@shared";
import type { Project } from "./types";
class ProjectService {
async list() {
const { data } = await net.request<APIResponse<Project[]>>({
method: 'GET',
url: '/api/project/list',
})
return data;
}
}
export const projectService = new ProjectService();

View File

@@ -8,7 +8,7 @@ export interface Project {
id: string;
name: string;
description: string;
git: string;
repository: string;
env: Record<string, string>;
createdAt: string;
status: BuildStatus;