初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
@@ -0,0 +1,605 @@
import 'package:hgdj/assets_tool/images.dart';
import '../../alert/vip_guide/guide_manager.dart';
import '../../config/config.dart';
import '../../hj_page/ai/ai_sub_type/ai_function_logic.dart';
import '../../hj_page/main_page/provider/msg_provider.dart';
import '../../hj_page/pre_sale/pre_sale_model.dart';
import 'ads_model.dart';
/// 预售/活动状态
class AdvanceStatus {
bool? activityPopUp; //预售活动是否首页弹窗
bool? activityStatus; //是否开启
bool? balancePayment; //是否有尾款支付
String? oid;
PrivilegeLimit? privilegeLimit; //预付权益
AdvanceStatus.fromJson(dynamic json) {
activityStatus = json['activityStatus'];
balancePayment = json['balancePayment'];
activityPopUp = json['activityPopUp'];
oid = json['oid'];
privilegeLimit = json['privilegeLimit'] != null
? PrivilegeLimit.fromJson(json['privilegeLimit'])
: null;
}
}
/// 预付权益限制与剩余次数
class PrivilegeLimit {
bool? hasLimit; //是否有权益限制
Remain? remain;
PrivilegeLimit({this.hasLimit, this.remain});
PrivilegeLimit.fromJson(Map<String, dynamic> json) {
hasLimit = json['hasLimit'];
remain = json['remain'] != null ? Remain.fromJson(json['remain']) : null;
}
}
/// 今日各项功能剩余次数
class Remain {
int? todayAiUndressCount; //今日剩余ai脱衣次数
int? todayCoinVideoCount; //今日剩余金币视频观看次数
int? todayDownloadCount; //今日剩余下载次数
Remain(
{this.todayAiUndressCount,
this.todayCoinVideoCount,
this.todayDownloadCount});
Remain.fromJson(Map<String, dynamic> json) {
todayAiUndressCount = json['todayAiUndressCount'];
todayCoinVideoCount = json['todayCoinVideoCount'];
todayDownloadCount = json['todayDownloadCount'];
}
void reduceVideoCoinCount() {
todayCoinVideoCount = (todayCoinVideoCount ?? 0) - 1;
}
}
/// 同一份 bannerJump 下按位置取:key '1' 播放页,'2' 全民代理页
class BannerJump {
AdditionalProp? additionalProp;
BannerJump({this.additionalProp});
BannerJump.fromJson(Map<String, dynamic> json, {String key = '1'}) {
additionalProp =
json[key] != null ? AdditionalProp.fromJson(json[key]) : null;
}
}
class AdditionalProp {
String? id;
String? title; //标题
String? banner; //BANNER图片
String? url; //跳转地址
int? position; //位置 1:视频播放器下方
String? startAt; //开始时间
String? endAt; //结束时间
int? countdownType;
AdditionalProp(
{this.id,
this.banner,
this.endAt,
this.position,
this.startAt,
this.title,
this.url,
this.countdownType});
AdditionalProp.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
banner = json['banner'];
url = json['url'];
position = json['position'];
startAt = json['startAt'];
endAt = json['endAt'];
countdownType = json['countdownType'];
}
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'banner': banner,
'url': url,
'position': position,
'startAt': startAt,
'endAt': endAt,
'countdownType': countdownType,
};
}
/// 全局配置:域名、广告、AI价格、活动、搜索等
class DomainSourceModel {
// ----- 域名 / 线路 / 版本 -----
List<String>? domain; //接口域名
List<Sourcelist>? sourceList;
List<CheckVersionInfo>? ver;
// ----- 广告 / 公告 -----
AdsBean? ads;
// ----- AI 功能价格 -----
String? aiUndressPrice; //AI脱衣价格
String? aiImageToVideoPrice; //图生视频价格
String? aiTextToImagePrice; //绘图价格
String? aiTextToNovelPrice; //小说价格
// ----- 活动 / 预售 / Banner -----
AdvanceStatus? advanceStatus;
AdvancePage? advancePage;
BannerJump? bannerJump; //限时Banner活动
BannerJump? proxyBannerJump;
// ----- 抽奖 -----
String? luckyDrawH5; //抽奖H5链接
String? luckyDrawIcon; //抽奖入口浮动图标
// ----- 搜索 -----
List<String>? hotSearchTerms; //搜索热门词
List<String>? searchHintWord; //搜索提示词
// ----- 暗网 VIP -----
String? darkWebVipId; //暗网vip卡id
String? darkWebVipName; //暗网vip卡名
String? shortDramaCardId; //短剧卡id(付费墙默认选中的会员卡)
List<String>? recommendVipIds;
// ----- 其他 -----
bool? broadcast; //是否展示直播模块
bool? storeIsOpen; //原味商店开关
List<JGAreaModel>? jgArea; //金刚区配置
static DomainSourceModel fromJson(Map<String, dynamic>? map) {
map ??= {};
final info = DomainSourceModel();
// 直接写入全局 Config 的配置项
Config.vipMark = map['vipMark'] ?? true;
Config.coinMark = map['coinMark'] ?? true;
Config.freeMark = map['freeMark'] ?? true;
Config.signIcon = map['signIcon'];
Config.darkWebEnable = map['darkWebEnable'] ?? true;
Config.darkWebImg = map['darkWebImg'];
Config.darkWebIcon = map['darkWebIcon'];
Config.darkWebIconName = map['darkWebIconName'];
Config.mineBg = map['personalCenterBackground'];
Config.entryPopupEnabled =
map['shortDramaEntryPopupEnabled'] ?? true; //短剧引导气泡,字段缺失当开启
MineMsgProvider().payTier =
PayTierModel.fromJson(map); //支付状态分层(各展示位图片/卡ID/倒计时)
GuideManager().update(map['paymentGuide']); //付费引导各场景开关(弹窗一律读缓存,不再临时查接口)
if (map['aiSwitchConf'] is List) {
Config.aiTypes = (map['aiSwitchConf'] as List)
.map((e) => AISwitchConf.fromJson(e))
.toList();
}
if (map.containsKey('bannerJumpList')) {
Config.bannerJumps = (map['bannerJumpList'] as List?)
?.map((o) => BannerJumpEntity.fromJson(o))
.toList() ??
[];
}
// 域名 / 线路 / 版本
if (map.containsKey('domain')) {
info.domain = (map['domain'] as List?)?.map((o) => o.toString()).toList();
}
if (map.containsKey('sourceList')) {
info.sourceList = (map['sourceList'] as List?)
?.map((o) => Sourcelist.fromJson(o))
.toList();
}
if (map.containsKey('ver')) {
info.ver = (map['ver'] as List?)
?.map((o) => CheckVersionInfo.fromMap(o))
.toList();
}
// 广告
if (map.containsKey('ads')) {
info.ads = AdsBean.fromMap(map['ads']);
}
// AI 价格
info.aiUndressPrice = map['aiUndressPrice']?.toString();
info.aiImageToVideoPrice = map['aiImageToVideoPrice']?.toString();
info.aiTextToImagePrice = map['aiTextToImagePrice']?.toString();
info.aiTextToNovelPrice = map['aiTextToNovelPrice']?.toString();
// 活动 / 预售 / Banner
if (map.containsKey('advancePage')) {
info.advancePage = AdvancePage.fromJson(map['advancePage']);
}
if (map.containsKey('advanceStatus')) {
info.advanceStatus = AdvanceStatus.fromJson(map['advanceStatus']);
}
info.bannerJump = map['bannerJump'] != null
? BannerJump.fromJson(map['bannerJump'])
: null;
info.proxyBannerJump = map['bannerJump'] != null
? BannerJump.fromJson(map['bannerJump'], key: '2')
: null;
// 抽奖
info.luckyDrawH5 = map['luckyDrawH5'];
info.luckyDrawIcon = map['luckyDrawIcon'];
// 搜索
if (map['hotSearchTerms'] is List) {
info.hotSearchTerms =
(map['hotSearchTerms'] as List).map((e) => e.toString()).toList();
}
if (map['searchHintWord'] is List) {
info.searchHintWord =
(map['searchHintWord'] as List).map((e) => e.toString()).toList();
}
// 暗网 VIP
info.darkWebVipId = map['darkWebVipId'];
info.darkWebVipName = map['darkWebVipName'];
info.shortDramaCardId = map['shortDramaCardId'];
if (map['recommendVipIds'] is List) {
info.recommendVipIds =
(map['recommendVipIds'] as List).map((e) => e.toString()).toList();
}
// 其他
info.broadcast = map['broadcast'] ?? false;
info.storeIsOpen = map['storeIsOpen'];
if (map['jgArea'] is List) {
info.jgArea = (map['jgArea'] as List?)
?.map((e) => JGAreaModel.fromJson(e))
.toList();
}
return info;
}
}
/// 跑马灯公告
class MarqueeModel {
String? content; //跑马灯内容
String? url; //跳转连接
bool? active; //激活状态
String? id; //公告id
int? type; //跑马灯类型,0:会员中心
int? position;
String? positionDesc;
static MarqueeModel fromMap(Map<String, dynamic>? map) {
map ??= {};
return MarqueeModel()
..content = map['content']
..url = map['url']
..active = map['active']
..id = map['id']
..type = map['type']
..position = map['position']
..positionDesc = map['positionDesc'];
}
}
/// 版本检测信息
class CheckVersionInfo {
String? description;
bool? forcedUpdate; //是否强制更新
String? platform;
String? url; //Android 的 apk 下载地址
String? iosUrl; //iOS 的 App Store 地址,点升级直接跳商店
String? verName;
int? code;
static CheckVersionInfo fromMap(Map<String, dynamic>? map) {
map ??= {};
return CheckVersionInfo()
..description = map['description']
..forcedUpdate = map['forcedUpdate']
..platform = map['platform']
..url = map['url']
..iosUrl = map['iosUrl']
..verName = map['verName']
..code = map['Code'];
}
}
/// 广告聚合:图片广告列表 + 文本公告
class AdsBean {
List<AdsInfoModel>? adsInfoList;
List<AnnounceInfoBean>? announInfo; //文本公告
///多个公告
List<AnnounceInfoBean>? announList;
static AdsBean fromMap(Map<String, dynamic>? map) {
map ??= {};
return AdsBean()
..announInfo = (map['announInfo'] as List?)
?.map((o) => AnnounceInfoBean.fromJson(o))
.toList()
..announList = (map['announList'] as List?)
?.map((o) => AnnounceInfoBean.fromJson(o))
.toList()
..adsInfoList = (map['adsInfoList'] as List?)
?.map((o) => AdsInfoModel.fromMap(o))
.toList();
}
Map toJson() => {
"announInfo": announInfo,
};
}
/// 金刚区单项
class JGAreaModel {
String? id;
String? name;
int? type;
String? img;
String? desc;
int? linkType;
String? linkUrl;
String? mid;
JGAreaModel(
{this.id,
this.name,
this.img,
this.desc,
this.linkType,
this.linkUrl,
this.type,
this.mid});
factory JGAreaModel.fromJson(Map<String, dynamic> json) => JGAreaModel(
id: json["id"],
name: json["name"],
img: json["img"],
desc: json["desc"],
linkType: json["link_type"],
linkUrl: json["link_url"],
type: json["type"],
mid: json["mid"],
);
Map<String, dynamic> toJson() => {
"id": id,
"name": name,
"img": img,
"desc": desc,
"link_type": linkType,
"link_url": linkUrl,
'mid': mid,
};
}
/// 线路源
class Sourcelist {
String? id;
List<Domain>? domain;
bool? isActive;
String? type;
Sourcelist({this.id, this.domain, this.isActive, this.type});
factory Sourcelist.fromJson(Map<String, dynamic> json) => Sourcelist(
id: json["id"],
domain:
List<Domain>.from(json["domain"].map((x) => Domain.fromJson(x))),
isActive: json["isActive"],
type: json["type"],
);
Map<String, dynamic> toJson() => {
"id": id,
"domain": List<dynamic>.from(domain!.map((x) => x.toJson())),
"isActive": isActive,
"type": type,
};
}
/// 域名节点(带权重)
class Domain {
int? weight;
String? url;
String? desc;
int? status;
Domain({this.weight, this.url, this.desc, this.status});
factory Domain.fromJson(Map<String, dynamic> json) => Domain(
weight: json["weight"],
url: json["url"],
desc: json["desc"],
status: json["status"],
);
Map<String, dynamic> toJson() => {
"weight": weight,
"url": url,
"desc": desc,
"status": status,
};
}
//AI 类型配置表:type → 名称/功能枚举/图标(存图标名,读取时拼 .aiPath)
class _AiTypeConf {
final String name;
final AiType type;
final String img;
const _AiTypeConf(this.name, this.type, this.img);
}
const _aiTypeConfMap = <int, _AiTypeConf>{
1: _AiTypeConf('AI脱衣', AiType.autoStrip, 'ai_address.png'),
2: _AiTypeConf('视频换脸', AiType.videoChangeFace, 'ai_video_face.png'),
3: _AiTypeConf('图片换脸', AiType.imageChangeFace, 'ai_pic_face.png'),
4: _AiTypeConf('图生视频', AiType.imageToVideo, 'ai_itv.png'),
5: _AiTypeConf('AI绘画', AiType.aiPaint, 'ai_draw.png'),
6: _AiTypeConf('AI小说', AiType.aiNovel, 'ai_novel_enter.png'),
7: _AiTypeConf('AI女友', AiType.aiMate, 'ai_mate_enter.png'),
};
/// AI 功能开关配置
class AISwitchConf {
int? type; //AI类型 1:AI脱衣 2:AI视频换脸 3:AI图片换脸 4:图生视频 5:文生图 6:AI小说
int? sort; //排序,越小越靠前
bool? isOpen; //是否开启
String? aiTypeName;
String? img; //图标(选中/未选中同一张,渲染时统一染白)
AiType? aiType;
AISwitchConf.fromJson(Map<String, dynamic> json) {
type = json['type'];
sort = json['sort'];
isOpen = json['isOpen'];
final conf = _aiTypeConfMap[type];
if (conf != null) {
aiTypeName = conf.name;
aiType = conf.type;
img = conf.img.aiPath;
}
}
}
/// 限时 Banner 跳转配置
class BannerJumpEntity {
String? id;
int? position;
String? url;
String? title;
String? startAt;
String? banner;
String? endAt;
int? countdownType;
BannerJumpEntity(
{this.title,
this.id,
this.position,
this.url,
this.startAt,
this.banner,
this.endAt,
this.countdownType});
BannerJumpEntity.fromJson(dynamic json) {
id = json['id'];
position = json['position'];
url = json['url'];
title = json['title'];
startAt = json['startAt'];
endAt = json['endAt'];
banner = json['banner'];
countdownType = json['countdownType'];
}
}
/// 支付状态分层(ping/domain 的 paymentStatusPopup 字段),用于付费流程的用户分层
enum PayTier {
newUnpay('new_unpay'), //新用户:注册≤1天且从未付费 → 推新人200卡(新人价+首购引导+紧迫感)
under7DayUnpay('under_7_day_unpay'), //未付费·注册1~7天 → 推300卡(限时优惠+紧迫感)
over7DayUnpay('over_7_day_unpay'), //未付费·注册>7天 → 推300卡(永久卡唤醒需求+信任背书)
over7DayNeedUpgrade(
'over_7_day_need_upgrade'), //低级会员需续费:注册>7天且已付费、未达最高等级(<5) → 推500至尊卡
normal('normal'), //正常用户:无特殊弹窗
unregistered('unregistered'); //未注册(未登录)
final String value;
const PayTier(this.value);
static PayTier? from(String? value) {
if (value == null || value.isEmpty) return null;
for (final e in PayTier.values) {
if (e.value == value) return e;
}
return null;
}
}
/// 支付状态弹窗配置(收敛 ping/domain 支付分层相关字段)
class PayTierModel {
PayTier? status; //用户分层
PayTierConfig? config; //各展示位配置(图片/卡ID/倒计时)
PayTierModel({this.status, this.config});
PayTierModel.fromJson(Map<String, dynamic>? json) {
json ??= {};
status = PayTier.from(json['paymentStatusPopup']);
// /ping/domain 配置在 paymentStatusPopupConfig 下;/ping/domain/refresh 平铺在外层,回退用 json 本身
config = PayTierConfig.fromJson(json['paymentStatusPopupConfig'] ?? json);
}
}
/// 支付状态弹窗各展示位配置(meTab/playPage 为图片链接;vipCard 卡IDlastDiscountTime 优惠截止时间)
/// 全站新人卡/分层优惠倒计时的唯一数据源,各展示位一律读这里,不再另开接口取时间
class PayTierConfig {
String? homePage; //首页弹窗图片链接
String? homePageFlot; //首页浮窗图片链接
String? playPage; //播放页图片链接
String? meTab; //me tab 图片链接
String? vipCard; //点击跳转的会员卡 ID
int? lastDiscountTime; //优惠截止 Unix 时间戳(秒),早于当前则不展示倒计时
PayTierConfig(
{this.homePage,
this.homePageFlot,
this.playPage,
this.meTab,
this.vipCard,
this.lastDiscountTime});
PayTierConfig.fromJson(Map<String, dynamic>? json) {
json ??= {};
homePage = json['homepage'];
homePageFlot = json['homepageFlot'];
playPage = json['playPage'];
meTab = json['meTab'];
vipCard = json['vipCard'];
lastDiscountTime = _parseEpochSec(json['lastDiscountTime']);
}
static const _minValidEpochSec =
946684800; //2000-01-01,用于挡掉后端零值时间 0001-01-01T00:00:00Z
//兼容 Unix 秒(int) / 数字字符串 / ISO 字符串(带 Z 或 +08:00 时区);零值哨兵一律归 null,避免负 epoch 流入业务
static int? _parseEpochSec(dynamic v) {
if (v == null) return null;
int? sec;
if (v is num) {
sec = v.toInt();
} else if (v is String) {
final s = v.trim();
if (s.isEmpty) return null;
sec = int.tryParse(s) ??
(DateTime.tryParse(s)?.millisecondsSinceEpoch ?? 0) ~/ 1000;
}
return (sec != null && sec >= _minValidEpochSec) ? sec : null;
}
/// 优惠剩余秒数(截止时刻 - 当前),≤0 表示已过期/不展示倒计时
int get discountRemainSec {
final end = lastDiscountTime ?? 0;
if (end <= 0) return 0;
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final r = end - now;
return r > 0 ? r : 0;
}
bool get hasDiscountCountdown => discountRemainSec > 0;
String get discountHour =>
(discountRemainSec ~/ 3600).toString().padLeft(2, '0');
String get discountMin =>
((discountRemainSec ~/ 60) % 60).toString().padLeft(2, '0');
String get discountSec => (discountRemainSec % 60).toString().padLeft(2, '0');
}