chore: prepare yanting monorepo handoff
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
typedef JsonMap = Map<String, dynamic>;
|
||||
|
||||
String asString(Object? value, [String fallback = '']) =>
|
||||
value == null ? fallback : value.toString();
|
||||
|
||||
int asInt(Object? value, [int fallback = 0]) {
|
||||
if (value is int) return value;
|
||||
if (value is num) return value.round();
|
||||
return int.tryParse(asString(value)) ?? fallback;
|
||||
}
|
||||
|
||||
bool asBool(Object? value) => value == true;
|
||||
|
||||
List<String> asStringList(Object? value) {
|
||||
if (value is List) return value.map((item) => item.toString()).toList();
|
||||
return const [];
|
||||
}
|
||||
|
||||
JsonMap asMap(Object? value) {
|
||||
if (value is Map<String, dynamic>) return value;
|
||||
if (value is Map) return value.map((key, val) => MapEntry(key.toString(), val));
|
||||
return const {};
|
||||
}
|
||||
|
||||
List<JsonMap> asMapList(Object? value) {
|
||||
if (value is List) return value.map(asMap).where((item) => item.isNotEmpty).toList();
|
||||
return const [];
|
||||
}
|
||||
|
||||
String formatDate(String? iso) {
|
||||
if (iso == null || iso.isEmpty) return '';
|
||||
final parsed = DateTime.tryParse(iso);
|
||||
if (parsed == null) return iso;
|
||||
return '${parsed.year}年${parsed.month}月${parsed.day}日';
|
||||
}
|
||||
|
||||
String formatDuration(int seconds) {
|
||||
final safe = seconds < 0 ? 0 : seconds;
|
||||
final minutes = safe ~/ 60;
|
||||
final secs = safe % 60;
|
||||
return '$minutes:${secs.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
class Institution {
|
||||
const Institution({
|
||||
required this.id,
|
||||
required this.nameCn,
|
||||
this.nameEn = '',
|
||||
this.institutionType = '',
|
||||
this.sourceTier = '',
|
||||
this.websiteUrl = '',
|
||||
this.coveredTopics = const [],
|
||||
this.reportCount = 0,
|
||||
this.latestReportAt,
|
||||
this.credibilityNote = '',
|
||||
this.introCn = '',
|
||||
this.recentReports = const [],
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String nameCn;
|
||||
final String nameEn;
|
||||
final String institutionType;
|
||||
final String sourceTier;
|
||||
final String websiteUrl;
|
||||
final List<String> coveredTopics;
|
||||
final int reportCount;
|
||||
final String? latestReportAt;
|
||||
final String credibilityNote;
|
||||
final String introCn;
|
||||
final List<ReportCardModel> recentReports;
|
||||
|
||||
factory Institution.fromJson(JsonMap json) {
|
||||
return Institution(
|
||||
id: asString(json['institution_id']),
|
||||
nameCn: asString(json['name_cn']),
|
||||
nameEn: asString(json['name_en']),
|
||||
institutionType: asString(json['institution_type']),
|
||||
sourceTier: asString(json['source_tier']),
|
||||
websiteUrl: asString(json['website_url']),
|
||||
coveredTopics: asStringList(json['covered_topics']),
|
||||
reportCount: asInt(json['report_count']),
|
||||
latestReportAt: json['latest_report_at']?.toString(),
|
||||
credibilityNote: asString(json['credibility_note']),
|
||||
introCn: asString(json['intro_cn']),
|
||||
recentReports: asMapList(json['recent_reports'])
|
||||
.map(ReportCardModel.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReportCardModel {
|
||||
const ReportCardModel({
|
||||
required this.id,
|
||||
required this.titleCn,
|
||||
required this.institution,
|
||||
this.subtitleCn = '',
|
||||
this.oneLiner = '',
|
||||
this.topics = const [],
|
||||
this.releasedAt,
|
||||
this.hasAudio = false,
|
||||
this.interpretationLabel = '研报解读',
|
||||
this.sourceTier = '',
|
||||
this.cacheVersion = '',
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String titleCn;
|
||||
final String subtitleCn;
|
||||
final String oneLiner;
|
||||
final Institution institution;
|
||||
final List<String> topics;
|
||||
final String? releasedAt;
|
||||
final bool hasAudio;
|
||||
final String interpretationLabel;
|
||||
final String sourceTier;
|
||||
final String cacheVersion;
|
||||
|
||||
factory ReportCardModel.fromJson(JsonMap json) {
|
||||
final institution = Institution.fromJson(asMap(json['institution']));
|
||||
return ReportCardModel(
|
||||
id: asString(json['report_id']),
|
||||
titleCn: asString(json['title_cn']),
|
||||
subtitleCn: asString(json['subtitle_cn']),
|
||||
oneLiner: asString(json['one_liner']),
|
||||
institution: institution,
|
||||
topics: asStringList(json['topics']),
|
||||
releasedAt: json['released_at']?.toString(),
|
||||
hasAudio: asBool(json['has_audio']),
|
||||
interpretationLabel: asString(json['interpretation_label'], '研报解读'),
|
||||
sourceTier: asString(json['source_tier']),
|
||||
cacheVersion: asString(json['cache_version']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AudioItem {
|
||||
const AudioItem({
|
||||
required this.audioId,
|
||||
required this.reportId,
|
||||
required this.titleCn,
|
||||
required this.reportTitleCn,
|
||||
required this.durationSec,
|
||||
required this.institution,
|
||||
this.releasedAt,
|
||||
this.cacheVersion = '',
|
||||
});
|
||||
|
||||
final String audioId;
|
||||
final String reportId;
|
||||
final String titleCn;
|
||||
final String reportTitleCn;
|
||||
final int durationSec;
|
||||
final Institution institution;
|
||||
final String? releasedAt;
|
||||
final String cacheVersion;
|
||||
|
||||
factory AudioItem.fromJson(JsonMap json) {
|
||||
return AudioItem(
|
||||
audioId: asString(json['audio_id']),
|
||||
reportId: asString(json['report_id']),
|
||||
titleCn: asString(json['title_cn']),
|
||||
reportTitleCn: asString(json['report_title_cn'], asString(json['title_cn'])),
|
||||
durationSec: asInt(json['duration_sec']),
|
||||
institution: Institution.fromJson(asMap(json['institution'])),
|
||||
releasedAt: json['released_at']?.toString(),
|
||||
cacheVersion: asString(json['cache_version']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayModule {
|
||||
const DisplayModule({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.titleCn,
|
||||
required this.renderMode,
|
||||
this.layer = '',
|
||||
this.hasDetailPage = false,
|
||||
this.sortOrder = 0,
|
||||
this.content = const {},
|
||||
this.preview = const {},
|
||||
this.contentRef = '',
|
||||
this.contentEtag = '',
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String type;
|
||||
final String titleCn;
|
||||
final String layer;
|
||||
final String renderMode;
|
||||
final bool hasDetailPage;
|
||||
final int sortOrder;
|
||||
final JsonMap content;
|
||||
final JsonMap preview;
|
||||
final String contentRef;
|
||||
final String contentEtag;
|
||||
|
||||
factory DisplayModule.fromJson(JsonMap json) {
|
||||
return DisplayModule(
|
||||
id: asString(json['module_id']),
|
||||
type: asString(json['type']),
|
||||
titleCn: asString(json['title_cn'], asString(json['type'])),
|
||||
layer: asString(json['layer']),
|
||||
renderMode: asString(json['render_mode'], 'inline'),
|
||||
hasDetailPage: asBool(json['has_detail_page']),
|
||||
sortOrder: asInt(json['sort_order']),
|
||||
content: asMap(json['content']),
|
||||
preview: asMap(json['preview']),
|
||||
contentRef: asString(json['content_ref']),
|
||||
contentEtag: asString(json['content_etag']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReportDetail {
|
||||
const ReportDetail({
|
||||
required this.id,
|
||||
required this.titleCn,
|
||||
required this.institution,
|
||||
this.subtitleCn = '',
|
||||
this.originalTitle = '',
|
||||
this.oneLiner = '',
|
||||
this.source = const {},
|
||||
this.topics = const [],
|
||||
this.hasAudio = false,
|
||||
this.interpretationLabel = '研报解读',
|
||||
this.riskDisclaimer = '',
|
||||
this.releasedAt,
|
||||
this.cacheVersion = '',
|
||||
this.modules = const [],
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String titleCn;
|
||||
final String subtitleCn;
|
||||
final String originalTitle;
|
||||
final String oneLiner;
|
||||
final Institution institution;
|
||||
final JsonMap source;
|
||||
final List<String> topics;
|
||||
final bool hasAudio;
|
||||
final String interpretationLabel;
|
||||
final String riskDisclaimer;
|
||||
final String? releasedAt;
|
||||
final String cacheVersion;
|
||||
final List<DisplayModule> modules;
|
||||
|
||||
factory ReportDetail.fromJson(JsonMap json) {
|
||||
return ReportDetail(
|
||||
id: asString(json['report_id']),
|
||||
titleCn: asString(json['title_cn']),
|
||||
subtitleCn: asString(json['subtitle_cn']),
|
||||
originalTitle: asString(json['original_title']),
|
||||
oneLiner: asString(json['one_liner']),
|
||||
institution: Institution.fromJson(asMap(json['institution'])),
|
||||
source: asMap(json['source']),
|
||||
topics: asStringList(json['topics']),
|
||||
hasAudio: asBool(json['has_audio']),
|
||||
interpretationLabel: asString(json['interpretation_label'], '研报解读'),
|
||||
riskDisclaimer: asString(json['risk_disclaimer']),
|
||||
releasedAt: json['released_at']?.toString(),
|
||||
cacheVersion: asString(json['cache_version']),
|
||||
modules: asMapList(json['modules']).map(DisplayModule.fromJson).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
ReportCardModel asCard() {
|
||||
return ReportCardModel(
|
||||
id: id,
|
||||
titleCn: titleCn,
|
||||
subtitleCn: subtitleCn,
|
||||
oneLiner: oneLiner,
|
||||
institution: institution,
|
||||
topics: topics,
|
||||
releasedAt: releasedAt,
|
||||
hasAudio: hasAudio,
|
||||
interpretationLabel: interpretationLabel,
|
||||
sourceTier: asString(source['source_tier']),
|
||||
cacheVersion: cacheVersion,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleDetail {
|
||||
const ModuleDetail({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.titleCn,
|
||||
this.content = const {},
|
||||
this.contentEtag = '',
|
||||
this.cacheVersion = '',
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String type;
|
||||
final String titleCn;
|
||||
final JsonMap content;
|
||||
final String contentEtag;
|
||||
final String cacheVersion;
|
||||
|
||||
factory ModuleDetail.fromJson(JsonMap json) {
|
||||
return ModuleDetail(
|
||||
id: asString(json['module_id']),
|
||||
type: asString(json['type']),
|
||||
titleCn: asString(json['title_cn'], asString(json['type'])),
|
||||
content: asMap(json['content']),
|
||||
contentEtag: asString(json['content_etag']),
|
||||
cacheVersion: asString(json['cache_version']),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user