feat(app): 搜索改为调用后端并渲染卡片结果
This commit is contained in:
1 parent
730e38622f
commit
ade0cc3566
13 files changed
+424
-20
No files matched your search
@@ -65,6 +65,7 @@ class Drama {
|
||||
this.freeEpisodes = 0,
|
||||
this.isHot = false,
|
||||
this.viewCount = 0,
|
||||
this.language = '',
|
||||
this.detailLoaded = false,
|
||||
});
|
||||
|
||||
@@ -80,6 +81,9 @@ class Drama {
|
||||
final bool isHot;
|
||||
final int viewCount;
|
||||
|
||||
/// 卡片语种(如 zh-CN / id),用于剧场语言筛选。
|
||||
final String language;
|
||||
|
||||
/// 详情接口是否已经取过完整剧集列表。
|
||||
/// 列表卡片只带一集可播预览,需要完整剧集时再进详情。
|
||||
final bool detailLoaded;
|
||||
@@ -104,6 +108,7 @@ class Drama {
|
||||
isHot: json['isHot'] == true,
|
||||
// viewCount 后端是 Long,按字符串下发(见 json_numbers.dart)
|
||||
viewCount: jsonToInt(json['viewCount']),
|
||||
language: json['language'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,6 +125,7 @@ class Drama {
|
||||
freeEpisodes: (json['freeEpisodes'] as num?)?.toInt() ?? 0,
|
||||
isHot: json['isHot'] == true,
|
||||
viewCount: jsonToInt(json['viewCount']),
|
||||
language: json['language'] as String? ?? '',
|
||||
detailLoaded: true,
|
||||
episodes: (json['episodes'] as List? ?? const [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'drama.dart';
|
||||
import 'json_numbers.dart';
|
||||
|
||||
/// 搜索分页结果:`GET /app/search` 的 `{total, list}`。
|
||||
class SearchResult {
|
||||
const SearchResult({this.total = 0, this.list = const []});
|
||||
|
||||
final int total;
|
||||
final List<Drama> list;
|
||||
|
||||
factory SearchResult.fromJson(Map<String, dynamic> json) {
|
||||
return SearchResult(
|
||||
total: jsonToInt(json['total']),
|
||||
list: (json['list'] as List? ?? const [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(Drama.fromCardJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import '../models/drama.dart';
|
||||
import '../models/home_feed.dart';
|
||||
import '../models/search_result.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
/// 客户端目录接口,对应后端 `/app/**`。
|
||||
@@ -16,6 +17,23 @@ class CatalogApi {
|
||||
return HomeFeed.fromJson(_asMap(await _client.get('/app/discover/feed')));
|
||||
}
|
||||
|
||||
/// 搜索已上线短剧:按标题/简介模糊匹配,分页返回卡片列表。
|
||||
Future<SearchResult> search(
|
||||
String keyword, {
|
||||
int pageIndex = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
final data = await _client.get(
|
||||
'/app/search',
|
||||
query: {
|
||||
'keyword': keyword,
|
||||
'pageIndex': '$pageIndex',
|
||||
'pageSize': '$pageSize',
|
||||
},
|
||||
);
|
||||
return SearchResult.fromJson(_asMap(data));
|
||||
}
|
||||
|
||||
Future<Drama> dramaDetail(String dramaId) async {
|
||||
return Drama.fromDetailJson(
|
||||
_asMap(await _client.get('/app/dramas/$dramaId')),
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import '../models/search_result.dart';
|
||||
import '../remote/catalog_api.dart';
|
||||
|
||||
/// 搜索数据来源:只调用后端 `/app/search`,不在客户端做本地过滤。
|
||||
abstract interface class SearchRepository {
|
||||
Future<SearchResult> search(
|
||||
String keyword, {
|
||||
int pageIndex = 1,
|
||||
int pageSize = 20,
|
||||
});
|
||||
}
|
||||
|
||||
class HttpSearchRepository implements SearchRepository {
|
||||
const HttpSearchRepository(this._api);
|
||||
|
||||
final CatalogApi _api;
|
||||
|
||||
@override
|
||||
Future<SearchResult> search(
|
||||
String keyword, {
|
||||
int pageIndex = 1,
|
||||
int pageSize = 20,
|
||||
}) => _api.search(keyword, pageIndex: pageIndex, pageSize: pageSize);
|
||||
}
|
||||
Reference in new issue
Block a user