feat: AI Worker 平台 MVP v1.0.0 - 多租户/项目管理/AI Worker/任务编排/HITL审核/成本治理

This commit is contained in:
2026-08-12 13:31:19 +08:00
parent e7374b977e
commit fcca0e6325
+261
View File
@@ -0,0 +1,261 @@
import { useEffect, useState, useCallback } from 'react';
import {
Table,
Button,
Card,
Space,
Modal,
Form,
Input,
Select,
InputNumber,
DatePicker,
Typography,
Popconfirm,
message,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
EyeOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { projectApi } from '@/api';
import type { Project, ProjectCreate, ProjectStatus } from '@/types';
import { ProjectStatusTag } from '@/components/constants';
import dayjs from 'dayjs';
const { Title } = Typography;
const { TextArea } = Input;
const { RangePicker } = DatePicker;
const projectStatuses: { value: ProjectStatus; label: string }[] = [
{ value: 'planning', label: '规划中' },
{ value: 'active', label: '进行中' },
{ value: 'paused', label: '已暂停' },
{ value: 'completed', label: '已完成' },
{ value: 'cancelled', label: '已取消' },
];
export default function Projects() {
const navigate = useNavigate();
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [editing, setEditing] = useState<Project | null>(null);
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
const loadData = useCallback(() => {
setLoading(true);
projectApi
.list()
.then(setProjects)
.catch(() => {})
.finally(() => setLoading(false));
}, []);
useEffect(() => {
loadData();
}, [loadData]);
const openCreate = () => {
setEditing(null);
form.resetFields();
form.setFieldsValue({ status: 'planning' });
setModalOpen(true);
};
const openEdit = (record: Project) => {
setEditing(record);
form.setFieldsValue({
name: record.name,
description: record.description,
status: record.status,
budget: record.budget,
});
setModalOpen(true);
};
const handleSave = async () => {
try {
const values = await form.validateFields();
setSaving(true);
const payload: ProjectCreate = {
name: values.name,
description: values.description,
status: values.status,
budget: values.budget,
};
if (values.date_range) {
payload.start_date = values.date_range[0].toISOString();
payload.end_date = values.date_range[1].toISOString();
}
if (editing) {
await projectApi.update(editing.id, payload);
message.success('项目更新成功');
} else {
await projectApi.create(payload);
message.success('项目创建成功');
}
setModalOpen(false);
loadData();
} catch (err) {
if (err instanceof Error && err.message.includes('validateFields')) return;
} finally {
setSaving(false);
}
};
const handleDelete = async (id: string) => {
try {
await projectApi.delete(id);
message.success('删除成功');
loadData();
} catch {
/* error handled by interceptor */
}
};
const columns = [
{
title: '项目名称',
dataIndex: 'name',
key: 'name',
render: (text: string, record: Project) => (
<a onClick={() => navigate(`/projects/${record.id}`)}>{text}</a>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: ProjectStatus) => <ProjectStatusTag status={status} />,
},
{
title: '预算',
dataIndex: 'budget',
key: 'budget',
render: (v: number) => (v != null ? `¥${v.toFixed(2)}` : '-'),
},
{
title: '创建时间',
dataIndex: 'created_at',
key: 'created_at',
render: (t: string) => dayjs(t).format('YYYY-MM-DD'),
},
{
title: '操作',
key: 'action',
width: 200,
render: (_: unknown, record: Project) => (
<Space>
<Button
type="link"
size="small"
icon={<EyeOutlined />}
onClick={() => navigate(`/projects/${record.id}`)}
>
</Button>
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => openEdit(record)}
>
</Button>
<Popconfirm
title="确定要删除此项目吗?"
onConfirm={() => handleDelete(record.id)}
okText="确定"
cancelText="取消"
>
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
</Button>
</Popconfirm>
</Space>
),
},
];
return (
<div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
}}
>
<Title level={4} style={{ margin: 0 }}>
</Title>
<Space>
<Button icon={<ReloadOutlined />} onClick={loadData}>
</Button>
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button>
</Space>
</div>
<Card>
<Table
dataSource={projects}
columns={columns}
rowKey="id"
loading={loading}
pagination={{ pageSize: 10 }}
/>
</Card>
<Modal
title={editing ? '编辑项目' : '新建项目'}
open={modalOpen}
onCancel={() => setModalOpen(false)}
onOk={handleSave}
confirmLoading={saving}
okText={editing ? '保存' : '创建'}
cancelText="取消"
width={560}
>
<Form form={form} layout="vertical">
<Form.Item
name="name"
label="项目名称"
rules={[{ required: true, message: '请输入项目名称' }]}
>
<Input placeholder="请输入项目名称" />
</Form.Item>
<Form.Item name="description" label="项目描述">
<TextArea rows={3} placeholder="请输入项目描述" />
</Form.Item>
<Form.Item name="status" label="项目状态">
<Select options={projectStatuses} />
</Form.Item>
<Form.Item name="date_range" label="起止时间">
<RangePicker style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="budget" label="预算 (元)">
<InputNumber
style={{ width: '100%' }}
min={0}
step={100}
placeholder="请输入预算金额"
/>
</Form.Item>
</Form>
</Modal>
</div>
);
}