feat: 完成代码
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (iOS/tvOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 构建 iOS (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled

This commit is contained in:
2025-10-31 04:00:26 -07:00
parent 74df08144f
commit 445b1e0352
11 changed files with 502 additions and 334 deletions
+1 -1
View File
@@ -265,7 +265,7 @@ class KRAppRunData {
// 初始化设备信息服务
await KRDeviceInfoService().initialize();
KRLogUtil.kr_i('🔐 开始执行设备登录', tag: 'SplashController');
KRLogUtil.kr_i('🔐 开始执行设备登录', tag: 'AppRunData');
// 执行设备登录
final authApi = KRAuthApi();
+3
View File
@@ -1,4 +1,5 @@
import 'package:kaer_with_panels/app/model/response/kr_is_register.dart';
import 'package:kaer_with_panels/app/model/response/kr_check_subscription.dart';
import 'package:kaer_with_panels/app/model/response/kr_login_data.dart';
import 'package:kaer_with_panels/app/model/response/kr_node_list.dart';
import 'package:kaer_with_panels/app/model/response/kr_package_list.dart';
@@ -23,6 +24,8 @@ abstract class EntityFromJsonUtil {
switch (T.toString()) {
case "KRIsRegister":
return KRIsRegister.fromJson(json) as T;
case "KRCheckSubscription":
return KRCheckSubscription.fromJson(json) as T;
case "KRLoginData":
return KRLoginData.fromJson(json) as T;
case "KRPackageList":
+30
View File
@@ -0,0 +1,30 @@
/// 是否订阅(区分设备和邮箱)
class KRCheckSubscription {
/// 当前设备订阅状态
final bool deviceSubscribed;
/// 邮箱订阅状态
final bool emailSubscribed;
/// 构造函数,默认 false
KRCheckSubscription({
this.deviceSubscribed = false,
this.emailSubscribed = false,
});
/// 从 JSON 创建对象
KRCheckSubscription.fromJson(Map<String, dynamic>? json)
: deviceSubscribed = (json?['device_status'] == true || json?['deviceStatus'] == "true") ? true : false,
emailSubscribed = (json?['email_status'] == true || json?['emailStatus'] == "true") ? true : false;
/// 转换成 JSON
Map<String, dynamic> toJson() {
return {
'deviceStatus': deviceSubscribed,
'emailStatus': emailSubscribed,
};
}
/// 判断邮箱和设备是否都已订阅
bool get isFullySubscribed => deviceSubscribed && emailSubscribed;
}
@@ -267,62 +267,62 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
),
),
// 👇 核心改动 3: 将固定在底部的按钮放在 Expanded 外部
Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Column(
children: [
GestureDetector(
onTap: () {
// 点击“注销账户”时,显示一个确认对话框
HIDialog.show(
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w),
child: Text(
'注销账号后,所有此账号内的剩余套餐和账户数据将被清空,无法找回。', // 1. 修改为正确的提示信息
textAlign: TextAlign.left, // 文本居中
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
Obx(() {
if((KRAppRunData.getInstance().kr_account.value != null &&
KRAppRunData.getInstance().kr_account.value!.startsWith('9000')))
return SizedBox.shrink();
return Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Column(
children: [
GestureDetector(
onTap: () {
// 点击“注销账户”时,显示一个确认对话框
HIDialog.show(
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w),
child: Text(
'注销账号后,所有此账号内的剩余套餐和账户数据将被清空,无法找回。', // 1. 修改为正确的提示信息
textAlign: TextAlign.left, // 文本居中
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
cancelText: '注销', // 2. 修改按钮文字,使其含义清晰
confirmText: '返回',
// 3. onCancel 对应“确认注销”按钮的点击事件
onCancel: () {
// 执行页面跳转到注销账户页面
Get.toNamed(Routes.KR_DELETE_ACCOUNT);
},
// 4. onConfirm 对应“返回”按钮的点击事件
onConfirm: () {
},
);
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2),
),
cancelText: '注销', // 2. 修改按钮文字,使其含义清晰
confirmText: '返回',
// 3. onCancel 对应“确认注销”按钮的点击事件
onCancel: () {
// 执行页面跳转到注销账户页面
Get.toNamed(Routes.KR_DELETE_ACCOUNT);
},
// 4. onConfirm 对应“返回”按钮的点击事件
onConfirm: () {
},
);
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2),
),
alignment: Alignment.center,
child: Text(
'注销账户',
style: TextStyle(
color: const Color(0xFFFF2ED1),
fontSize: 16.sp,
fontWeight: FontWeight.w700,
alignment: Alignment.center,
child: Text(
'注销账户',
style: TextStyle(
color: const Color(0xFFFF2ED1),
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
),
),
),
),
SizedBox(height: 10.w),
Obx(() {
return !(KRAppRunData.getInstance().kr_account.value != null &&
KRAppRunData.getInstance().kr_account.value!.startsWith('9000'))
?
GestureDetector(
SizedBox(height: 10.w),
GestureDetector(
onTap: () {
HIDialog.show(
customMessageWidget: Padding(
@@ -369,11 +369,10 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
),
),
)
: SizedBox.shrink();
})
],
),
),
],
),
);
}),
// 为底部按钮和 HIHelpEntrance 留出空间
SizedBox(height: 100.h),
],
@@ -393,6 +392,17 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
final identifier = device['identifier'] ?? '';
final isCurrentDevice = device['is_current'] ?? false;
final id = device['id'] ?? '';
final userAgent = device['device_name'] ?? '未知设备';
final ip = device['ip'] ?? '';
final lastLoginRaw = device['last_login'];
final String lastLogin = lastLoginRaw?.toString() ?? '';
// 获取设备类型信息
final deviceInfo = controller.getDeviceTypeInfo(userAgent);
final deviceType = deviceInfo['type'] as String;
final iconName = deviceInfo['icon'] as String;
return Stack( // 使用 Stack 来放置删除按钮
children: [
// 主体内容容器
@@ -408,14 +418,14 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
children: [
Row(
children: [
Icon(Icons.smartphone, color: Colors.white, size: 18.w),
Icon(_getIconData(iconName), color: Colors.white, size: 18.w),
SizedBox(width: 6.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_extractDeviceModel(device['device_name']),
'设备:${_extractDeviceModel(userAgent)}',
style: TextStyle(
color: Colors.white,
fontSize: 10.sp, //
@@ -423,7 +433,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
),
SizedBox(height: 4.h),
Text(
'ID$identifier',
'ID: ${identifier.substring(0, identifier.length > 4 ? 4 : identifier.length)}$id',
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 10.sp,
@@ -576,18 +586,45 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
}
String _extractDeviceModel(String deviceName) {
// 2. 使用正则表达式匹配括号内内容
// 匹配括号内内容
final RegExp regExp = RegExp(r'\((.*?)\)');
// 3. 查找第一个匹配项
final Match? match = regExp.firstMatch(deviceName);
// 4. 如果找到匹配项,返回括号内的文本;否则返回原始名称
if (match != null && match.groupCount >= 1) {
// group(1) 返回第一个捕获组的内容,即括号内的文本
return match.group(1) ?? deviceName;
// 获取括号内内容
final inside = match.group(1)!; // "Android; google Pixel 9; 15"
// 按分号分割,并去掉首尾空格
final parts = inside.split(';').map((s) => s.trim()).toList();
if (parts.length >= 2) {
// 取第二段内容,即设备型号
return parts[1];
} else {
// 没有第二段,则返回整个括号内容
return inside;
}
} else {
// 如果没有括号,则直接返回原始设备名称
// 没有括号,返回原始字符串
return deviceName;
}
}
/// 获取图标数据
IconData _getIconData(String iconName) {
switch (iconName) {
case 'phone_android':
return Icons.phone_android;
case 'phone_iphone':
return Icons.phone_iphone;
case 'tablet':
return Icons.tablet_mac;
case 'desktop_mac':
return Icons.desktop_mac;
case 'computer':
return Icons.computer;
default:
return Icons.devices;
}
}
}
@@ -89,6 +89,7 @@ class KRDeleteAccountController extends GetxController {
// 删除账号(简化后的 API 只需要 code)
final result = await _authApi.kr_deleteAccount(
KRAppRunData.getInstance().kr_account.value!,
kr_codeController.text,
);
@@ -169,7 +169,6 @@ class _KRHomeViewState extends State<KRHomeView> {
? Icon(
Icons.check,
size: 14.w,
fontWeight: FontWeight.w900,
color: Colors.black,
)
: null,
@@ -10,6 +10,7 @@ import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/utils/kr_event_bus.dart';
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import '../../../localization/kr_language_utils.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
@@ -366,18 +367,43 @@ class KRLoginController extends GetxController
return;
}
final either = await KRAuthApi().kr_register(
accountController.text,
psdController.text,
code: codeController.text.isEmpty ? null : codeController.text,
inviteCode: inviteCodeController.text.isEmpty ? null : inviteCodeController.text);
either.fold((l) {
KRCommonUtil.kr_showToast(l.msg);
}, (r) async {
_saveLoginData(r);
// KRCommonUtil.kr_showToast(AppTranslations.kr_login.registerSuccess);
KRCommonUtil.kr_showToast('登录成功');
});
// 定义注册请求逻辑
Future<void> performRegister() async {
final either = await KRAuthApi().kr_register(
accountController.text,
psdController.text,
code: codeController.text.isEmpty ? null : codeController.text,
inviteCode: inviteCodeController.text.isEmpty ? null : inviteCodeController.text,
);
either.fold(
(l) => KRCommonUtil.kr_showToast(l.msg),
(r) async {
_saveLoginData(r);
KRCommonUtil.kr_showToast('登录成功');
},
);
}
// 检查是否已有订阅
final subscriptionResult = await KRAuthApi().kr_checkSubscription(accountController.text);
subscriptionResult.fold(
(error) {
KRCommonUtil.kr_showToast(error.msg);
},
(isFullySubscribed) async {
if (isFullySubscribed) {
HIDialog.show(
message: '当前邮箱已有套餐,继续绑定会丢失设备当前套餐,是否继续?',
confirmText: '继续',
cancelText: '取消',
onConfirm: performRegister,
);
} else {
await performRegister();
}
},
);
}
void kr_checkCode() {
+1
View File
@@ -61,6 +61,7 @@ class BaseResponse<T> {
}
if (retCode == 40004 || retCode == 40005 || retCode == 40002 || retCode == 40003) {
KRAppRunData().kr_loginOut();
return;
}
if (retCode != 200) {
+3 -1
View File
@@ -2,6 +2,8 @@
abstract class Api {
/// 游客登录查看是否已经注册
static const String kr_isRegister = "/v1/app/auth/check";
/// 判断邮箱和当前设备是否已存在订阅
static const String kr_checkSubscription = "/v1/public/user/subscribe_status";
/// 注册
// static const String kr_register = "/v1/auth/register";
@@ -21,7 +23,7 @@ abstract class Api {
static const String kr_deviceLogin = "/v1/auth/login/device";
/// 删除账号
static const String kr_deleteAccount = "/v1/app/user/account";
static const String kr_deleteAccount = "/v1/public/user/delete_account";
/// 忘记密码-设置新密码
static const String kr_setNewPsdByForgetPsd = "/v1/app/auth/reset_password";
+33 -1
View File
@@ -8,6 +8,7 @@ import 'package:kaer_with_panels/app/mixins/kr_app_bar_opacity_mixin.dart';
import 'package:kaer_with_panels/app/services/api_service/api.dart';
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
import 'package:kaer_with_panels/app/model/response/kr_is_register.dart';
import 'package:kaer_with_panels/app/model/response/kr_check_subscription.dart';
import 'package:kaer_with_panels/app/model/response/kr_login_data.dart';
import 'package:kaer_with_panels/app/network/base_response.dart';
import 'package:kaer_with_panels/app/network/http_error.dart';
@@ -43,6 +44,36 @@ class KRAuthApi {
return right(baseResponse.model.kr_isRegister);
}
/// 检查账号和邮箱是否都已经有订阅(只有两者都订阅才返回 true)
Future<Either<HttpError, bool>> kr_checkSubscription(String email) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
// 获取当前设备 ID
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
KRLogUtil.kr_i('设备ID: $deviceId', tag: 'KRAuthApi');
data["identifier"] = deviceId;
// 发起请求
BaseResponse<KRCheckSubscription> baseResponse = await HttpUtil.getInstance()
.request<KRCheckSubscription>(
Api.kr_checkSubscription,
data,
method: HttpMethod.POST,
isShowLoading: true,
);
// 请求失败
if (!baseResponse.isSuccess) {
return left(HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
final subscription = baseResponse.model;
// 只有设备和邮箱都已订阅才返回 true
return right(subscription.isFullySubscribed);
}
/// 注册(仅支持邮箱+密码,验证码和邀请码可选)
Future<Either<HttpError, String>> kr_register(
String email,
@@ -139,8 +170,9 @@ class KRAuthApi {
}
/// 删除账号
Future<Either<HttpError, String>> kr_deleteAccount(String code) async {
Future<Either<HttpError, String>> kr_deleteAccount(String email, String code) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['email'] = email;
data['code'] = code;
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
+291 -254
View File
File diff suppressed because it is too large Load Diff