教程区块链区块链技术ch1717.5 创建投票与投票表单 UI

本页目录

好的投票 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

评论加载中…

发表评论

0/2000