344 lines
9.2 KiB
Dart
344 lines
9.2 KiB
Dart
/// 时间: 2026-03-22
|
||
/// 功能: 诗词API服务
|
||
/// 介绍: 专门处理诗词相关的API请求,包括获取诗词、点赞、搜索等功能
|
||
/// 最新变化: 新增 search.php 搜索接口(与 API_DOCUMENTATION.md 一致)
|
||
|
||
import 'http_client.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) {
|
||
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() ?? '',
|
||
);
|
||
|
||
return poetryData;
|
||
} catch (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;
|
||
}
|