/// 时间: 2026-03-29 /// 功能: 投票API服务 /// 介绍: 专门处理投票相关的API请求,包括获取投票列表、投票详情、提交投票等功能 /// 最新变化: 添加Cookie管理器支持PHP Session认证 import 'dart:convert'; import 'dart:io' as io show Platform; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:cookie_jar/cookie_jar.dart'; import 'package:platform_info/platform_info.dart'; import 'package:shared_preferences/shared_preferences.dart'; class VoteApi { static const String _baseUrl = 'https://poe.vogov.cn/toupiao/'; static const String _voteEndpoint = 'vote_api.php'; static const String _userEndpoint = 'tapi.php'; static const Duration _timeout = Duration(seconds: 30); static final BaseOptions _options = BaseOptions( baseUrl: _baseUrl, connectTimeout: _timeout, receiveTimeout: _timeout, sendTimeout: _timeout, headers: { 'Content-Type': 'application/json; charset=UTF-8', 'Accept': 'application/json', 'User-Agent': 'Poes-Flutter/1.0.0', }, ); static final CookieJar _cookieJar = CookieJar(); static final Dio _dio = Dio(_options) ..interceptors.add(CookieManager(_cookieJar)); static Future> _get( String path, { Map? queryParameters, }) async { try { final url = '$_baseUrl$path'; final response = await _dio.get(url, queryParameters: queryParameters); return response.data as Map; } on DioException catch (e) { throw Exception('请求失败: ${e.message}'); } catch (e) { throw Exception('请求失败: $e'); } } static Future> _post( String path, { Map? data, }) async { try { final url = '$_baseUrl$path'; final response = await _dio.post(url, data: data); return response.data as Map; } on DioException catch (e) { throw Exception('请求失败: ${e.message}'); } catch (e) { throw Exception('请求失败: $e'); } } /// 获取用户登录状态 static Future isLoggedIn() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool('vote_logged_in') ?? false; } /// 获取用户信息 static Future?> getUserInfo() async { final prefs = await SharedPreferences.getInstance(); final userData = prefs.getString('vote_user'); if (userData != null) { try { return jsonDecode(userData) as Map; } catch (e) { return null; } } return null; } /// 保存用户登录状态 static Future saveUserLogin(Map userData) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('vote_logged_in', true); await prefs.setString('vote_user', jsonEncode(userData)); } /// 清除用户登录状态 static Future clearUserLogin() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove('vote_logged_in'); await prefs.remove('vote_user'); } /// 用户登录 static Future login({ required String username, required String password, }) async { final jsonData = await _post( _userEndpoint, data: {'act': 'login', 'username': username, 'password': password}, ); if (jsonData['code'] != 0) { throw Exception(jsonData['msg'] ?? '登录失败'); } final userData = jsonData['data']; if (userData != null) { await saveUserLogin(userData); } return VoteApiResponse.fromJson(jsonData); } /// 用户注册 static Future register({ required String username, required String password, String? userIdentifier, }) async { final data = { 'act': 'register', 'username': username, 'password': password, }; if (userIdentifier != null) { data['user_identifier'] = userIdentifier; } final jsonData = await _post(_userEndpoint, data: data); if (jsonData['code'] != 0) { throw Exception(jsonData['msg'] ?? '注册失败'); } return VoteApiResponse.fromJson(jsonData); } /// 获取投票列表 static Future getVoteList({ int page = 1, int pageSize = 10, int status = 1, int type = -1, }) async { final jsonData = await _get( _voteEndpoint, queryParameters: { 'act': 'getVoteList', 'page': page.toString(), 'pageSize': pageSize.toString(), 'status': status.toString(), 'type': type.toString(), }, ); if (jsonData['code'] != 0) { throw Exception(jsonData['msg'] ?? '获取投票列表失败'); } return VoteListResponse.fromJson(jsonData); } /// 获取投票详情 static Future getVoteDetail(int id) async { final jsonData = await _get( _voteEndpoint, queryParameters: {'act': 'getVoteDetail', 'id': id.toString()}, ); if (jsonData['code'] != 0) { throw Exception(jsonData['msg'] ?? '获取投票详情失败'); } return VoteDetailResponse.fromJson(jsonData); } /// 提交投票 static Future submitVote({ required int topicId, required List options, }) async { final jsonData = await _post( _voteEndpoint, data: {'act': 'submitVote', 'topic_id': topicId, 'options': options}, ); if (jsonData['code'] != 0) { throw Exception(jsonData['msg'] ?? '提交投票失败'); } return VoteApiResponse.fromJson(jsonData); } /// 获取投票结果 static Future getVoteResult(int id) async { final jsonData = await _get( _voteEndpoint, queryParameters: {'act': 'getVoteResult', 'id': id.toString()}, ); if (jsonData['code'] != 0) { throw Exception(jsonData['msg'] ?? '获取投票结果失败'); } return VoteResultResponse.fromJson(jsonData); } /// 获取用户投票记录 static Future getUserVotes({ int page = 1, int pageSize = 10, }) async { final jsonData = await _get( _voteEndpoint, queryParameters: { 'act': 'getUserVotes', 'page': page.toString(), 'pageSize': pageSize.toString(), }, ); if (jsonData['code'] != 0) { throw Exception(jsonData['msg'] ?? '获取用户投票记录失败'); } return VoteListResponse.fromJson(jsonData); } /// 获取设备类型作为user_identifier static Future getDeviceType() async { final platform = Platform.instance; bool isHarmonyOS = false; String platformName = 'Unknown'; try { final String osName = io.Platform.operatingSystem; final String osVersion = io.Platform.operatingSystemVersion.toLowerCase(); if (osName == 'ohos' || osName == 'harmonyos' || osName == 'openharmony') { platformName = 'HarmonyOS'; isHarmonyOS = true; } else if (io.Platform.isAndroid) { platformName = 'Android'; if (osVersion.contains('harmony') || osVersion.contains('ohos') || osVersion.contains('openharmony')) { platformName = 'HarmonyOS'; isHarmonyOS = true; } } else if (io.Platform.isIOS) { platformName = 'iOS'; } else if (io.Platform.isMacOS) { platformName = 'macOS'; } else if (io.Platform.isWindows) { platformName = 'Windows'; } else if (io.Platform.isLinux) { platformName = 'Linux'; } else if (io.Platform.isFuchsia) { platformName = 'Fuchsia'; } else { platformName = osName[0].toUpperCase() + osName.substring(1); } } catch (e) { platformName = switch (platform.operatingSystem) { const OperatingSystem.android() => 'Android', const OperatingSystem.fuchsia() => 'Fuchsia', const OperatingSystem.iOS() => 'iOS', const OperatingSystem.linux() => 'Linux', const OperatingSystem.macOS() => 'macOS', const OperatingSystem.windows() => 'Windows', const OperatingSystem.unknown() || _ => 'Unknown', }; } String deviceType = 'Unknown'; if (isHarmonyOS) { final String osName = io.Platform.operatingSystem; if (osName == 'ohos') { deviceType = 'OHOS'; } else if (osName == 'harmonyos') { deviceType = 'HarmonyOS'; } else if (osName == 'openharmony') { deviceType = 'OpenHarmony'; } else { deviceType = 'HarmonyOS'; } } else { deviceType = platform.when( mobile: () => 'Mobile', desktop: () => 'Desktop', js: () => 'Web', orElse: () => null, ) ?? 'Unknown'; } return '$platformName-$deviceType-Flutter'; } } /// 基础API响应 class VoteApiResponse { final int code; final String message; final dynamic data; VoteApiResponse({required this.code, required this.message, this.data}); factory VoteApiResponse.fromJson(Map json) { return VoteApiResponse( code: json['code'] ?? 0, message: json['msg'] ?? '', data: json['data'], ); } bool get isSuccess => code == 0; } /// 投票列表响应 class VoteListResponse { final List list; final VotePagination pagination; VoteListResponse({required this.list, required this.pagination}); factory VoteListResponse.fromJson(Map json) { final data = json['data'] as Map? ?? {}; final listRaw = data['list'] as List? ?? []; final list = listRaw.map((item) => VoteItem.fromJson(item)).toList(); final pagination = VotePagination.fromJson( data['pagination'] as Map? ?? {}, ); return VoteListResponse(list: list, pagination: pagination); } } /// 投票项 class VoteItem { final int id; final String title; final String? idesc; final int itype; final int maxtime; final int status; final int iview; final String addtime; final String statime; final String endtime; final String? statusText; final String? statusClass; VoteItem({ required this.id, required this.title, this.idesc, required this.itype, required this.maxtime, required this.status, required this.iview, required this.addtime, required this.statime, required this.endtime, this.statusText, this.statusClass, }); factory VoteItem.fromJson(Map json) { return VoteItem( id: int.tryParse(json['id'].toString()) ?? 0, title: json['title']?.toString() ?? '', idesc: json['idesc']?.toString(), itype: int.tryParse(json['itype'].toString()) ?? 0, maxtime: int.tryParse(json['maxtime'].toString()) ?? 0, status: int.tryParse(json['status'].toString()) ?? 0, iview: int.tryParse(json['iview'].toString()) ?? 0, addtime: json['addtime']?.toString() ?? '', statime: json['statime']?.toString() ?? '', endtime: json['endtime']?.toString() ?? '', statusText: json['status_text']?.toString(), statusClass: json['status_class']?.toString(), ); } bool get isSingleChoice => itype == 0; bool get isMultipleChoice => itype == 1; bool get isActive => status == 1; } /// 分页信息 class VotePagination { final int total; final int page; final int pageSize; final int totalPage; final int offset; VotePagination({ required this.total, required this.page, required this.pageSize, required this.totalPage, required this.offset, }); factory VotePagination.fromJson(Map json) { return VotePagination( total: int.tryParse(json['total'].toString()) ?? 0, page: int.tryParse(json['page'].toString()) ?? 1, pageSize: int.tryParse(json['pageSize'].toString()) ?? 10, totalPage: int.tryParse(json['totalPage'].toString()) ?? 1, offset: int.tryParse(json['offset'].toString()) ?? 0, ); } } /// 投票详情响应 class VoteDetailResponse { final VoteItem vote; final List options; final bool hasVoted; final List userVotes; final bool canVote; VoteDetailResponse({ required this.vote, required this.options, required this.hasVoted, required this.userVotes, required this.canVote, }); factory VoteDetailResponse.fromJson(Map json) { final data = json['data'] as Map? ?? {}; final voteRaw = data['vote'] as Map? ?? {}; final optionsRaw = data['options'] as List? ?? []; final userVotesRaw = data['userVotes'] as List? ?? []; return VoteDetailResponse( vote: VoteItem.fromJson(voteRaw), options: optionsRaw.map((item) => VoteOption.fromJson(item)).toList(), hasVoted: data['hasVoted'] as bool? ?? false, userVotes: userVotesRaw .map((id) => int.tryParse(id.toString()) ?? 0) .where((id) => id > 0) .toList(), canVote: data['canVote'] as bool? ?? true, ); } } /// 投票选项 class VoteOption { final int id; final int topicId; final String name; final String? idesc; final String? imgs; final int sort; VoteOption({ required this.id, required this.topicId, required this.name, this.idesc, this.imgs, required this.sort, }); factory VoteOption.fromJson(Map json) { return VoteOption( id: int.tryParse(json['id'].toString()) ?? 0, topicId: int.tryParse(json['topic_id'].toString()) ?? 0, name: json['name']?.toString() ?? '', idesc: json['idesc']?.toString(), imgs: json['imgs']?.toString(), sort: int.tryParse(json['sort'].toString()) ?? 0, ); } } /// 投票结果响应 class VoteResultResponse { final VoteItem vote; final List options; final int totalVotes; final bool hasVoted; final List userVotes; VoteResultResponse({ required this.vote, required this.options, required this.totalVotes, required this.hasVoted, required this.userVotes, }); factory VoteResultResponse.fromJson(Map json) { final data = json['data'] as Map? ?? {}; final voteRaw = data['vote'] as Map? ?? {}; final optionsRaw = data['options'] as List? ?? []; final userVotesRaw = data['userVotes'] as List? ?? []; return VoteResultResponse( vote: VoteItem.fromJson(voteRaw), options: optionsRaw .map((item) => VoteResultOption.fromJson(item)) .toList(), totalVotes: int.tryParse(data['totalVotes'].toString()) ?? 0, hasVoted: data['hasVoted'] as bool? ?? false, userVotes: userVotesRaw .map((id) => int.tryParse(id.toString()) ?? 0) .where((id) => id > 0) .toList(), ); } } /// 投票结果选项 class VoteResultOption { final int id; final String name; final String? idesc; final String? imgs; final int count; final int percentage; VoteResultOption({ required this.id, required this.name, this.idesc, this.imgs, required this.count, required this.percentage, }); factory VoteResultOption.fromJson(Map json) { return VoteResultOption( id: int.tryParse(json['id'].toString()) ?? 0, name: json['name']?.toString() ?? '', idesc: json['idesc']?.toString(), imgs: json['imgs']?.toString(), count: int.tryParse(json['count'].toString()) ?? 0, percentage: int.tryParse(json['percentage'].toString()) ?? 0, ); } }