Initial commit: Flutter 无书应用项目

This commit is contained in:
Developer
2026-03-30 02:35:31 +08:00
commit 9175ff9905
566 changed files with 103261 additions and 0 deletions

View File

@@ -0,0 +1,584 @@
/// 时间: 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:flutter/foundation.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 void _debugLog(String message) {
if (kDebugMode) {
print('VoteApi: $message');
}
}
static Future<Map<String, dynamic>> _get(
String path, {
Map<String, dynamic>? queryParameters,
}) async {
try {
final url = '$_baseUrl$path';
_debugLog('GET $url');
if (queryParameters != null) {
_debugLog('查询参数: $queryParameters');
}
final response = await _dio.get(url, queryParameters: queryParameters);
_debugLog('响应: ${response.data}');
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
_debugLog('Dio异常: ${e.type} - ${e.message}');
throw Exception('请求失败: ${e.message}');
} catch (e) {
_debugLog('未知异常: $e');
throw Exception('请求失败: $e');
}
}
static Future<Map<String, dynamic>> _post(
String path, {
Map<String, dynamic>? data,
}) async {
try {
final url = '$_baseUrl$path';
_debugLog('POST $url');
if (data != null) {
_debugLog('请求数据: $data');
}
final response = await _dio.post(url, data: data);
_debugLog('响应: ${response.data}');
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
_debugLog('Dio异常: ${e.type} - ${e.message}');
throw Exception('请求失败: ${e.message}');
} catch (e) {
_debugLog('未知异常: $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,
);
}
}