Files
wushu/lib/utils/http/vote_api.dart
Developer 888363785b 重构
2026-03-31 03:48:14 +08:00

564 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 时间: 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<Map<String, dynamic>> _get(
String path, {
Map<String, dynamic>? queryParameters,
}) async {
try {
final url = '$_baseUrl$path';
final response = await _dio.get(url, queryParameters: queryParameters);
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
throw Exception('请求失败: ${e.message}');
} catch (e) {
throw Exception('请求失败: $e');
}
}
static Future<Map<String, dynamic>> _post(
String path, {
Map<String, dynamic>? data,
}) async {
try {
final url = '$_baseUrl$path';
final response = await _dio.post(url, data: data);
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
throw Exception('请求失败: ${e.message}');
} catch (e) {
throw Exception('请求失败: $e');
}
}
/// 获取用户登录状态
static Future<bool> isLoggedIn() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool('vote_logged_in') ?? false;
}
/// 获取用户信息
static Future<Map<String, dynamic>?> getUserInfo() async {
final prefs = await SharedPreferences.getInstance();
final userData = prefs.getString('vote_user');
if (userData != null) {
try {
return jsonDecode(userData) as Map<String, dynamic>;
} catch (e) {
return null;
}
}
return null;
}
/// 保存用户登录状态
static Future<void> saveUserLogin(Map<String, dynamic> userData) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('vote_logged_in', true);
await prefs.setString('vote_user', jsonEncode(userData));
}
/// 清除用户登录状态
static Future<void> clearUserLogin() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('vote_logged_in');
await prefs.remove('vote_user');
}
/// 用户登录
static Future<VoteApiResponse> 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<VoteApiResponse> register({
required String username,
required String password,
String? userIdentifier,
}) async {
final data = <String, dynamic>{
'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<VoteListResponse> 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<VoteDetailResponse> 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<VoteApiResponse> submitVote({
required int topicId,
required List<int> 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<VoteResultResponse> 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<VoteListResponse> 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<String> 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<String?>(
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<String, dynamic> json) {
return VoteApiResponse(
code: json['code'] ?? 0,
message: json['msg'] ?? '',
data: json['data'],
);
}
bool get isSuccess => code == 0;
}
/// 投票列表响应
class VoteListResponse {
final List<VoteItem> list;
final VotePagination pagination;
VoteListResponse({required this.list, required this.pagination});
factory VoteListResponse.fromJson(Map<String, dynamic> json) {
final data = json['data'] as Map<String, dynamic>? ?? {};
final listRaw = data['list'] as List? ?? [];
final list = listRaw.map((item) => VoteItem.fromJson(item)).toList();
final pagination = VotePagination.fromJson(
data['pagination'] as Map<String, dynamic>? ?? {},
);
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<String, dynamic> 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<String, dynamic> 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<VoteOption> options;
final bool hasVoted;
final List<int> userVotes;
final bool canVote;
VoteDetailResponse({
required this.vote,
required this.options,
required this.hasVoted,
required this.userVotes,
required this.canVote,
});
factory VoteDetailResponse.fromJson(Map<String, dynamic> json) {
final data = json['data'] as Map<String, dynamic>? ?? {};
final voteRaw = data['vote'] as Map<String, dynamic>? ?? {};
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<String, dynamic> 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<VoteResultOption> options;
final int totalVotes;
final bool hasVoted;
final List<int> userVotes;
VoteResultResponse({
required this.vote,
required this.options,
required this.totalVotes,
required this.hasVoted,
required this.userVotes,
});
factory VoteResultResponse.fromJson(Map<String, dynamic> json) {
final data = json['data'] as Map<String, dynamic>? ?? {};
final voteRaw = data['vote'] as Map<String, dynamic>? ?? {};
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<String, dynamic> 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,
);
}
}