好的投票 UI 必须有状态反馈:投票中 → 已提交 → 已确认 → 结果更新。
17.5.1 表单状态机
graph LR
Idle[空闲] --> Creating[正在创建]
Creating --> Created[创建成功/显示TX链接]
Created --> Idle
Vote[投票中] --> Signing[钱包签名中]
Signing --> Submitting[提交交易]
Submitting --> Confirming[等待确认]
Confirming --> Voted[投票成功]
Voted --> Results[结果更新]
17.5.2 完整投票表单组件
tsx
// components/VoteForm.tsx
import { useState } from 'react';
export const VoteForm = ({ program, wallet, poll, onVote }: any) => {
const [status, setStatus] = useState<'idle' | 'signing' | 'submitting' | 'done'>('idle');
const [selected, setSelected] = useState<number | null>(null);
const [error, setError] = useState('');
const handleVote = async () => {
if (selected === null) return;
setStatus('signing');
setError('');
try {
setStatus('submitting');
const tx = await program.methods
.castVote(selected)
.accounts({
voteAccount: poll.publicKey,
voter: wallet.publicKey,
systemProgram: web3.SystemProgram.programId,
})
.rpc({
commitment: 'confirmed',
preflightCommitment: 'confirmed',
});
setStatus('done');
onVote(); // 回调刷新列表
} catch (e: any) {
setError(e.message || '投票失败');
setStatus('idle');
}
};
return (
<div className="vote-form">
<h3>{poll.question}</h3>
{poll.options.map((opt: string, i: number) => (
<label key={i} className={`option ${selected === i ? 'selected' : ''}`}>
<input
type="radio"
name="vote-option"
checked={selected === i}
onChange={() => setSelected(i)}
/>
{opt}
</label>
))}
<button
onClick={handleVote}
disabled={status !== 'idle' || selected === null}
>
{status === 'idle' && '投票'}
{status === 'signing' && '钱包签名中...'}
{status === 'submitting' && '提交中...'}
{status === 'done' && '✅ 已投票'}
</button>
{error && <div className="error">{error}</div>}
</div>
);
};
// CSS(Tailwind 风格)
// .vote-form { @apply p-4 border rounded-lg bg-gray-50; }
// .option { @apply block p-2 cursor-pointer hover:bg-gray-100; }
// .option.selected { @apply bg-blue-100 border-blue-500; }
// button:disabled { @apply opacity-50 cursor-not-allowed; }17.5.3 UX 最佳实践
| 场景 | 处理 | 方式 |
|---|---|---|
| 用户重复投票 | 禁用投票按钮,显示"已投票" | 查询 has_voted 数组 |
| 投票过期 | 禁用整个组件,显示"已结束" | 比较当前时间 > end_time |
| 无钱包连接 | 提示"请先连接钱包" | 条件渲染 |
| 交易失败 | 显示错误信息,允许重试 | catch / setError |
| 交易确认中 | 显示加载状态,防止重复点击 | 状态机 |
, 前往 → 17.6 结果展示 |*
评论
0评论加载中…