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,218 @@
/// 时间: 2025-03-21
/// 功能: HTTP客户端工具类使用纯Dart dio库
/// 介绍: 提供统一的HTTP请求方法支持GET、POST等请求用于项目中的网络请求
/// 最新变化: 移除CORS代理需后台服务器配置CORS响应头
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
class HttpClient {
static const String _baseUrl = 'https://yy.vogov.cn/api/';
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 Dio _dio = Dio(_options);
/// 添加调试日志
static void _debugLog(String message) {
if (kDebugMode) {
print('HttpClient: $message');
}
}
/// GET请求
static Future<HttpResponse> get(
String path, {
Map<String, dynamic>? queryParameters,
Map<String, String>? headers,
Duration? timeout,
}) async {
return _request(
'GET',
path,
queryParameters: queryParameters,
headers: headers,
timeout: timeout,
);
}
/// POST请求
static Future<HttpResponse> post(
String path, {
Map<String, dynamic>? data,
Map<String, String>? headers,
Duration? timeout,
}) async {
return _request(
'POST',
path,
data: data,
headers: headers,
timeout: timeout,
);
}
/// 通用请求方法
static Future<HttpResponse> _request(
String method,
String path, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? data,
Map<String, String>? headers,
Duration? timeout,
}) async {
try {
final url = '$_baseUrl$path';
_debugLog('请求 $method $url');
if (queryParameters != null) {
_debugLog('查询参数: $queryParameters');
}
final options = Options(
method: method,
headers: headers != null
? {..._options.headers!, ...headers}
: _options.headers,
);
if (timeout != null) {
options.connectTimeout = timeout;
options.receiveTimeout = timeout;
options.sendTimeout = timeout;
}
Response response;
if (method.toUpperCase() == 'GET') {
response = await _dio.get(
url,
queryParameters: queryParameters,
options: options,
);
} else if (method.toUpperCase() == 'POST') {
response = await _dio.post(
url,
data: data,
queryParameters: queryParameters,
options: options,
);
} else {
throw UnsupportedError('HTTP method $method is not supported');
}
_debugLog('响应状态: ${response.statusCode}');
_debugLog('响应数据: ${response.data}');
return HttpResponse(
statusCode: response.statusCode ?? 0,
body: response.data is String
? response.data
: json.encode(response.data),
headers: response.headers.map.cast<String, String>(),
);
} on DioException catch (e) {
_debugLog('Dio异常: ${e.type} - ${e.message}');
String message;
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
message = '请求超时,请检查网络连接';
break;
case DioExceptionType.connectionError:
message = '网络连接失败,请检查网络设置';
break;
case DioExceptionType.badResponse:
message = '服务器错误: ${e.response?.statusCode} - ${e.response?.data}';
break;
default:
message = '请求失败: ${e.message}';
}
throw HttpException(message);
} catch (e) {
_debugLog('未知异常: $e');
throw HttpException('请求失败:$e');
}
}
}
/// HTTP响应类
class HttpResponse {
final int statusCode;
final String body;
final Map<String, String> headers;
HttpResponse({
required this.statusCode,
required this.body,
required this.headers,
});
/// 是否成功 (2xx状态码)
bool get isSuccess => statusCode >= 200 && statusCode < 300;
/// 解析JSON响应
Map<String, dynamic> get jsonData {
try {
return json.decode(body) as Map<String, dynamic>;
} catch (e) {
throw FormatException('Invalid JSON response: $body');
}
}
/// 获取响应消息
String get message {
if (isSuccess) {
try {
return jsonData['msg'] ?? 'Success';
} catch (e) {
return 'Success';
}
} else {
return body;
}
}
/// 获取响应数据
dynamic get data {
if (isSuccess) {
return jsonData['data'];
}
return null;
}
/// 获取响应代码
int get code {
if (isSuccess) {
try {
return jsonData['code'] ?? 0;
} catch (e) {
return 0;
}
}
return statusCode;
}
}
/// HTTP异常类
class HttpException implements Exception {
final String message;
const HttpException(this.message);
@override
String toString() => 'HttpException: $message';
}

View File

@@ -0,0 +1,350 @@
/// 时间: 2026-03-22
/// 功能: 诗词API服务
/// 介绍: 专门处理诗词相关的API请求包括获取诗词、点赞、搜索等功能
/// 最新变化: 新增 search.php 搜索接口(与 API_DOCUMENTATION.md 一致)
import 'http_client.dart';
import 'package:flutter/foundation.dart';
class PoetryApi {
static const String _endpoint = 'pms.php';
/// 全文搜索(见 lib/services/API_DOCUMENTATION.md 第二节)
static const String _searchEndpoint = 'searchs.php';
/// 获取随机诗词
static Future<PoetryResponse> getRandomPoetry({
String? dynasty,
String? tag,
}) async {
final queryParams = <String, dynamic>{};
if (dynasty != null && dynasty.isNotEmpty) {
queryParams['dyn'] = dynasty;
}
if (tag != null && tag.isNotEmpty) {
queryParams['tag'] = tag;
}
final response = await HttpClient.get(_endpoint, queryParameters: queryParams);
if (!response.isSuccess) {
throw HttpException('获取诗词失败1: ${response.message}');
}
final jsonData = response.jsonData;
if (jsonData['code'] != 0) {
throw HttpException(jsonData['msg'] ?? '获取诗词失败2');
}
return PoetryResponse.fromJson(jsonData);
}
/// 获取指定ID的诗词
static Future<PoetryResponse> getPoetryById(int id) async {
final response = await HttpClient.get(
_endpoint,
queryParameters: {'id': id.toString()},
);
if (!response.isSuccess) {
throw HttpException('获取诗词失败3: ${response.message}');
}
final jsonData = response.jsonData;
if (jsonData['code'] != 0) {
throw HttpException(jsonData['msg'] ?? '获取诗词失败4');
}
return PoetryResponse.fromJson(jsonData);
}
/// 点赞或取消点赞诗词
static Future<PoetryResponse> toggleLike(int id) async {
final response = await HttpClient.get(
_endpoint,
queryParameters: {
'id': id.toString(),
'like': '', // 无值参数
},
);
if (!response.isSuccess) {
throw HttpException('点赞失败: ${response.message}');
}
final jsonData = response.jsonData;
if (jsonData['code'] != 0) {
throw HttpException(jsonData['msg'] ?? '点赞失败');
}
return PoetryResponse.fromJson(jsonData);
}
/// 直接点赞指定诗词通过lid参数
static Future<PoetryResponse> likePoetry(int lid) async {
final response = await HttpClient.get(
_endpoint,
queryParameters: {'lid': lid.toString()},
);
if (!response.isSuccess) {
throw HttpException('点赞失败: ${response.message}');
}
final jsonData = response.jsonData;
if (jsonData['code'] != 0) {
throw HttpException(jsonData['msg'] ?? '点赞失败');
}
return PoetryResponse.fromJson(jsonData);
}
/// 按朝代获取诗词
static Future<PoetryResponse> getPoetryByDynasty(String dynasty) async {
return getRandomPoetry(dynasty: dynasty);
}
/// 按标签获取诗词
static Future<PoetryResponse> getPoetryByTag(String tag) async {
return getRandomPoetry(tag: tag);
}
/// 按朝代和标签获取诗词
static Future<PoetryResponse> getPoetryByDynastyAndTag(String dynasty, String tag) async {
return getRandomPoetry(dynasty: dynasty, tag: tag);
}
/// 诗词搜索GET `searchs.php`keyword、fields、page、size
static Future<SearchPoetryResult> searchPoetry({
required String q,
String field = '',
int page = 1,
int limit = 20,
}) async {
final keyword = q.trim();
final response = await HttpClient.get(
_searchEndpoint,
queryParameters: {
'keyword': keyword,
if (field.isNotEmpty) 'fields': field,
'page': page.toString(),
'size': limit.toString(),
},
);
if (!response.isSuccess) {
throw HttpException('搜索失败: ${response.message}');
}
final jsonData = response.jsonData;
if (jsonData['code'] != 0) {
throw HttpException(jsonData['msg']?.toString() ?? '搜索失败');
}
final raw = jsonData['data'];
if (raw is! Map<String, dynamic>) {
return SearchPoetryResult(
total: 0,
page: page,
limit: limit,
list: const [],
);
}
final listRaw = raw['results'];
final list = <PoetryData>[];
if (listRaw is List) {
for (final item in listRaw) {
if (item is Map<String, dynamic>) {
list.add(PoetryData.fromJson(item));
} else if (item is Map) {
list.add(PoetryData.fromJson(Map<String, dynamic>.from(item)));
}
}
}
// 从 pagination 中获取分页信息
final pagination = raw['pagination'] as Map<String, dynamic>? ?? {};
final totalCount = int.tryParse(pagination['total_count']?.toString() ?? '0') ?? 0;
final currentPage = int.tryParse(pagination['current_page']?.toString() ?? '$page') ?? page;
final pageSize = int.tryParse(pagination['page_size']?.toString() ?? '$limit') ?? limit;
return SearchPoetryResult(
total: totalCount,
page: currentPage,
limit: pageSize,
list: list,
);
}
}
/// 搜索结果分页数据(对应 searchs.php 返回 data
class SearchPoetryResult {
final int total;
final int page;
final int limit;
final List<PoetryData> list;
const SearchPoetryResult({
required this.total,
required this.page,
required this.limit,
required this.list,
});
bool get hasNextPage => page * limit < total;
}
/// 诗词响应数据模型
class PoetryResponse {
final int code;
final String message;
final PoetryData? data;
PoetryResponse({
required this.code,
required this.message,
this.data,
});
factory PoetryResponse.fromJson(Map<String, dynamic> json) {
return PoetryResponse(
code: json['code'] ?? 0,
message: json['msg'] ?? '',
data: json['data'] != null ? PoetryData.fromJson(json['data']) : null,
);
}
Map<String, dynamic> toJson() {
return {
'code': code,
'msg': message,
'data': data?.toJson(),
};
}
}
/// 诗词数据模型
class PoetryData {
final int id;
final String name;// 精选诗句
final String alias; // 朝代
final String keywords; // 标签
final String introduce; // 译文/介绍
final String drtime; // 原文
final int like; // 点赞数
final String url; // 诗人和<标题>
final int tui; // 是否推荐
final int star; // 星级
final int hitsTotal; // 总浏览数
final int hitsMonth; // 月浏览数
final int hitsDay; // 日浏览数
final String date; // 最后统计日期
final String datem; // 最后统计月份
final String time; // 收录时间
final String createTime; // 创建时间
final String updateTime; // 更新时间
PoetryData({
required this.id,
required this.name,
required this.alias,
required this.keywords,
required this.introduce,
required this.drtime,
required this.like,
required this.url,
required this.tui,
required this.star,
required this.hitsTotal,
required this.hitsMonth,
required this.hitsDay,
required this.date,
required this.datem,
required this.time,
required this.createTime,
required this.updateTime,
});
factory PoetryData.fromJson(Map<String, dynamic> json) {
// 添加调试信息
if (kDebugMode) {
print('PoetryData.fromJson: 输入JSON = $json');
}
try {
final poetryData = PoetryData(
id: int.tryParse(json['id'].toString()) ?? 0,
name: json['name']?.toString() ?? '',
alias: json['alias']?.toString() ?? '',
keywords: json['keywords']?.toString() ?? '',
introduce: json['introduce']?.toString() ?? '',
drtime: json['drtime']?.toString() ?? '',
like: int.tryParse(json['like'].toString()) ?? 0,
url: json['url']?.toString() ?? '',
tui: int.tryParse(json['tui'].toString()) ?? 0,
star: int.tryParse(json['star'].toString()) ?? 0,
hitsTotal: int.tryParse(json['hits_total'].toString()) ?? 0,
hitsMonth: int.tryParse(json['hits_month'].toString()) ?? 0,
hitsDay: int.tryParse(json['hits_day'].toString()) ?? 0,
date: json['date']?.toString() ?? '',
datem: json['datem']?.toString() ?? '',
time: json['time']?.toString() ?? '',
createTime: json['create_time']?.toString() ?? '',
updateTime: json['update_time']?.toString() ?? '',
);
if (kDebugMode) {
print('PoetryData.fromJson: 解析成功');
}
return poetryData;
} catch (e) {
if (kDebugMode) {
print('PoetryData.fromJson: 解析失败 - $e');
}
rethrow;
}
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'alias': alias,
'keywords': keywords,
'introduce': introduce,
'drtime': drtime,
'like': like,
'url': url,
'tui': tui,
'star': star,
'hits_total': hitsTotal,
'hits_month': hitsMonth,
'hits_day': hitsDay,
'date': date,
'datem': datem,
'time': time,
'create_time': createTime,
'update_time': updateTime,
};
}
/// 获取标签列表
List<String> get keywordList {
if (keywords.isEmpty) return [];
return keywords.split(',').map((k) => k.trim()).where((k) => k.isNotEmpty).toList();
}
/// 生成星级显示
String get starDisplay {
if (star <= 0) return '';
if (star >= 5) return '🌟';
return '';
}
/// 是否为推荐内容
bool get isRecommended => tui == 1;
}

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,
);
}
}