fix: 修复步骤3白屏问题 - 将useState从render函数提取为独立子组件(StakeholderStep/MilestoneStep)
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Table,
|
Table,
|
||||||
Select,
|
Select,
|
||||||
DatePicker,
|
|
||||||
Tag,
|
Tag,
|
||||||
} from '@arco-design/web-react';
|
} from '@arco-design/web-react';
|
||||||
import '@arco-design/web-react/dist/css/arco.css';
|
import '@arco-design/web-react/dist/css/arco.css';
|
||||||
@@ -18,7 +17,6 @@ import '@arco-design/web-react/dist/css/arco.css';
|
|||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
// Step definitions
|
|
||||||
const STEPS = [
|
const STEPS = [
|
||||||
{ title: '基本信息', description: '项目名称和目标' },
|
{ title: '基本信息', description: '项目名称和目标' },
|
||||||
{ title: '章程信息', description: '范围与约束' },
|
{ title: '章程信息', description: '范围与约束' },
|
||||||
@@ -27,7 +25,6 @@ const STEPS = [
|
|||||||
{ title: '完成', description: '确认创建' },
|
{ title: '完成', description: '确认创建' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Types
|
|
||||||
interface Stakeholder {
|
interface Stakeholder {
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -66,6 +63,123 @@ const createEmptyProject = (): ProjectData => ({
|
|||||||
milestones: [],
|
milestones: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Extracted sub-components (hooks rules compliant) ---
|
||||||
|
|
||||||
|
const StakeholderStep: React.FC<{
|
||||||
|
stakeholders: Stakeholder[];
|
||||||
|
onChange: (s: Stakeholder[]) => void;
|
||||||
|
}> = ({ stakeholders, onChange }) => {
|
||||||
|
const [newName, setNewName] = useState('');
|
||||||
|
const [newRole, setNewRole] = useState('');
|
||||||
|
const [newPower, setNewPower] = useState<'高' | '中' | '低'>('中');
|
||||||
|
const [newInterest, setNewInterest] = useState<'高' | '中' | '低'>('中');
|
||||||
|
|
||||||
|
const addStakeholder = () => {
|
||||||
|
if (!newName || !newRole) return;
|
||||||
|
const strategy =
|
||||||
|
newPower === '高' && newInterest === '高'
|
||||||
|
? '重点管理'
|
||||||
|
: newPower === '低' && newInterest === '高'
|
||||||
|
? '保持满意'
|
||||||
|
: newPower === '高' && newInterest === '低'
|
||||||
|
? '随时告知'
|
||||||
|
: '监督';
|
||||||
|
onChange([
|
||||||
|
...stakeholders,
|
||||||
|
{ key: Date.now().toString(), name: newName, role: newRole, power: newPower, interest: newInterest, strategy },
|
||||||
|
]);
|
||||||
|
setNewName('');
|
||||||
|
setNewRole('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '姓名', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '角色', dataIndex: 'role', key: 'role' },
|
||||||
|
{
|
||||||
|
title: '权力',
|
||||||
|
dataIndex: 'power',
|
||||||
|
key: 'power',
|
||||||
|
render: (val: string) => (
|
||||||
|
<Tag color={val === '高' ? 'red' : val === '中' ? 'orange' : 'green'}>{val}</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '利益/关注度',
|
||||||
|
dataIndex: 'interest',
|
||||||
|
key: 'interest',
|
||||||
|
render: (val: string) => (
|
||||||
|
<Tag color={val === '高' ? 'red' : val === '中' ? 'orange' : 'green'}>{val}</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ title: '参与策略', dataIndex: 'strategy', key: 'strategy' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Space style={{ marginBottom: 16 }}>
|
||||||
|
<Input placeholder="姓名" value={newName} onChange={setNewName} style={{ width: 120 }} />
|
||||||
|
<Input placeholder="角色" value={newRole} onChange={setNewRole} style={{ width: 120 }} />
|
||||||
|
<Select value={newPower} onChange={setNewPower} style={{ width: 80 }}>
|
||||||
|
<Select.Option value="高">高</Select.Option>
|
||||||
|
<Select.Option value="中">中</Select.Option>
|
||||||
|
<Select.Option value="低">低</Select.Option>
|
||||||
|
</Select>
|
||||||
|
<Select value={newInterest} onChange={setNewInterest} style={{ width: 80 }}>
|
||||||
|
<Select.Option value="高">高</Select.Option>
|
||||||
|
<Select.Option value="中">中</Select.Option>
|
||||||
|
<Select.Option value="低">低</Select.Option>
|
||||||
|
</Select>
|
||||||
|
<Button type="primary" onClick={addStakeholder}>
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<Table columns={columns} data={stakeholders} pagination={false} size="small" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MilestoneStep: React.FC<{
|
||||||
|
milestones: Milestone[];
|
||||||
|
onChange: (m: Milestone[]) => void;
|
||||||
|
}> = ({ milestones, onChange }) => {
|
||||||
|
const [newName, setNewName] = useState('');
|
||||||
|
const [newDate, setNewDate] = useState('');
|
||||||
|
const [newDeliverable, setNewDeliverable] = useState('');
|
||||||
|
|
||||||
|
const addMilestone = () => {
|
||||||
|
if (!newName) return;
|
||||||
|
onChange([
|
||||||
|
...milestones,
|
||||||
|
{ key: Date.now().toString(), name: newName, targetDate: newDate, deliverable: newDeliverable },
|
||||||
|
]);
|
||||||
|
setNewName('');
|
||||||
|
setNewDate('');
|
||||||
|
setNewDeliverable('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '里程碑', dataIndex: 'name', key: 'name' },
|
||||||
|
{ title: '目标日期', dataIndex: 'targetDate', key: 'targetDate' },
|
||||||
|
{ title: '交付物', dataIndex: 'deliverable', key: 'deliverable' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Space style={{ marginBottom: 16 }}>
|
||||||
|
<Input placeholder="里程碑名称" value={newName} onChange={setNewName} style={{ width: 160 }} />
|
||||||
|
<Input placeholder="目标日期" value={newDate} onChange={setNewDate} style={{ width: 120 }} />
|
||||||
|
<Input placeholder="交付物" value={newDeliverable} onChange={setNewDeliverable} style={{ width: 160 }} />
|
||||||
|
<Button type="primary" onClick={addMilestone}>
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<Table columns={columns} data={milestones} pagination={false} size="small" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Main Wizard ---
|
||||||
|
|
||||||
const WizardPage: React.FC = () => {
|
const WizardPage: React.FC = () => {
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
const [project, setProject] = useState<ProjectData>(createEmptyProject());
|
const [project, setProject] = useState<ProjectData>(createEmptyProject());
|
||||||
@@ -80,8 +194,10 @@ const WizardPage: React.FC = () => {
|
|||||||
console.log('Project created:', project);
|
console.log('Project created:', project);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Step 1: Basic Info ----
|
const renderStep = () => {
|
||||||
const renderBasicInfo = () => (
|
switch (current) {
|
||||||
|
case 0:
|
||||||
|
return (
|
||||||
<Form layout="vertical" style={{ maxWidth: 600 }}>
|
<Form layout="vertical" style={{ maxWidth: 600 }}>
|
||||||
<Form.Item label="项目名称" required>
|
<Form.Item label="项目名称" required>
|
||||||
<Input
|
<Input
|
||||||
@@ -100,9 +216,8 @@ const WizardPage: React.FC = () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
case 1:
|
||||||
// ---- Step 2: Charter ----
|
return (
|
||||||
const renderCharter = () => (
|
|
||||||
<Form layout="vertical" style={{ maxWidth: 600 }}>
|
<Form layout="vertical" style={{ maxWidth: 600 }}>
|
||||||
<Form.Item label="包含范围">
|
<Form.Item label="包含范围">
|
||||||
<TextArea
|
<TextArea
|
||||||
@@ -136,124 +251,22 @@ const WizardPage: React.FC = () => {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
case 2:
|
||||||
// ---- Step 3: Stakeholders ----
|
|
||||||
const renderStakeholders = () => {
|
|
||||||
const columns = [
|
|
||||||
{ title: '姓名', dataIndex: 'name', key: 'name' },
|
|
||||||
{ title: '角色', dataIndex: 'role', key: 'role' },
|
|
||||||
{
|
|
||||||
title: '权力',
|
|
||||||
dataIndex: 'power',
|
|
||||||
key: 'power',
|
|
||||||
render: (val: string) => (
|
|
||||||
<Tag color={val === '高' ? 'red' : val === '中' ? 'orange' : 'green'}>{val}</Tag>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '利益/关注度',
|
|
||||||
dataIndex: 'interest',
|
|
||||||
key: 'interest',
|
|
||||||
render: (val: string) => (
|
|
||||||
<Tag color={val === '高' ? 'red' : val === '中' ? 'orange' : 'green'}>{val}</Tag>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{ title: '参与策略', dataIndex: 'strategy', key: 'strategy' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const [newName, setNewName] = useState('');
|
|
||||||
const [newRole, setNewRole] = useState('');
|
|
||||||
const [newPower, setNewPower] = useState<'高' | '中' | '低'>('中');
|
|
||||||
const [newInterest, setNewInterest] = useState<'高' | '中' | '低'>('中');
|
|
||||||
|
|
||||||
const addStakeholder = () => {
|
|
||||||
if (!newName || !newRole) return;
|
|
||||||
const strategy =
|
|
||||||
newPower === '高' && newInterest === '高'
|
|
||||||
? '重点管理'
|
|
||||||
: newPower === '低' && newInterest === '高'
|
|
||||||
? '保持满意'
|
|
||||||
: newPower === '高' && newInterest === '低'
|
|
||||||
? '随时告知'
|
|
||||||
: '监督';
|
|
||||||
setProject({
|
|
||||||
...project,
|
|
||||||
stakeholders: [
|
|
||||||
...project.stakeholders,
|
|
||||||
{ key: Date.now().toString(), name: newName, role: newRole, power: newPower, interest: newInterest, strategy },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
setNewName('');
|
|
||||||
setNewRole('');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<StakeholderStep
|
||||||
<Space style={{ marginBottom: 16 }}>
|
stakeholders={project.stakeholders}
|
||||||
<Input placeholder="姓名" value={newName} onChange={setNewName} style={{ width: 120 }} />
|
onChange={(s) => setProject({ ...project, stakeholders: s })}
|
||||||
<Input placeholder="角色" value={newRole} onChange={setNewRole} style={{ width: 120 }} />
|
/>
|
||||||
<Select value={newPower} onChange={setNewPower} style={{ width: 80 }}>
|
|
||||||
<Select.Option value="高">高</Select.Option>
|
|
||||||
<Select.Option value="中">中</Select.Option>
|
|
||||||
<Select.Option value="低">低</Select.Option>
|
|
||||||
</Select>
|
|
||||||
<Select value={newInterest} onChange={setNewInterest} style={{ width: 80 }}>
|
|
||||||
<Select.Option value="高">高</Select.Option>
|
|
||||||
<Select.Option value="中">中</Select.Option>
|
|
||||||
<Select.Option value="低">低</Select.Option>
|
|
||||||
</Select>
|
|
||||||
<Button type="primary" onClick={addStakeholder}>
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
<Table columns={columns} data={project.stakeholders} pagination={false} size="small" />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
case 3:
|
||||||
|
|
||||||
// ---- Step 4: Milestones ----
|
|
||||||
const renderMilestones = () => {
|
|
||||||
const columns = [
|
|
||||||
{ title: '里程碑', dataIndex: 'name', key: 'name' },
|
|
||||||
{ title: '目标日期', dataIndex: 'targetDate', key: 'targetDate' },
|
|
||||||
{ title: '交付物', dataIndex: 'deliverable', key: 'deliverable' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const [newName, setNewName] = useState('');
|
|
||||||
const [newDate, setNewDate] = useState('');
|
|
||||||
const [newDeliverable, setNewDeliverable] = useState('');
|
|
||||||
|
|
||||||
const addMilestone = () => {
|
|
||||||
if (!newName) return;
|
|
||||||
setProject({
|
|
||||||
...project,
|
|
||||||
milestones: [
|
|
||||||
...project.milestones,
|
|
||||||
{ key: Date.now().toString(), name: newName, targetDate: newDate, deliverable: newDeliverable },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
setNewName('');
|
|
||||||
setNewDate('');
|
|
||||||
setNewDeliverable('');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<MilestoneStep
|
||||||
<Space style={{ marginBottom: 16 }}>
|
milestones={project.milestones}
|
||||||
<Input placeholder="里程碑名称" value={newName} onChange={setNewName} style={{ width: 160 }} />
|
onChange={(m) => setProject({ ...project, milestones: m })}
|
||||||
<Input placeholder="目标日期" value={newDate} onChange={setNewDate} style={{ width: 120 }} />
|
/>
|
||||||
<Input placeholder="交付物" value={newDeliverable} onChange={setNewDeliverable} style={{ width: 160 }} />
|
|
||||||
<Button type="primary" onClick={addMilestone}>
|
|
||||||
添加
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
<Table columns={columns} data={project.milestones} pagination={false} size="small" />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
case 4:
|
||||||
|
return (
|
||||||
// ---- Step 5: Confirm ----
|
|
||||||
const renderConfirm = () => (
|
|
||||||
<Card title="项目创建确认">
|
<Card title="项目创建确认">
|
||||||
<Space direction="vertical" size="medium" style={{ width: '100%' }}>
|
<Space direction="vertical" size="medium" style={{ width: '100%' }}>
|
||||||
<div>
|
<div>
|
||||||
@@ -277,13 +290,17 @@ const WizardPage: React.FC = () => {
|
|||||||
<Text>{project.milestones.length}</Text>
|
<Text>{project.milestones.length}</Text>
|
||||||
</div>
|
</div>
|
||||||
{(!project.name || !project.goal) && (
|
{(!project.name || !project.goal) && (
|
||||||
<div style={{ color: '#ff7d00', fontSize: 14, marginTop: 8 }}>⚠️ 项目名称和目标为必填项</div>
|
<div style={{ color: '#ff7d00', fontSize: 14, marginTop: 8 }}>
|
||||||
|
⚠️ 项目名称和目标为必填项
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
default:
|
||||||
const stepRenderers = [renderBasicInfo, renderCharter, renderStakeholders, renderMilestones, renderConfirm];
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ maxWidth: 900, margin: '40px auto', padding: '0 24px' }}>
|
<div style={{ maxWidth: 900, margin: '40px auto', padding: '0 24px' }}>
|
||||||
@@ -296,7 +313,7 @@ const WizardPage: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
<Card style={{ minHeight: 300, marginBottom: 24 }}>{stepRenderers[current]()}</Card>
|
<Card style={{ minHeight: 300, marginBottom: 24 }}>{renderStep()}</Card>
|
||||||
|
|
||||||
<Space style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<Space style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Button onClick={prev} disabled={current === 0}>
|
<Button onClick={prev} disabled={current === 0}>
|
||||||
|
|||||||
Reference in New Issue
Block a user