完善游客模式登录 并且新增设备管理

This commit is contained in:
2025-10-17 21:11:40 +08:00
parent 2333ad52a9
commit de9cf751f3
46 changed files with 4590 additions and 543 deletions
+20 -21
View File
@@ -3,21 +3,17 @@ abstract class Api {
/// 游客登录查看是否已经注册
static const String kr_isRegister = "/v1/app/auth/check";
/// 注册1024
static const String kr_register = "/v1/app/auth/register";
/// 注册
static const String kr_register = "/v1/auth/register";
/// 验证验证码
static const String kr_checkVerificationCode =
"/v1/common/check_verification_code";
static const String kr_checkVerificationCode = "/v1/auth/check-code";
/// 发送手机验证码
static const String kr_sendPhoneCode = "/v1/common/send_sms_code";
/// 发送邮箱验证码
static const String kr_sendEmailCode = "/v1/common/send_code";
/// 发送验证码(统一接口,支持邮箱和手机)
static const String kr_sendCode = "/v1/auth/send-code";
/// 登录接口
static const String kr_login = "/v1/app/auth/login";
static const String kr_login = "/v1/auth/login";
/// 设备登录(游客登录)
/// 参考 OmnOem 项目 ppanel.json 配置
@@ -29,8 +25,8 @@ abstract class Api {
/// 忘记密码-设置新密码
static const String kr_setNewPsdByForgetPsd = "/v1/app/auth/reset_password";
/// 节点信息
static const String kr_nodeList = "/v1/app/node/list";
/// 节点信息(包含试用/付费标志)
static const String kr_nodeList = "/v1/public/subscribe/node/list";
/// 获取用户订阅流量日志
static const String kr_nodeGroupList = "/v1/app/node/rule_group_list";
@@ -48,15 +44,15 @@ abstract class Api {
static const String kr_checkout = "/v1/app/order/checkout";
/// 获取可购买套餐
static const String kr_getPackageList = "/v1/app/subscribe/list";
static const String kr_getPackageList = "/v1/public/subscribe/list";
/// 获取用户已订阅套餐
/// 获取用户已订阅套餐(用于判断是否购买过)
static const String kr_getAlreadySubscribe =
"/v1/app/subscribe/user/already_subscribe";
"/v1/public/user/subscribe";
/// 获取用户可用订阅
/// 获取用户可用订阅(与已订阅接口相同,OmnOem 项目中没有区分)
static const String kr_userAvailableSubscribe =
"/v1/app/subscribe/user/available_subscribe";
"/v1/public/user/subscribe";
/// 续费
static const String kr_renewal = "/v1/app/order/renewal";
@@ -66,7 +62,7 @@ abstract class Api {
static const String kr_orderDetail = "/v1/app/order/detail";
/// 获取消息列表
static const String kr_getMessageList = "/v1/app/announcement/list";
static const String kr_getMessageList = "/v1/public/announcement/list";
/// 获取邀请数据
// static const String kr_getInviteData = "/v1/public/invite/code";
@@ -74,9 +70,6 @@ abstract class Api {
/// 配置信息
static const String kr_config = "/v1/app/auth/config";
/// 获取用户信息
static const String kr_getUserInfo = "/v1/app/user/info";
/// 获取用户在线时长统计
static const String kr_getUserOnlineTimeStatistics =
"/v1/app/user/online_time/statistics";
@@ -96,4 +89,10 @@ abstract class Api {
/// 重置订阅周期
static const String kr_resetSubscribePeriod =
"/v1/app/subscribe/reset/period";
/// 获取用户设备列表
static const String kr_getUserDevices = "/v1/public/user/devices";
/// 解绑用户设备
static const String kr_unbindUserDevice = "/v1/public/user/unbind_device";
}
+76 -15
View File
@@ -83,21 +83,22 @@ class KRUserApi {
return right(baseResponse.model);
}
Future<Either<HttpError, KRUserInfo>> kr_getUserInfo() async {
final Map<String, dynamic> data = <String, dynamic>{};
BaseResponse<KRUserInfo> baseResponse =
await HttpUtil.getInstance().request<KRUserInfo>(
Api.kr_getUserInfo,
data,
method: HttpMethod.GET,
isShowLoading: false,
);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model);
}
// ⚠️ 已废弃:新版本后端不再提供此接口
// Future<Either<HttpError, KRUserInfo>> kr_getUserInfo() async {
// final Map<String, dynamic> data = <String, dynamic>{};
// BaseResponse<KRUserInfo> baseResponse =
// await HttpUtil.getInstance().request<KRUserInfo>(
// Api.kr_getUserInfo,
// data,
// method: HttpMethod.GET,
// isShowLoading: false,
// );
// if (!baseResponse.isSuccess) {
// return left(
// HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
// }
// return right(baseResponse.model);
// }
Future<Either<HttpError, KRAffiliateCount>> kr_getAffiliateCount() async {
final Map<String, dynamic> data = <String, dynamic>{};
@@ -132,4 +133,64 @@ class KRUserApi {
return right(baseResponse.model);
}
/// 获取用户设备列表
Future<Either<HttpError, List<Map<String, dynamic>>>> kr_getUserDevices() async {
final Map<String, dynamic> data = <String, dynamic>{};
BaseResponse<dynamic> baseResponse =
await HttpUtil.getInstance().request<dynamic>(
Api.kr_getUserDevices,
data,
method: HttpMethod.GET,
isShowLoading: false,
);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
// 返回设备列表数据
try {
// 响应格式: { data: { list: [...], total: N } }
final responseData = baseResponse.model;
final List<Map<String, dynamic>> devices =
(responseData['list'] as List)
.map((item) => item as Map<String, dynamic>)
.toList();
return right(devices);
} catch (e) {
KRLogUtil.kr_e('解析设备列表失败: $e', tag: 'KRUserApi');
return left(HttpError(msg: '数据解析失败', code: -1));
}
}
/// 解绑用户设备
Future<Either<HttpError, void>> kr_unbindUserDevice(String deviceId) async {
final Map<String, dynamic> data = <String, dynamic>{};
// 将字符串 ID 转换为整数
try {
data['id'] = int.parse(deviceId);
} catch (e) {
KRLogUtil.kr_e('设备ID格式错误: $deviceId', tag: 'KRUserApi');
return left(HttpError(msg: '设备ID格式错误', code: -1));
}
BaseResponse<dynamic> baseResponse =
await HttpUtil.getInstance().request<dynamic>(
Api.kr_unbindUserDevice,
data,
method: HttpMethod.PUT,
isShowLoading: true,
);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(null);
}
}
+47 -100
View File
@@ -23,22 +23,14 @@ import '../../common/app_config.dart';
import 'package:dio/dio.dart' as dio;
class KRAuthApi {
/// 是否开启了审核开关
Future<Either<HttpError, bool>> kr_isRegister(
KRLoginType tpye, String account, String? areaCode) async {
/// 检查账号是否已注册(仅支持邮箱)
Future<Either<HttpError, bool>> kr_isRegister(String email) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['method'] = tpye.value;
data['account'] = account;
final deviceId = await KRDeviceUtil().kr_getDeviceId();
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
data['email'] = email;
data["user_agent"] = _kr_getUserAgent();
data["os"] = _kr_getUserAgent();
if (areaCode != null) {
data['area_code'] = areaCode.toString();
}
final deviceId = await KRDeviceUtil().kr_getDeviceId();
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
.request<KRIsRegister>(Api.kr_isRegister, data,
@@ -52,29 +44,26 @@ class KRAuthApi {
return right(baseResponse.model.kr_isRegister);
}
/// 注册
/// 注册(仅支持邮箱+密码,验证码和邀请码可选)
Future<Either<HttpError, String>> kr_register(
KRLoginType tpye,
String account,
String? areaCode,
String? code,
String? password,
{String? inviteCode}) async {
String email,
String password,
{String? code,
String? inviteCode}) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['method'] = tpye.value;
data['account'] = account;
data['email'] = email;
data['password'] = password;
data["code"] = code;
data["identifier"] = await KRDeviceUtil().kr_getDeviceId();
data["os"] = _kr_getUserAgent();
// 验证码是可选的,只有在提供时才发送
if (code != null && code.isNotEmpty) {
data["code"] = code;
}
// 邀请码是可选的
if (inviteCode != null && inviteCode.isNotEmpty) {
data["invite"] = inviteCode;
}
data["user_agent"] = _kr_getUserAgent();
if (tpye == KRLoginType.kr_telephone) {
data['area_code'] = areaCode;
}
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_register, data,
@@ -87,20 +76,14 @@ class KRAuthApi {
return right(baseResponse.model.kr_token.toString());
}
/// 验证验证码
Future<Either<HttpError, bool>> kr_checkVerificationCode( KRLoginType tpye,
String account, String? areaCode, String code, int type) async {
/// 验证验证码(仅支持邮箱)
Future<Either<HttpError, bool>> kr_checkVerificationCode(
String email, String code, int type) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['method'] = tpye.value;
if(tpye == KRLoginType.kr_telephone){
data['account'] = areaCode.toString() + account;
}else{
data['account'] = account;
}
data['email'] = email;
data['code'] = code;
data['type'] = type;
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
.request<KRIsRegister>(Api.kr_checkVerificationCode, data,
method: HttpMethod.POST, isShowLoading: true);
@@ -108,37 +91,23 @@ class KRAuthApi {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
if(baseResponse.model.kr_isRegister){
if (baseResponse.model.kr_isRegister) {
return right(true);
}else{
} else {
return left(HttpError(msg: "error.70001".tr, code: 70001));
}
}
/// 登
Future<Either<HttpError, String>> kr_login(KRLoginType tpye, bool isPsd,
String account, String? areaCode, String? code, String? password) async {
/// 登录(仅支持邮箱+密码)
Future<Either<HttpError, String>> kr_login(
String email, String password) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['method'] = tpye.value;
data['account'] = account;
data['email'] = email;
data['password'] = password;
final deviceId = await KRDeviceUtil().kr_getDeviceId();
final deviceId = await KRDeviceUtil().kr_getDeviceId();
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
data["user_agent"] = _kr_getUserAgent();
data["os"] = _kr_getUserAgent();
if (tpye == KRLoginType.kr_telephone) {
data['area_code'] = areaCode;
}
if (isPsd) {
data['password'] = password;
} else {
data["code"] = code;
}
data["identifier"] = deviceId;
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_login, data,
@@ -151,45 +120,30 @@ class KRAuthApi {
return right(baseResponse.model.kr_token.toString());
}
/// 发送验证码 type 1 注册 其他 2
Future<Either<HttpError, bool>> kr_sendCode(
KRLoginType tpye, String account, String? areaCode, int type) async {
/// 发送验证码(仅支持邮箱)
/// type: 1=登录, 2=注册, 3=重置密码
Future<Either<HttpError, bool>> kr_sendCode(String email, int type) async {
final Map<String, dynamic> data = <String, dynamic>{};
if (tpye == KRLoginType.kr_email) {
data['email'] = account;
} else {
data['telephone'] = account;
data['telephone_area_code'] = areaCode.toString();
}
data['email'] = email;
data['type'] = type;
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
.request<dynamic>(
tpye == KRLoginType.kr_email
? Api.kr_sendEmailCode
: Api.kr_sendPhoneCode,
data,
method: HttpMethod.POST,
isShowLoading: true);
.request<dynamic>(Api.kr_sendCode, data,
method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
// KRCommonUtil.kr_showToast(baseResponse.model.toString());
// KRIsRegister model = (baseResponse..model) as KRIsRegister;
return right(true);
}
/// 删除账号
Future<Either<HttpError, String>> kr_deleteAccount(KRLoginType tpye,
String code) async {
Future<Either<HttpError, String>> kr_deleteAccount(String code) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['method'] = tpye.value;
data['code'] = code;
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
.request<dynamic>(Api.kr_deleteAccount, data,
method: HttpMethod.DELETE, isShowLoading: true);
if (!baseResponse.isSuccess) {
@@ -198,23 +152,16 @@ class KRAuthApi {
}
return right("");
}
/// 忘记密码-设置新密码
Future<Either<HttpError, String>> kr_setNewPsdByForgetPsd(KRLoginType tpye,
String account, String? areaCode, String? code, String? password) async {
/// 忘记密码-设置新密码(仅支持邮箱)
Future<Either<HttpError, String>> kr_setNewPsdByForgetPsd(
String email, String code, String password) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['method'] = tpye.value;
data['account'] = account;
data['email'] = email;
data['password'] = password;
data["code"] = code;
data["identifier"] = await KRDeviceUtil().kr_getDeviceId();
data["user_agent"] = _kr_getUserAgent();
data["os"] = _kr_getUserAgent();
if (tpye == KRLoginType.kr_telephone) {
data['area_code'] = areaCode;
}
data["identifier"] = await KRDeviceUtil().kr_getDeviceId();
BaseResponse<KRLoginData> baseResponse = await HttpUtil.getInstance()
.request<KRLoginData>(Api.kr_setNewPsdByForgetPsd, data,
@@ -21,6 +21,11 @@ class KRAnnouncementService {
KRAnnouncementService._internal();
// 重置公告显示状态(用于退出登录时)
void kr_reset() {
_kr_hasShownAnnouncement = false;
}
// 检查是否需要显示公告弹窗
Future<void> kr_checkAnnouncement() async {
if (_kr_hasShownAnnouncement) {
+118 -32
View File
@@ -72,6 +72,9 @@ class KRSubscribeService {
/// 是否处于试用状态
final RxBool kr_isTrial = false.obs;
/// 当前节点列表是否包含试用节点
final RxBool kr_hasTrialNodes = false.obs;
/// 订阅记录
final RxList<KRAlreadySubscribe> kr_alreadySubscribe =
<KRAlreadySubscribe>[].obs;
@@ -247,9 +250,13 @@ class KRSubscribeService {
result.fold((error) {
kr_currentStatus.value = KRSubscribeServiceStatus.kr_error;
}, (nodes) {
// 处理节点列表
// 记录当前节点列表是否包含试用节点
kr_hasTrialNodes.value = nodes.isTryOut;
KRLogUtil.kr_i('切换订阅 - 节点列表包含试用节点: ${kr_hasTrialNodes.value}', tag: 'SubscribeService');
// 处理节点列表(不使用分组)
final listModel = KrOutboundsList();
listModel.processOutboundItems(nodes.list, kr_nodeGroups);
listModel.processOutboundItems(nodes.list, []);
// 更新UI数据
groupOutboundList.value = listModel.groupOutboundList;
@@ -277,24 +284,45 @@ class KRSubscribeService {
_kr_trialTimer?.cancel();
_kr_subscriptionTimer?.cancel();
// 检查试用状态
final bool kr_isSubscribed = kr_currentSubscribe.value != null &&
kr_alreadySubscribe.any((subscribe) =>
kr_currentSubscribe.value?.id == subscribe.userSubscribeId);
if (kr_currentSubscribe.value == null) {
kr_isTrial.value = false;
return;
}
KRLogUtil.kr_i('当前订阅状态: ${kr_isSubscribed ? "已订阅" : "未订阅"}',
tag: 'SubscribeService');
KRLogUtil.kr_i('当前订阅ID: ${kr_currentSubscribe.value?.id}',
tag: 'SubscribeService');
KRLogUtil.kr_i(
'已订阅记录: ${kr_alreadySubscribe.map((s) => s.userSubscribeId).join(', ')}',
tag: 'SubscribeService');
// 优先使用 API 返回的 isTryOut 字段判断试用状态
final currentSubscribe = kr_currentSubscribe.value!;
// 设置试用状态
kr_isTrial.value = kr_currentSubscribe.value != null && !kr_isSubscribed;
// 1. 优先使用 API 返回的 isTryOut 字段
kr_isTrial.value = currentSubscribe.isTryOut;
KRLogUtil.kr_i('步骤1 - API isTryOut 字段: ${currentSubscribe.isTryOut}', tag: 'SubscribeService');
KRLogUtil.kr_i('试用状态: ${kr_isTrial.value ? "" : ""}',
tag: 'SubscribeService');
// 2. 如果 API 说不是试用,检查是否有购买记录
if (!kr_isTrial.value) {
final bool kr_isSubscribed = kr_alreadySubscribe.any(
(subscribe) => currentSubscribe.id == subscribe.userSubscribeId
);
KRLogUtil.kr_i('步骤2 - 检查购买记录: $kr_isSubscribed', tag: 'SubscribeService');
// 如果没有购买记录,判断为试用
if (!kr_isSubscribed) {
kr_isTrial.value = true;
KRLogUtil.kr_i('步骤2 - 没有购买记录,判定为试用', tag: 'SubscribeService');
}
}
// 3. 最后检查订阅名称是否包含"试用"关键字(最后的备用方案)
if (!kr_isTrial.value && currentSubscribe.name.contains('试用')) {
kr_isTrial.value = true;
KRLogUtil.kr_i('步骤3 - 订阅名称包含"试用"关键字,判定为试用', tag: 'SubscribeService');
}
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SubscribeService');
KRLogUtil.kr_i('当前订阅: ${currentSubscribe.name}(${currentSubscribe.id})', tag: 'SubscribeService');
KRLogUtil.kr_i('API isTryOut: ${currentSubscribe.isTryOut}', tag: 'SubscribeService');
KRLogUtil.kr_i('已订阅记录数: ${kr_alreadySubscribe.length}', tag: 'SubscribeService');
KRLogUtil.kr_i('已订阅ID列表: ${kr_alreadySubscribe.map((s) => s.userSubscribeId).join(', ')}', tag: 'SubscribeService');
KRLogUtil.kr_i('✅ 最终试用状态: ${kr_isTrial.value ? "试用" : "付费"}', tag: 'SubscribeService');
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'SubscribeService');
if (kr_isTrial.value) {
// 启动试用倒计时
@@ -445,15 +473,10 @@ class KRSubscribeService {
},
);
final result = await kr_subscribeApi.kr_nodeGroupList();
result.fold(
(error) {
throw Exception('获取节点分组失败: ${error.msg}');
},
(groups) {
kr_nodeGroups.value = groups;
},
);
// 🔧 取消节点分组的概念,不再调用 kr_nodeGroupList
// 直接使用节点列表,通过 is_try_out 字段区分免费/付费节点
kr_nodeGroups.clear();
KRLogUtil.kr_i('已取消节点分组,将直接使用节点列表', tag: 'SubscribeService');
// 保存当前选中的订阅名称
final currentSubscribeID = kr_currentSubscribe.value?.id;
@@ -502,9 +525,26 @@ class KRSubscribeService {
}
}
// 2. 如果没有找到之前的订阅,优先选择已购买的套餐(非试用)
// 2. 如果没有找到之前的订阅,优先选择试用套餐
if (selectedSubscribe == null) {
KRLogUtil.kr_i('开始查找已购买的套餐...', tag: 'SubscribeService');
KRLogUtil.kr_i('开始查找试用套餐...', tag: 'SubscribeService');
for (var subscribe in subscribes) {
KRLogUtil.kr_i('检查订阅: ${subscribe.name}(${subscribe.id}), 是否试用: ${subscribe.isTryOut}',
tag: 'SubscribeService');
if (subscribe.isTryOut) {
selectedSubscribe = subscribe;
KRLogUtil.kr_i('✅ 找到试用套餐,默认选择: ${selectedSubscribe.name}',
tag: 'SubscribeService');
break;
}
}
}
// 3. 如果没有试用套餐,选择已购买的套餐(非试用)
if (selectedSubscribe == null) {
KRLogUtil.kr_i('没有试用套餐,查找已购买的套餐...', tag: 'SubscribeService');
KRLogUtil.kr_i('已订阅记录: ${kr_alreadySubscribe.map((s) => s.userSubscribeId).join(', ')}',
tag: 'SubscribeService');
@@ -524,10 +564,10 @@ class KRSubscribeService {
}
}
// 3. 如果没有已购买的套餐,选择第一个(可能是试用套餐)
// 4. 如果没有找到,选择第一个
if (selectedSubscribe == null) {
selectedSubscribe = subscribes.first;
KRLogUtil.kr_i('没有已购买的套餐,选择第一个: ${selectedSubscribe.name}',
KRLogUtil.kr_i('没有找到匹配的套餐,选择第一个: ${selectedSubscribe.name}',
tag: 'SubscribeService');
}
@@ -545,9 +585,13 @@ class KRSubscribeService {
(nodes) => nodes,
);
// 处理节点列表
// 记录当前节点列表是否包含试用节点
kr_hasTrialNodes.value = nodes.isTryOut;
KRLogUtil.kr_i('节点列表包含试用节点: ${kr_hasTrialNodes.value}', tag: 'SubscribeService');
// 处理节点列表(不使用分组)
final listModel = KrOutboundsList();
listModel.processOutboundItems(nodes.list, kr_nodeGroups);
listModel.processOutboundItems(nodes.list, []);
// 更新UI数据
groupOutboundList.value = listModel.groupOutboundList;
@@ -596,6 +640,7 @@ class KRSubscribeService {
Future<void> kr_clearCutNodeData() async {
kr_isLastDayOfSubscription.value = false;
kr_isTrial.value = false;
kr_hasTrialNodes.value = false;
kr_subscriptionRemainingTime.value = '';
kr_trialRemainingTime.value = '';
@@ -618,4 +663,45 @@ class KRSubscribeService {
/// 获取当前订阅
KRUserAvailableSubscribeItem? get kr_getCurrentSubscribe =>
kr_currentSubscribe.value;
/// 获取免费节点列表(试用节点)
/// 当试用结束时,返回空列表
List<KROutboundItem> get kr_freeNodes {
if (!kr_hasTrialNodes.value) {
return [];
}
// 如果当前是试用状态或有试用节点,返回所有节点作为免费节点
// 如果试用已结束(kr_isTrial=false 且 kr_hasTrialNodes=true),返回空列表
if (kr_isTrial.value) {
KRLogUtil.kr_i('返回免费节点: ${allList.length}', tag: 'SubscribeService');
return allList.toList();
} else {
KRLogUtil.kr_i('试用已结束,不显示免费节点', tag: 'SubscribeService');
return [];
}
}
/// 获取付费节点列表
/// 如果当前不是试用,返回所有节点
/// 如果当前是试用,返回空列表
List<KROutboundItem> get kr_paidNodes {
if (kr_isTrial.value) {
KRLogUtil.kr_i('当前是试用状态,不显示付费节点', tag: 'SubscribeService');
return [];
} else {
KRLogUtil.kr_i('返回付费节点: ${allList.length}', tag: 'SubscribeService');
return allList.toList();
}
}
/// 是否应该显示免费标签页
bool get kr_shouldShowFreeTab {
return kr_hasTrialNodes.value && kr_isTrial.value;
}
/// 是否应该显示付费标签页
bool get kr_shouldShowPaidTab {
return !kr_isTrial.value;
}
}