/// 时间: 2026-03-29 /// 功能: 投票页面 /// 介绍: 提供投票功能,显示投票列表,查看投票详情,提交投票 import 'package:flutter/material.dart'; import '../../../constants/app_constants.dart'; import '../../../utils/http/vote_api.dart'; import '../components/login_register_dialog.dart'; class VotePage extends StatefulWidget { const VotePage({super.key}); @override State createState() => _VotePageState(); } class _VotePageState extends State { List _voteList = []; bool _isLoading = true; bool _isLoggedIn = false; int _currentPage = 1; bool _hasMore = true; bool _showDebugInfo = false; final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); _checkLoginStatus(); _loadVoteList(); _scrollController.addListener(_onScroll); } @override void dispose() { _scrollController.dispose(); super.dispose(); } void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { if (_hasMore && !_isLoading) { _loadMoreVotes(); } } } Future _checkLoginStatus() async { final isLoggedIn = await VoteApi.isLoggedIn(); if (mounted) { setState(() { _isLoggedIn = isLoggedIn; }); } } void _showLoginDialog() { showDialog( context: context, builder: (context) => LoginRegisterDialog( onLoginSuccess: () { _checkLoginStatus(); _loadVoteList(); }, ), ); } static const String _filterTitle = 'poes'; Future _loadVoteList() async { setState(() { _isLoading = true; _currentPage = 1; _voteList = []; }); try { final response = await VoteApi.getVoteList(page: 1); final filteredList = response.list .where( (vote) => vote.title.toLowerCase() == _filterTitle.toLowerCase(), ) .toList(); if (mounted) { setState(() { _voteList = filteredList; _hasMore = response.pagination.page < response.pagination.totalPage; _isLoading = false; }); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toString().replaceAll('Exception: ', '')), backgroundColor: AppConstants.errorColor, ), ); setState(() { _isLoading = false; }); } } } Future _loadMoreVotes() async { if (mounted) { setState(() { _isLoading = true; }); } try { final response = await VoteApi.getVoteList(page: _currentPage + 1); final filteredList = response.list .where( (vote) => vote.title.toLowerCase() == _filterTitle.toLowerCase(), ) .toList(); if (mounted) { setState(() { _voteList.addAll(filteredList); _currentPage = response.pagination.page; _hasMore = response.pagination.page < response.pagination.totalPage; _isLoading = false; }); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toString().replaceAll('Exception: ', '')), backgroundColor: AppConstants.errorColor, ), ); setState(() { _isLoading = false; }); } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('参与投票'), backgroundColor: AppConstants.primaryColor, actions: [ if (_isLoggedIn) IconButton( icon: const Icon(Icons.logout), onPressed: () async { await VoteApi.clearUserLogin(); await _checkLoginStatus(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('已清除投票凭证'), backgroundColor: AppConstants.successColor, ), ); } }, ), ], ), body: RefreshIndicator( onRefresh: _loadVoteList, child: _isLoading && _voteList.isEmpty ? const Center(child: CircularProgressIndicator()) : Column( children: [ Expanded( child: _voteList.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.poll_outlined, size: 64, color: Colors.grey, ), const SizedBox(height: 16), const Text( '暂无投票', style: TextStyle( color: Colors.grey, fontSize: 16, ), ), const SizedBox(height: 16), ElevatedButton( onPressed: _loadVoteList, style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, ), child: const Text('刷新'), ), ], ), ) : ListView.builder( controller: _scrollController, padding: const EdgeInsets.all(16), itemCount: _voteList.length + (_hasMore ? 1 : 0), itemBuilder: (context, index) { if (index == _voteList.length) { return const Center( child: Padding( padding: EdgeInsets.all(16), child: CircularProgressIndicator(), ), ); } return _buildVoteItem(_voteList[index]); }, ), ), Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), child: Column( children: [ SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () { setState(() { _showDebugInfo = !_showDebugInfo; }); }, icon: Icon( _showDebugInfo ? Icons.visibility_off : Icons.bug_report, ), label: Text(_showDebugInfo ? '隐藏调试信息' : '显示调试信息'), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[700], padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ), ), if (_showDebugInfo) _buildDebugInfo(), ], ), ), ], ), ), ); } Widget _buildVoteItem(VoteItem vote) { final statusColor = _getStatusColor(vote); final statusText = _getStatusText(vote); return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.08), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: InkWell( onTap: () { if (!_isLoggedIn) { _showLoginDialog(); return; } _navigateToVoteDetail(vote); }, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppConstants.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( vote.isSingleChoice ? Icons.radio_button_checked : Icons.check_box, color: AppConstants.primaryColor, size: 20, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( vote.title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( vote.isSingleChoice ? '单选投票' : '多选投票 (最多${vote.maxtime}项)', style: TextStyle( color: Colors.grey[500], fontSize: 12, ), ), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(20), ), child: Text( statusText, style: TextStyle( color: statusColor, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ], ), if (vote.idesc != null && vote.idesc!.isNotEmpty) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon( Icons.description_outlined, size: 16, color: Colors.grey[400], ), const SizedBox(width: 8), Expanded( child: Text( vote.idesc!, style: TextStyle( color: Colors.grey[600], fontSize: 13, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), ), ], const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), ), child: Column( children: [ Row( children: [ _buildInfoItem( icon: Icons.play_arrow_outlined, label: '开始', value: _formatDate(vote.statime), ), Container( height: 24, width: 1, color: Colors.grey[200], margin: const EdgeInsets.symmetric(horizontal: 12), ), _buildInfoItem( icon: Icons.stop_outlined, label: '结束', value: _formatDate(vote.endtime), ), ], ), const SizedBox(height: 10), Row( children: [ _buildInfoItem( icon: Icons.visibility_outlined, label: '浏览', value: '${vote.iview}次', ), Container( height: 24, width: 1, color: Colors.grey[200], margin: const EdgeInsets.symmetric(horizontal: 12), ), _buildInfoItem( icon: Icons.add_circle_outline, label: '创建', value: _formatDate(vote.addtime), ), ], ), ], ), ), ], ), ), ), ); } Widget _buildInfoItem({ required IconData icon, required String label, required String value, }) { return Expanded( child: Row( children: [ Icon(icon, size: 16, color: Colors.grey[400]), const SizedBox(width: 6), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle(color: Colors.grey[400], fontSize: 11), ), Text( value, style: TextStyle( color: Colors.grey[700], fontSize: 13, fontWeight: FontWeight.w500, ), ), ], ), ], ), ); } Color _getStatusColor(VoteItem vote) { if (vote.status == 0) return Colors.grey; if (vote.statusClass == 'vote-status-ended') return Colors.red; if (vote.statusClass == 'vote-status-pending') return Colors.orange; return AppConstants.successColor; } String _getStatusText(VoteItem vote) { if (vote.statusText != null && vote.statusText!.isNotEmpty) { return vote.statusText!; } if (vote.status == 0) return '已禁用'; return vote.isActive ? '进行中' : '已结束'; } String _formatDate(String dateStr) { if (dateStr.isEmpty) return '-'; try { final parts = dateStr.split(' '); if (parts.isNotEmpty) { final dateParts = parts[0].split('-'); if (dateParts.length >= 3) { return '${dateParts[1]}-${dateParts[2]}'; } } return dateStr; } catch (e) { return dateStr; } } void _navigateToVoteDetail(VoteItem vote) { Navigator.push( context, MaterialPageRoute(builder: (context) => VoteDetailPage(vote: vote)), ).then((_) { _loadVoteList(); }); } Widget _buildDebugInfo() { return FutureBuilder?>( future: VoteApi.getUserInfo(), builder: (context, snapshot) { final userInfo = snapshot.data; return Container( margin: const EdgeInsets.only(top: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[900], borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '调试信息', style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), _buildDebugItem('登录状态', _isLoggedIn ? '已验证' : '未验证'), _buildDebugItem('投票数量', '${_voteList.length}'), _buildDebugItem('当前页码', '$_currentPage'), _buildDebugItem('是否有更多', _hasMore ? '是' : '否'), if (userInfo != null) ...[ const Divider(color: Colors.grey, height: 16), ...userInfo.entries.map((entry) { return _buildDebugItem( entry.key, entry.value?.toString() ?? 'null', ); }), ], ], ), ); }, ); } Widget _buildDebugItem(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 100, child: Text( '$label:', style: TextStyle(color: Colors.grey[400], fontSize: 12), ), ), Expanded( child: Text( value, style: const TextStyle(color: Colors.white, fontSize: 12), ), ), ], ), ); } } class VoteDetailPage extends StatefulWidget { final VoteItem vote; const VoteDetailPage({super.key, required this.vote}); @override State createState() => _VoteDetailPageState(); } class _VoteDetailPageState extends State { VoteDetailResponse? _voteDetail; VoteResultResponse? _voteResult; List _selectedOptions = []; bool _isLoading = true; bool _isSubmitting = false; bool _showResult = false; @override void initState() { super.initState(); _loadVoteDetail(); } Future _loadVoteDetail() async { if (mounted) { setState(() { _isLoading = true; }); } try { final response = await VoteApi.getVoteDetail(widget.vote.id); if (mounted) { setState(() { _voteDetail = response; _selectedOptions = List.from(response.userVotes); _showResult = response.hasVoted; _isLoading = false; }); } if (_showResult) { await _loadVoteResult(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toString().replaceAll('Exception: ', '')), backgroundColor: AppConstants.errorColor, ), ); setState(() { _isLoading = false; }); } } } Future _loadVoteResult() async { try { final response = await VoteApi.getVoteResult(widget.vote.id); if (mounted) { setState(() { _voteResult = response; }); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toString().replaceAll('Exception: ', '')), backgroundColor: AppConstants.errorColor, ), ); } } } void _toggleOption(int optionId) { if (_showResult) return; setState(() { if (_voteDetail!.vote.isSingleChoice) { _selectedOptions = [optionId]; } else { if (_selectedOptions.contains(optionId)) { _selectedOptions.remove(optionId); } else { if (_selectedOptions.length < _voteDetail!.vote.maxtime) { _selectedOptions.add(optionId); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('最多只能选择${_voteDetail!.vote.maxtime}项'), backgroundColor: AppConstants.warningColor, ), ); } } } }); } Future _submitVote() async { if (_selectedOptions.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('请至少选择一个选项'), backgroundColor: AppConstants.warningColor, ), ); return; } setState(() { _isSubmitting = true; }); try { await VoteApi.submitVote( topicId: widget.vote.id, options: _selectedOptions, ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('投票成功'), backgroundColor: AppConstants.successColor, ), ); setState(() { _showResult = true; }); await _loadVoteResult(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toString().replaceAll('Exception: ', '')), backgroundColor: AppConstants.errorColor, ), ); } } finally { if (mounted) { setState(() { _isSubmitting = false; }); } } } @override Widget build(BuildContext context) { final statusColor = _getDetailStatusColor(); final statusText = _getDetailStatusText(); return Scaffold( appBar: AppBar( title: Text(widget.vote.title), backgroundColor: AppConstants.primaryColor, ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppConstants.primaryColor.withValues(alpha: 0.1), AppConstants.primaryColor.withValues(alpha: 0.05), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppConstants.primaryColor.withValues( alpha: 0.15, ), borderRadius: BorderRadius.circular(10), ), child: Icon( widget.vote.isSingleChoice ? Icons.radio_button_checked : Icons.check_box, color: AppConstants.primaryColor, size: 24, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.vote.title, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), Text( widget.vote.isSingleChoice ? '单选投票' : '多选投票 (最多${widget.vote.maxtime}项)', style: TextStyle( color: Colors.grey[600], fontSize: 13, ), ), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(20), ), child: Text( statusText, style: TextStyle( color: statusColor, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ], ), if (widget.vote.idesc != null && widget.vote.idesc!.isNotEmpty) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( Icons.description_outlined, size: 18, color: Colors.grey[400], ), const SizedBox(width: 8), Expanded( child: Text( widget.vote.idesc!, style: TextStyle( color: Colors.grey[600], fontSize: 14, ), ), ), ], ), ), ], ], ), ), const SizedBox(height: 16), _buildTimeInfoCard(), const SizedBox(height: 16), if (_voteDetail != null && !_voteDetail!.canVote) Container( width: double.infinity, padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.orange.withValues(alpha: 0.3), ), ), child: Row( children: [ Icon( Icons.info_outline, color: Colors.orange[700], size: 20, ), const SizedBox(width: 8), Expanded( child: Text( _voteDetail!.hasVoted ? '您已参与过此投票' : '当前无法参与投票', style: TextStyle( color: Colors.orange[700], fontSize: 13, ), ), ), ], ), ), if (_showResult && _voteResult != null) _buildResultView() else _buildVoteView(), ], ), ), ); } Widget _buildTimeInfoCard() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Row( children: [ _buildTimeItem( icon: Icons.play_arrow_rounded, label: '开始时间', value: widget.vote.statime, color: AppConstants.successColor, ), Container( height: 40, width: 1, color: Colors.grey[200], margin: const EdgeInsets.symmetric(horizontal: 16), ), _buildTimeItem( icon: Icons.stop_rounded, label: '结束时间', value: widget.vote.endtime, color: Colors.red, ), ], ), const Divider(height: 24), Row( children: [ _buildTimeItem( icon: Icons.add_circle_outline, label: '创建时间', value: widget.vote.addtime, color: Colors.blue, ), Container( height: 40, width: 1, color: Colors.grey[200], margin: const EdgeInsets.symmetric(horizontal: 16), ), _buildTimeItem( icon: Icons.visibility_outlined, label: '浏览次数', value: '${widget.vote.iview} 次', color: Colors.purple, ), ], ), ], ), ); } Widget _buildTimeItem({ required IconData icon, required String label, required String value, required Color color, }) { return Expanded( child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, size: 18, color: color), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle(color: Colors.grey[500], fontSize: 11), ), const SizedBox(height: 2), Text( value, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ); } Color _getDetailStatusColor() { if (widget.vote.status == 0) return Colors.grey; if (widget.vote.statusClass == 'vote-status-ended') return Colors.red; if (widget.vote.statusClass == 'vote-status-pending') return Colors.orange; return AppConstants.successColor; } String _getDetailStatusText() { if (widget.vote.statusText != null && widget.vote.statusText!.isNotEmpty) { return widget.vote.statusText!; } if (widget.vote.status == 0) return '已禁用'; return widget.vote.isActive ? '进行中' : '已结束'; } Widget _buildVoteView() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ..._voteDetail!.options.map((option) { final isSelected = _selectedOptions.contains(option.id); return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( border: Border.all( color: isSelected ? AppConstants.primaryColor : Colors.grey[300]!, width: 2, ), borderRadius: BorderRadius.circular(12), color: isSelected ? AppConstants.primaryColor.withValues(alpha: 0.05) : Colors.white, ), child: InkWell( onTap: () => _toggleOption(option.id), borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon( widget.vote.isSingleChoice ? (isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked) : (isSelected ? Icons.check_box : Icons.check_box_outline_blank), color: isSelected ? AppConstants.primaryColor : Colors.grey, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( option.name, style: TextStyle( fontSize: 16, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? AppConstants.primaryColor : Colors.black, ), ), if (option.idesc != null && option.idesc!.isNotEmpty) ...[ const SizedBox(height: 4), Text( option.idesc!, style: TextStyle( color: Colors.grey[600], fontSize: 12, ), ), ], ], ), ), ], ), ), ), ); }), const SizedBox(height: 24), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isSubmitting ? null : _submitVote, style: ElevatedButton.styleFrom( backgroundColor: AppConstants.primaryColor, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: _isSubmitting ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Text('提交投票', style: TextStyle(fontSize: 16)), ), ), ], ); } Widget _buildResultView() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AppConstants.successColor.withValues(alpha: 0.15), AppConstants.successColor.withValues(alpha: 0.05), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), border: Border.all( color: AppConstants.successColor.withValues(alpha: 0.3), width: 1, ), ), child: Column( children: [ Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppConstants.successColor.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(10), ), child: const Icon( Icons.how_to_vote, color: AppConstants.successColor, size: 24, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '投票结果', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppConstants.successColor, ), ), Text( '您已参与投票', style: TextStyle( color: Colors.grey[600], fontSize: 13, ), ), ], ), ), ], ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ _buildResultStat( icon: Icons.poll, label: '总票数', value: '${_voteResult!.totalVotes}', color: AppConstants.primaryColor, ), Container(height: 36, width: 1, color: Colors.grey[200]), _buildResultStat( icon: Icons.how_to_vote_outlined, label: '选项数', value: '${_voteResult!.options.length}', color: Colors.blue, ), Container(height: 36, width: 1, color: Colors.grey[200]), _buildResultStat( icon: Icons.check_circle_outline, label: '我的选择', value: '${_voteResult!.userVotes.length}', color: AppConstants.successColor, ), ], ), ), ], ), ), const SizedBox(height: 20), Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), child: Row( children: [ Icon(Icons.bar_chart_rounded, size: 18, color: Colors.grey[500]), const SizedBox(width: 8), Text( '投票详情', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Colors.grey[700], ), ), ], ), ), const SizedBox(height: 8), ..._voteResult!.options.map((option) { final isVoted = _voteResult!.userVotes.contains(option.id); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: isVoted ? AppConstants.successColor.withValues(alpha: 0.05) : Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all( color: isVoted ? AppConstants.successColor.withValues(alpha: 0.3) : Colors.grey[200]!, width: 1, ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.03), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ if (isVoted) Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: AppConstants.successColor, borderRadius: BorderRadius.circular(6), ), child: const Icon( Icons.check, color: Colors.white, size: 14, ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( option.name, style: TextStyle( fontSize: 15, fontWeight: isVoted ? FontWeight.bold : FontWeight.w500, ), ), if (option.idesc != null && option.idesc!.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 2), child: Text( option.idesc!, style: TextStyle( color: Colors.grey[500], fontSize: 12, ), ), ), ], ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), decoration: BoxDecoration( color: isVoted ? AppConstants.successColor.withValues(alpha: 0.1) : AppConstants.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '${option.percentage}%', style: TextStyle( color: isVoted ? AppConstants.successColor : AppConstants.primaryColor, fontSize: 14, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 10), Row( children: [ Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(6), child: LinearProgressIndicator( value: option.percentage / 100, backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation( isVoted ? AppConstants.successColor : AppConstants.primaryColor, ), minHeight: 8, ), ), ), const SizedBox(width: 12), Text( '${option.count}票', style: TextStyle( color: Colors.grey[600], fontSize: 13, fontWeight: FontWeight.w500, ), ), ], ), ], ), ); }), ], ); } Widget _buildResultStat({ required IconData icon, required String label, required String value, required Color color, }) { return Expanded( child: Column( children: [ Icon(icon, size: 20, color: color), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: color, ), ), Text(label, style: TextStyle(color: Colors.grey[500], fontSize: 11)), ], ), ); } }