P1 Backend (8 modules, 50+ new API endpoints): - P1-1: Stakeholder management (power-interest matrix) - P1-2: WBS task decomposition (tree structure + AI split) - P1-3: Risk management (register + matrix + AI identify) - P1-4: Requirement pool (MoSCoW + coverage + conflict detect) - P1-5: Change management (submit → evaluate → approve → implement) - P1-6: Health report (6 dimensions + red/yellow/green) - P1-7: Retrospective & knowledge base - P1-8: Multi-model support (6 models + strategy selection) Frontend (9 pages with sidebar navigation): - Project wizard, Kanban, Stakeholders, WBS, Risks, Requirements, Changes, Health, Retrospective Feishu integration: - WebSocket long-connection for receiving messages - Card button callback with decision tracking - 11 chat commands for P0+P1 features Tech: TypeScript, React 18, Arco Design, Hono, @larksuiteoapi/node-sdk
95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Table, Button, Modal, Form, Input, Select, Tag, Space, Message, Typography } from '@arco-design/web-react';
|
|
|
|
const { TextArea } = Input;
|
|
const { Title } = Typography;
|
|
|
|
interface Requirement {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
priority: 'must' | 'should' | 'could' | 'wont';
|
|
category: string;
|
|
status: string;
|
|
source?: string;
|
|
}
|
|
|
|
const PRIORITY_COLOR: Record<string, string> = { must: 'red', should: 'orange', could: 'blue', wont: 'grey' };
|
|
const PRIORITY_LABEL: Record<string, string> = { must: 'Must', should: 'Should', could: 'Could', wont: "Won't" };
|
|
|
|
export default function RequirementPage() {
|
|
const [projectId] = useState('pmp-demo');
|
|
const [data, setData] = useState<Requirement[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
const [form] = Form.useForm();
|
|
|
|
const api = (path: string, opts?: RequestInit) => fetch(`/api/projects/${projectId}${path}`, opts);
|
|
|
|
const fetchData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await api('/requirements');
|
|
if (res.ok) { const d = await res.json(); setData(d?.requirements || (Array.isArray(d) ? d : [])) };
|
|
} catch { Message.error('获取需求失败'); }
|
|
setLoading(false);
|
|
};
|
|
|
|
useEffect(() => { fetchData(); }, []);
|
|
|
|
const handleAdd = async () => {
|
|
const values = form.getFieldsValue();
|
|
try {
|
|
const res = await api('/requirements', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values) });
|
|
if (res.ok) { Message.success('添加成功'); setModalVisible(false); form.resetFields(); fetchData(); }
|
|
else Message.error('添加失败');
|
|
} catch { Message.error('添加失败'); }
|
|
};
|
|
|
|
const handleCoverage = async () => {
|
|
try {
|
|
const res = await api('/requirements/coverage');
|
|
if (res.ok) { const d = await res.json(); Modal.info({ title: '需求覆盖率', content: JSON.stringify(d, null, 2) }); }
|
|
} catch { Message.error('获取覆盖率失败'); }
|
|
};
|
|
|
|
const handleConflict = async () => {
|
|
try {
|
|
const res = await api('/requirements/detect-conflicts', { method: 'POST' });
|
|
if (res.ok) { const d = await res.json(); Modal.info({ title: '冲突检测结果', content: JSON.stringify(d, null, 2) }); }
|
|
} catch { Message.error('冲突检测失败'); }
|
|
};
|
|
|
|
const columns = [
|
|
{ title: '标题', dataIndex: 'title' },
|
|
{ title: '优先级', dataIndex: 'priority', render: (v: string) => <Tag color={PRIORITY_COLOR[v]}>{PRIORITY_LABEL[v]}</Tag> },
|
|
{ title: '分类', dataIndex: 'category' },
|
|
{ title: '状态', dataIndex: 'status' },
|
|
{ title: '来源', dataIndex: 'source' },
|
|
];
|
|
|
|
return (
|
|
<div style={{ padding: 20 }}>
|
|
<Title heading={4}>需求池管理</Title>
|
|
<Space style={{ marginBottom: 16 }}>
|
|
<Button type="primary" onClick={() => setModalVisible(true)}>添加需求</Button>
|
|
<Button onClick={handleCoverage}>覆盖率</Button>
|
|
<Button onClick={handleConflict}>冲突检测</Button>
|
|
</Space>
|
|
<Table rowKey="id" columns={columns} data={data} loading={loading} />
|
|
<Modal title="添加需求" visible={modalVisible} onOk={handleAdd} onCancel={() => setModalVisible(false)}>
|
|
<Form form={form} layout="vertical">
|
|
<Form.Item label="标题" field="title" rules={[{ required: true }]}><Input /></Form.Item>
|
|
<Form.Item label="描述" field="description"><TextArea /></Form.Item>
|
|
<Form.Item label="优先级" field="priority" rules={[{ required: true }]}>
|
|
<Select options={['must', 'should', 'could', 'wont'].map(v => ({ label: PRIORITY_LABEL[v], value: v }))} />
|
|
</Form.Item>
|
|
<Form.Item label="分类" field="category">
|
|
<Select options={['功能', '性能', '安全', '体验', '其他'].map(v => ({ label: v, value: v }))} />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
}
|