教程区块链区块链技术ch1717.6 投票结果展示与结果计数更新

本页目录

flowchart TD
    User[用户] --> F[前端 React]
    F --> Q[查询 RPC get_results]
    Q --> P[Vote Program 账户]
    P --> Data[反序列化投票数据<br/>投票选项/各选项票数]
    Data --> Chart[可视化渲染
    条形图/饼图/表格]
    Chart --> Live[WebSocket 实时更新<br/>solana_account_sub]
    Live --> Q
    style P fill:#c8e6c9
    style Chart fill:#e3f2fd

结果展示需要实时性。Solana 的 400ms 出块时间让"实时更新"成为可能。


17.6.1 实时数据获取

typescript
// hooks/usePolls.ts — 获取所有投票
import { useEffect, useState } from 'react';
import { PublicKey } from '@solana/web3.js';

export const usePolls = (program: any) => {
  const [polls, setPolls] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);

  const fetchPolls = async () => {
    if (!program) return;
    
    // Anchor 自动索引所有 VoteAccount
    const accounts = await program.account.voteAccount.all();
    
    // 按创建时间排序
    const sorted = accounts.sort((a: any, b: any) => 
      (b.account.createdAt - a.account.createdAt)
    );
    
    setPolls(sorted);
    setLoading(false);
  };

  useEffect(() => { fetchPolls(); }, [program]);
  
  // 每 5 秒自动刷新
  useEffect(() => {
    const interval = setInterval(fetchPolls, 5000);
    return () => clearInterval(interval);
  }, [program]);

  return { polls, loading, refetch: fetchPolls };
};

17.6.2 结果卡片组件

tsx
// components/ResultsCard.tsx
export const ResultsCard = ({ poll }: { poll: any }) => {
  const { question, options, votes, isActive, endTime } = poll.account;
  const totalVotes = votes.reduce((a: number, b: number) => a + b, 0);
  
  return (
    <div className="results-card">
      <h3>{question}</h3>
      <div className="status-badge">
        {isActive ? <span className="active">● 进行中</span> : <span className="closed">✓ 已结束</span>}
        {isActive && <span>剩余 {Math.max(0, Math.floor((endTime - Date.now()/1000)/60))} 分钟</span>}
      </div>
      
      {options.map((opt: string, i: number) => {
        const pct = totalVotes ? (votes[i] / totalVotes * 100) : 0;
        return (
          <div key={i} className="result-bar">
            <div className="label">{opt}</div>
            <div className="bar-container">
              <div className="bar" style={{width: `${pct}%`}} />
            </div>
            <div className="count">{votes[i]} 票 ({pct.toFixed(1)}%)</div>
          </div>
        );
      })}
      
      <div className="total">总计: {totalVotes} 票</div>
    </div>
  );
};

17.6.3 概率统计(可选高级)

pi=vijvjp_i = \frac{v_i}{\sum_{j} v_j}
typescript
// 计算置信区间(Wilson 得分)
function wilsonScore(votes: number, total: number, z: number = 1.96) {
  const p = votes / total;
  const n = total;
  const phat = (p + z*z/(2*n)) / (1 + z*z/n);
  const error = z * Math.sqrt((p*(1-p) + z*z/(4*n)) / n) / (1 + z*z/n);
  return { lower: phat - error, upper: phat + error };
}

当票数较小时,Wilson 区间给出更保守的估计。教学版可省略。


, 前往 → 17.7 动画与实时更新 |*

评论

0

评论加载中…

发表评论

0/2000