From ac65aaa22745d70297224582a4f794b7260e7a9c Mon Sep 17 00:00:00 2001 From: speakeloudest Date: Fri, 9 Jan 2026 01:36:24 -0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=AE=A2=E6=9C=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/hi_help_controller.dart | 2 + .../modules/hi_help/views/hi_help_view.dart | 3 +- .../controllers/hi_menu_controller.dart | 2 + .../modules/hi_menu/views/hi_menu_view.dart | 63 ++--- .../hi_user_info/views/hi_user_info_view.dart | 10 +- .../controllers/kr_crisp_controller.dart | 262 ++++++++++++------ .../kr_crisp_chat/views/kr_crisp_view.dart | 159 ++++++----- .../kr_purchase_membership_controller.dart | 2 + .../views/kr_purchase_membership_view.dart | 3 +- lib/app/utils/kr_common_util.dart | 105 +++++++ pubspec.lock | 12 +- pubspec.yaml | 3 +- 12 files changed, 435 insertions(+), 191 deletions(-) diff --git a/lib/app/modules/hi_help/controllers/hi_help_controller.dart b/lib/app/modules/hi_help/controllers/hi_help_controller.dart index 1860288..f5a3380 100755 --- a/lib/app/modules/hi_help/controllers/hi_help_controller.dart +++ b/lib/app/modules/hi_help/controllers/hi_help_controller.dart @@ -91,6 +91,8 @@ class HIHelpController extends GetxController { @override void onInit() { super.onInit(); + // 预热客服 + KRCommonUtil.kr_warmUpCustomerService(); // 隐藏全局订阅按钮 // kr_getMessageList(); } diff --git a/lib/app/modules/hi_help/views/hi_help_view.dart b/lib/app/modules/hi_help/views/hi_help_view.dart index bb82ee9..76688bd 100755 --- a/lib/app/modules/hi_help/views/hi_help_view.dart +++ b/lib/app/modules/hi_help/views/hi_help_view.dart @@ -15,6 +15,7 @@ import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.d import '../../../routes/app_pages.dart'; import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart'; import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart'; +import '../../../utils/kr_common_util.dart'; class HIHelpView extends GetView implements HasSwipeConfig { const HIHelpView({super.key}); @@ -84,7 +85,7 @@ class HIHelpView extends GetView implements HasSwipeConfig { title: '在线客服', // 3. 使用 onTap 回调来处理跳转逻辑 onTap: () { - Get.toNamed(Routes.KR_CRISP); + KRCommonUtil.kr_openCustomerService(); }, ), ), diff --git a/lib/app/modules/hi_menu/controllers/hi_menu_controller.dart b/lib/app/modules/hi_menu/controllers/hi_menu_controller.dart index 5a45730..e1d5295 100755 --- a/lib/app/modules/hi_menu/controllers/hi_menu_controller.dart +++ b/lib/app/modules/hi_menu/controllers/hi_menu_controller.dart @@ -18,6 +18,8 @@ class HIMenuController extends GetxController { @override void onInit() { super.onInit(); + // 预加载客服系统 + KRCommonUtil.kr_warmUpCustomerService(); _kr_getVersion(); } diff --git a/lib/app/modules/hi_menu/views/hi_menu_view.dart b/lib/app/modules/hi_menu/views/hi_menu_view.dart index 424829a..341695a 100755 --- a/lib/app/modules/hi_menu/views/hi_menu_view.dart +++ b/lib/app/modules/hi_menu/views/hi_menu_view.dart @@ -17,6 +17,7 @@ import 'package:kaer_with_panels/app/modules/kr_home/views/hi_subscription_corne import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart'; import 'package:kaer_with_panels/app/modules/hi_menu/widgets/user_info_card.dart'; +import '../../../utils/kr_common_util.dart'; class HIMenuView extends GetView implements HasSwipeConfig { const HIMenuView({super.key}); @@ -29,6 +30,34 @@ class HIMenuView extends GetView implements HasSwipeConfig { @override Widget build(BuildContext context) { + final List menuItems = [ + const MenuItem( + iconName: 'icon-1', + title: '节点列表', + route: Routes.HI_NODE_LIST, + ), + const MenuItem( + iconName: 'icon-2', + title: '消息中心', + route: Routes.KR_MESSAGE, + ), + const MenuItem( + iconName: 'icon-3', + title: '常见问题', + route: Routes.HI_HELP, + ), + const MenuItem( + iconName: 'icon-4', + title: '邀请好友', + route: Routes.KR_INVITE, + ), + MenuItem( + iconName: 'icon-5', + title: '在线客服', + onTap: () => KRCommonUtil.kr_openCustomerService(), + ), + ]; + return HIBaseScaffold( child: Stack( fit: StackFit.expand, @@ -58,17 +87,17 @@ class HIMenuView extends GetView implements HasSwipeConfig { ); }), - SizedBox(height: 10.h), // 卡片和菜单列表的间距 + SizedBox(height: 10.h), // 卡片 and 菜单列表的间距 // ListView.separated 现在也会继承 Padding 的约束 ListView.separated( shrinkWrap: true, // 让 ListView 高度自适应内容 physics: const NeverScrollableScrollPhysics(), // 在 Stack 中,禁用其自身的滚动 - itemCount: _menuItems.length, + itemCount: menuItems.length, // 渲染每一项 itemBuilder: (context, index) { - return MenuListItem(item: _menuItems[index]); + return MenuListItem(item: menuItems[index]); }, // 设置每一项之间的间距 separatorBuilder: (context, index) { @@ -104,31 +133,3 @@ class HIMenuView extends GetView implements HasSwipeConfig { ); } } - -const List _menuItems = [ - MenuItem( - iconName: 'icon-1', - title: '节点列表', - route: Routes.HI_NODE_LIST, - ), - MenuItem( - iconName: 'icon-2', - title: '消息中心', - route: Routes.KR_MESSAGE, - ), - MenuItem( - iconName: 'icon-3', - title: '常见问题', - route: Routes.HI_HELP, - ), - MenuItem( - iconName: 'icon-4', - title: '邀请好友', - route: Routes.KR_INVITE, - ), - MenuItem( - iconName: 'icon-5', - title: '在线客服', - route: Routes.KR_CRISP, - ), -]; diff --git a/lib/app/modules/hi_user_info/views/hi_user_info_view.dart b/lib/app/modules/hi_user_info/views/hi_user_info_view.dart index 97f6571..cbab546 100755 --- a/lib/app/modules/hi_user_info/views/hi_user_info_view.dart +++ b/lib/app/modules/hi_user_info/views/hi_user_info_view.dart @@ -500,10 +500,12 @@ class HIUserInfoView extends GetView { confirmText: KRAppRunData.getInstance().isDeviceLogin() ? '前往' : null, cancelText: KRAppRunData.getInstance().isDeviceLogin() ? '取消' : null, onConfirm: () { - Get.toNamed( - Routes.MR_LOGIN, - arguments: {'entry': 'bind_email'}, - ); + if(KRAppRunData.getInstance().isDeviceLogin()) { + Get.toNamed( + Routes.MR_LOGIN, + arguments: {'entry': 'bind_email'}, + ); + } }, ); }, diff --git a/lib/app/modules/kr_crisp_chat/controllers/kr_crisp_controller.dart b/lib/app/modules/kr_crisp_chat/controllers/kr_crisp_controller.dart index e017621..365e914 100755 --- a/lib/app/modules/kr_crisp_chat/controllers/kr_crisp_controller.dart +++ b/lib/app/modules/kr_crisp_chat/controllers/kr_crisp_controller.dart @@ -1,33 +1,45 @@ import 'package:get/get.dart'; -import 'package:crisp_sdk/crisp_sdk.dart'; import 'package:kaer_with_panels/app/common/app_config.dart'; import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/localization/kr_language_utils.dart'; import 'dart:io' show Platform; -import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; import 'dart:async'; import '../../../services/kr_device_info_service.dart'; import 'package:flutter/foundation.dart'; -/// Crisp 聊天控制器 +// 移动平台使用原生 SDK +import 'package:crisp_chat/crisp_chat.dart' as native_crisp; +// 桌面平台使用 WebView SDK +import 'package:crisp_sdk/crisp_sdk.dart' as webview_crisp; + +/// 🔧 P15 重构: Crisp 聊天控制器 +/// - 移动平台 (Android/iOS): 使用 crisp_chat 原生 SDK,解决 WebView 黑屏问题 +/// - 桌面平台 (macOS/Windows): 使用 crisp_sdk WebView 实现 class KRCrispController extends GetxController { - // Crisp 控制器 - CrispController? crispController; + // ========== 移动平台 (原生 SDK) ========== + native_crisp.CrispConfig? _nativeConfig; - // 加载状态 - final RxBool kr_isLoading = true.obs; - // 初始化完成状态 - final RxBool kr_isInitialized = false.obs; - - // 用于取消异步操作的订阅 + // ========== 桌面平台 (WebView SDK) ========== + webview_crisp.CrispController? crispController; Completer? _kr_initializationCompleter; bool _kr_isDisposed = false; + // ========== 共享状态 ========== + final RxBool kr_isLoading = true.obs; + final RxBool kr_isInitialized = false.obs; + + /// 是否为移动平台 + bool get _isMobilePlatform => Platform.isAndroid || Platform.isIOS; + @override void onInit() { super.onInit(); - _kr_prepareInitialization(); + if (_isMobilePlatform) { + _kr_prepareMobileConfig(); + } else { + _kr_prepareDesktopInitialization(); + } } @override @@ -35,21 +47,104 @@ class KRCrispController extends GetxController { super.onReady(); } - /// 准备初始化 - Future _kr_prepareInitialization() async { + // ========================================== + // 移动平台方法 (Android/iOS - 原生 SDK) + // ========================================== + + /// 准备移动平台 Crisp 配置 + void _kr_prepareMobileConfig() { + try { + kr_isLoading.value = false; // 原生 SDK 不需要加载状态 + + final appData = KRAppRunData(); + final userEmail = appData.kr_account.value ?? ''; + final deviceId = KRDeviceInfoService().deviceId ?? 'unknown'; + final identifier = userEmail.isNotEmpty ? userEmail : deviceId; + + // 创建原生 SDK 配置 + _nativeConfig = native_crisp.CrispConfig( + websiteID: AppConfig.getInstance().kr_website_id, + user: native_crisp.User( + email: identifier, + nickName: identifier, + ), + ); + + kr_isInitialized.value = true; + + if (kDebugMode) { + print('[P15-Mobile] Crisp 原生 SDK 配置已准备'); + } + } catch (e) { + if (kDebugMode) { + print('[P15-Mobile] 准备 Crisp 配置时出错: $e'); + } + kr_isInitialized.value = false; + } + } + + /// 打开原生 Crisp 聊天界面 (移动平台) + Future kr_openNativeCrispChat() async { + if (_nativeConfig == null) { + if (kDebugMode) { + print('[P15-Mobile] Crisp 配置未准备好'); + } + return; + } + + try { + if (kDebugMode) { + print('[P15-Mobile] 正在打开 Crisp 原生聊天界面...'); + } + + await native_crisp.FlutterCrispChat.openCrispChat(config: _nativeConfig!); + + // 设置会话数据 + final currentLanguage = KRLanguageUtils.getCurrentLanguageCode(); + final deviceId = KRDeviceInfoService().deviceId ?? 'unknown'; + + native_crisp.FlutterCrispChat.setSessionString( + key: 'platform', + value: Platform.isAndroid ? 'android' : 'ios', + ); + native_crisp.FlutterCrispChat.setSessionString( + key: 'language', + value: currentLanguage, + ); + native_crisp.FlutterCrispChat.setSessionString( + key: 'device_id', + value: deviceId, + ); + + if (kDebugMode) { + print('[P15-Mobile] Crisp 聊天界面已打开'); + } + } catch (e) { + if (kDebugMode) { + print('[P15-Mobile] 打开 Crisp 聊天时出错: $e'); + } + } + } + + // ========================================== + // 桌面平台方法 (macOS/Windows - WebView SDK) + // ========================================== + + /// 准备桌面平台初始化 + Future _kr_prepareDesktopInitialization() async { if (_kr_isDisposed) return; _kr_initializationCompleter = Completer(); try { kr_isLoading.value = true; - await kr_initializeCrisp(); + await kr_initializeDesktopCrisp(); if (!_kr_isDisposed) { kr_isInitialized.value = true; } } catch (e) { if (kDebugMode) { - print('初始化 Crisp 时出错: $e'); + print('[P15-Desktop] 初始化 Crisp 时出错: $e'); } if (!_kr_isDisposed) { kr_isInitialized.value = false; @@ -62,34 +157,23 @@ class KRCrispController extends GetxController { } } - /// 初始化 Crisp - Future kr_initializeCrisp() async { + /// 初始化桌面平台 Crisp (WebView) + Future kr_initializeDesktopCrisp() async { if (_kr_isDisposed) return; try { final appData = KRAppRunData(); final currentLanguage = KRLanguageUtils.getCurrentLanguageCode(); final userEmail = appData.kr_account.value ?? ''; - - // 获取设备 ID final deviceId = KRDeviceInfoService().deviceId ?? 'unknown'; final identifier = userEmail.isNotEmpty ? userEmail : deviceId; - // 根据当前语言设置对应的 Crisp locale - // Crisp 支持的语言:https://docs.crisp.chat/guides/chatbox/languages/ String locale = _getLocaleForCrisp(currentLanguage); if (_kr_isDisposed) return; - final websiteId = AppConfig.getInstance().kr_website_id; - if (websiteId.isEmpty) { - // 如果为空,打印错误日志并终止函数 - print('Crisp 初始化失败:website_id 未配置'); - return; - } - - // 初始化 Crisp 控制器 - crispController = CrispController( + // 初始化 WebView Crisp 控制器 + crispController = webview_crisp.CrispController( websiteId: AppConfig.getInstance().kr_website_id, locale: locale, ); @@ -101,7 +185,7 @@ class KRCrispController extends GetxController { // 设置用户信息 crispController?.register( - user: CrispUser( + user: webview_crisp.CrispUser( email: identifier, nickname: identifier, ), @@ -114,82 +198,100 @@ class KRCrispController extends GetxController { // 设置会话数据 crispController?.setSessionData({ - 'platform': Platform.isAndroid - ? 'android' - : Platform.isIOS - ? 'ios' - : Platform.isWindows - ? 'windows' - : Platform.isMacOS - ? 'macos' - : 'unknown', + 'platform': Platform.isWindows ? 'windows' : 'macos', 'language': currentLanguage, 'app_version': '1.0.0', 'device_id': deviceId, }); if (kDebugMode) { - print('Crisp 初始化完成'); + print('[P15-Desktop] Crisp WebView 初始化完成'); } } catch (e) { if (kDebugMode) { - print('初始化 Crisp 时出错: $e'); + print('[P15-Desktop] 初始化 Crisp 时出错: $e'); } crispController = null; rethrow; } } - @override - void onClose() { - _kr_isDisposed = true; - kr_cleanupResources(); - super.onClose(); - } - - /// 清理 Crisp 资源 - Future kr_cleanupResources() async { - try { - // 等待初始化完成 - if (_kr_initializationCompleter != null && !_kr_initializationCompleter!.isCompleted) { - await _kr_initializationCompleter!.future; - } - - if (kr_isInitialized.value) { - // 清理 Crisp 会话 - crispController = null; - kr_isInitialized.value = false; - kr_isLoading.value = false; - } - } catch (e) { - if (kDebugMode) { - print('清理 Crisp 资源时出错: $e'); - } - } - } - /// 根据应用语言代码获取 Crisp locale - /// 支持应用中所有语言:中文、英文、西班牙语、繁体中文、日语、俄语、爱沙尼亚语 String _getLocaleForCrisp(String languageCode) { - // 映射应用语言到 Crisp 支持的 locale switch (languageCode) { case 'zh_CN': case 'zh': - return 'zh'; // 简体中文 + return 'zh'; case 'zh_TW': case 'zhHant': - return 'zh-tw'; // 繁体中文 + return 'zh-tw'; case 'es': - return 'es'; // 西班牙语 + return 'es'; case 'ja': - return 'ja'; // 日语 + return 'ja'; case 'ru': - return 'ru'; // 俄语 + return 'ru'; case 'et': - return 'et'; // 爱沙尼亚语 + return 'et'; case 'en': default: - return 'en'; // 英语(默认) + return 'en'; + } + } + + // ========================================== + // 生命周期管理 + // ========================================== + + @override + void onClose() { + _kr_isDisposed = true; + + if (_isMobilePlatform) { + // 移动平台:原生 SDK 无需手动清理 + if (kDebugMode) { + print('[P15-Mobile] onClose - 原生 SDK 无需手动清理'); + } + } else { + // 桌面平台:清理 WebView 资源 + _kr_cleanupDesktopResources(); + } + + super.onClose(); + } + + /// 清理桌面平台资源 + Future _kr_cleanupDesktopResources() async { + try { + if (_kr_initializationCompleter != null && + !_kr_initializationCompleter!.isCompleted) { + try { + await _kr_initializationCompleter!.future.timeout( + const Duration(milliseconds: 500), + onTimeout: () { + if (!_kr_initializationCompleter!.isCompleted) { + _kr_initializationCompleter!.complete(); + } + }, + ); + } catch (e) { + // 忽略超时错误 + } + } + + if (kr_isInitialized.value || kr_isLoading.value) { + crispController = null; + kr_isInitialized.value = false; + kr_isLoading.value = false; + + if (kDebugMode) { + print('[P15-Desktop] Crisp WebView 资源已清理'); + } + } + } catch (e) { + if (kDebugMode) { + print('[P15-Desktop] 清理资源时出错: $e'); + } } } } diff --git a/lib/app/modules/kr_crisp_chat/views/kr_crisp_view.dart b/lib/app/modules/kr_crisp_chat/views/kr_crisp_view.dart index 7d75444..a6c1081 100755 --- a/lib/app/modules/kr_crisp_chat/views/kr_crisp_view.dart +++ b/lib/app/modules/kr_crisp_chat/views/kr_crisp_view.dart @@ -1,95 +1,114 @@ import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:get/get.dart'; -import 'package:crisp_sdk/crisp_sdk.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'dart:io' show Platform; import '../controllers/kr_crisp_controller.dart'; import 'package:kaer_with_panels/app/localization/app_translations.dart'; import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; import '../../../widgets/kr_simple_loading.dart'; -import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart'; -import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; +// 桌面平台使用 WebView SDK +import 'package:crisp_sdk/crisp_sdk.dart' as webview_crisp; -/// Crisp 客服聊天视图 +/// 🔧 P15 重构: Crisp 客服聊天视图 +/// - 移动平台 (Android/iOS): 打开原生聊天界面后自动返回 +/// - 桌面平台 (macOS/Windows): 在页面内嵌入 WebView class KRCrispView extends GetView { const KRCrispView({Key? key}) : super(key: key); + /// 是否为移动平台 + bool get _isMobilePlatform => Platform.isAndroid || Platform.isIOS; + @override Widget build(BuildContext context) { - return PopScope( - onPopInvokedWithResult: (didPop, result) async { - if (didPop) { - await controller.kr_cleanupResources(); - } - }, - child: Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - appBar: _kr_buildAppBar(context), - body: _kr_buildBody(context), - ), + return Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + appBar: _kr_buildAppBar(context), + body: _kr_buildBody(context), ); } /// 构建导航栏 PreferredSizeWidget _kr_buildAppBar(BuildContext context) { return AppBar( - backgroundColor: Colors.transparent, - toolbarHeight: 0.0, + backgroundColor: Theme.of(context).cardColor, elevation: 0, - automaticallyImplyLeading: false, // 1. 禁用自动生成的 leading - // title: Text( - // AppTranslations.kr_userInfo.customerService, - // style: KrAppTextStyle( - // color: Theme.of(context).textTheme.bodyMedium?.color, - // fontSize: 16, - // fontWeight: FontWeight.w500, - // ), - // ), + title: Text( + AppTranslations.kr_userInfo.customerService, + style: KrAppTextStyle( + color: Theme.of(context).textTheme.bodyMedium?.color, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + size: 20.sp, + color: Theme.of(context).textTheme.bodyMedium?.color, + ), + onPressed: () => Get.back(), + ), ); } /// 构建主体内容 Widget _kr_buildBody(BuildContext context) { - return Stack( - children: [ - // --- 第一层:WebView 内容 --- - // Obx 包裹的是 CrispView 和其他状态视图 - Obx(() { - if (controller.kr_isLoading.value) { - return _kr_buildLoadingView(context, '正在初始化客服系统...'); + return Container( + color: Theme.of(context).scaffoldBackgroundColor, + child: Obx(() { + // ========== 移动平台 ========== + if (_isMobilePlatform) { + if (!controller.kr_isInitialized.value) { + return _kr_buildErrorView(context); } + // 显示加载中界面,同时原生聊天界面在后台启动 + return _kr_buildLoadingView(context, '正在加载客服'); + } - if (controller.kr_isInitialized.value && controller.crispController != null) { - return CrispView( - crispController: controller.crispController!, - clearCache: true, - onSessionIdReceived: _kr_onSessionIdReceived, - ); - } + // ========== 桌面平台 ========== + if (controller.kr_isLoading.value) { + return _kr_buildLoadingView(context, '正在初始化客服系统'); + } - return _kr_buildErrorView(context); - }), + if (controller.kr_isInitialized.value && controller.crispController != null) { + // 桌面平台:嵌入 WebView + return webview_crisp.CrispView( + crispController: controller.crispController!, + clearCache: true, + onSessionIdReceived: _kr_onSessionIdReceived, + ); + } - // --- 第二层:悬浮的返回按钮 --- - Positioned( - // 4. 使用 MediaQuery 获取状态栏高度,确保按钮不会与系统UI重叠 - top: 40.w, - left: 20.w, - child: GestureDetector( - onTap: () => _kr_handleBack(), - behavior: HitTestBehavior.translucent, - child: KrLocalImage( - imageName: 'hi-back-icon', - width: 48.w, - height: 48.w, - imageType: ImageType.svg, - ), - ), - ), - ], + return _kr_buildErrorView(context); + }), ); } + /// 打开原生 Crisp 聊天界面 (移动平台) + void _kr_openNativeCrispChat(BuildContext context) async { + if (!controller.kr_isInitialized.value) { + if (kDebugMode) { + print('[P15-Mobile] Crisp 未初始化,无法打开聊天'); + } + return; + } + + try { + if (kDebugMode) { + print('[P15-Mobile] 正在打开原生 Crisp 聊天界面...'); + } + + // 打开原生聊天界面 + await controller.kr_openNativeCrispChat(); + } catch (e) { + if (kDebugMode) { + print('[P15-Mobile] 打开 Crisp 聊天时出错: $e'); + } + } + } + /// 构建加载视图 Widget _kr_buildLoadingView(BuildContext context, String message) { return Center( @@ -97,7 +116,7 @@ class KRCrispView extends GetView { mainAxisAlignment: MainAxisAlignment.center, children: [ KRSimpleLoading( - color: Theme.of(context).primaryColor, + color: Colors.blue, size: 50.0, ), if (message.isNotEmpty) SizedBox(height: 16.sp), @@ -127,7 +146,7 @@ class KRCrispView extends GetView { ), SizedBox(height: 16.sp), Text( - '客服系统初始化失败', + 'crisp.initFailed'.tr, style: KrAppTextStyle( fontSize: 16, color: Colors.red, @@ -136,23 +155,21 @@ class KRCrispView extends GetView { SizedBox(height: 24.sp), ElevatedButton( onPressed: () { - controller.kr_initializeCrisp(); + if (_isMobilePlatform) { + _kr_openNativeCrispChat(context); + } else { + controller.kr_initializeDesktopCrisp(); + } }, - child: Text('重试'), + child: Text('common.retry'.tr), ), ], ), ); } - /// 处理返回事件 - Future _kr_handleBack() async { - await controller.kr_cleanupResources(); - Get.back(); - } - - /// 处理会话 ID 接收事件 + /// 处理会话 ID 接收事件 (桌面平台) void _kr_onSessionIdReceived(String sessionId) { - debugPrint('Crisp 会话 ID: $sessionId'); + debugPrint('[P15-Desktop] Crisp 会话 ID: $sessionId'); } } diff --git a/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart b/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart index e1b8357..b2a8aad 100755 --- a/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart +++ b/lib/app/modules/kr_purchase_membership/controllers/kr_purchase_membership_controller.dart @@ -60,6 +60,8 @@ class KRPurchaseMembershipController extends GetxController { @override void onInit() async { super.onInit(); + // 预热客服 + KRCommonUtil.kr_warmUpCustomerService(); print( '💳 [PurchaseMembership] ========== Controller.onInit 被调用 =========='); print('💳 [PurchaseMembership] 当前时间: ${DateTime.now()}'); diff --git a/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart b/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart index 2e5b1a6..5906c25 100755 --- a/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart +++ b/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart @@ -20,6 +20,7 @@ import 'dart:convert'; import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart'; import '../../../routes/app_pages.dart'; import 'package:kaer_with_panels/app/services/iap/iap_service.dart'; +import 'package:kaer_with_panels/app/utils/kr_common_util.dart'; @@ -216,7 +217,7 @@ class KRPurchaseMembershipView extends GetView barrierDismissible: false, preventBackDismiss: true, onConfirm: () async { - Get.toNamed(Routes.KR_CRISP); + KRCommonUtil.kr_openCustomerService(); }, ); }, diff --git a/lib/app/utils/kr_common_util.dart b/lib/app/utils/kr_common_util.dart index b956ddb..7f2caea 100755 --- a/lib/app/utils/kr_common_util.dart +++ b/lib/app/utils/kr_common_util.dart @@ -1,7 +1,112 @@ +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../widgets/kr_toast.dart'; +import '../common/app_config.dart'; +import '../routes/app_pages.dart'; +import '../widgets/dialogs/hi_dialog.dart'; +import '../modules/kr_crisp_chat/controllers/kr_crisp_controller.dart'; class KRCommonUtil { + /// 是否正在打开客服(防重复点击) + static bool _kr_isOpeningCustomerService = false; + + /// 预热客服(提前初始化控制器) + static void kr_warmUpCustomerService() { + if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { + if (!Get.isRegistered()) { + Get.put(KRCrispController(), permanent: true); + debugPrint('客服系统预热中(持久模式)...'); + } + } + } + + /// 打开客服 + static Future kr_openCustomerService() async { + // 🔧 防重复点击 + if (_kr_isOpeningCustomerService) { + return; + } + + final websiteId = AppConfig.getInstance().kr_website_id; + if (websiteId.isEmpty) { + HIDialog.show( + title: '提示', + message: '加载失败', + ); + return; + } + + // 🔧 Windows 端直接跳转外部浏览器 + if (Platform.isWindows) { + _kr_isOpeningCustomerService = true; + + // 显示加载提示 + kr_showToast('userInfo.openingCustomerService'.tr); + + // 直接使用 zh + final Uri url = Uri.parse( + 'https://go.crisp.chat/chat/embed/?website_id=$websiteId&locale=zh', + ); + + try { + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } else { + kr_showToast("common.cannotOpenBrowser".tr); + } + } catch (e) { + kr_showToast("common.openLinkFailed".tr); + } finally { + // 延迟重置状态 + Future.delayed(const Duration(seconds: 2), () { + _kr_isOpeningCustomerService = false; + }); + } + return; + } + + // 移动平台 (Android/iOS):直接调用原生 SDK,避免页面跳转闪烁 + if (Platform.isAndroid || Platform.isIOS) { + _kr_isOpeningCustomerService = true; + + // 预先显示加载提示(可选,原生 SDK 启动很快) + // kr_showToast('userInfo.openingCustomerService'.tr); + + try { + // 优先使用已注册的控制器 + KRCrispController crispController; + if (Get.isRegistered()) { + crispController = Get.find(); + } else { + crispController = Get.put(KRCrispController(), permanent: true); + } + // 直接调用原生打开方法 + await crispController.kr_openNativeCrispChat(); + } catch (e) { + debugPrint('打开原生客服失败: $e'); + // 如果原生失败,可以考虑降级到内置页面或报错 + Get.toNamed(Routes.KR_CRISP); + } finally { + Future.delayed(const Duration(seconds: 2), () { + _kr_isOpeningCustomerService = false; + }); + } + return; + } + + // macOS/桌面平台:跳转内置页面 (WebView 实现) + _kr_isOpeningCustomerService = true; + + Get.toNamed(Routes.KR_CRISP); + + // 延迟重置状态 + Future.delayed(const Duration(seconds: 2), () { + _kr_isOpeningCustomerService = false; + }); + } + /// 提示 meesage: 提示内容, toastPosition: 提示显示的位置, timeout: 显示时间(毫秒) static kr_showToast(String message, {KRToastPosition toastPosition = KRToastPosition.center, diff --git a/pubspec.lock b/pubspec.lock index d64a561..00a749c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -241,6 +241,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + crisp_chat: + dependency: "direct main" + description: + name: crisp_chat + sha256: "63be64c416b5b44bb099bbd278b8de718714075e5ac9d444272dcf99e48eda3d" + url: "https://pub.dev" + source: hosted + version: "2.4.3" crisp_sdk: dependency: "direct main" description: @@ -780,10 +788,10 @@ packages: dependency: transitive description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.0" http2: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 928bb68..49ce515 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,7 +73,8 @@ dependencies: window_manager: ^0.4.3 url_launcher: ^6.3.1 flutter_inappwebview: ^6.1.5 # 最新稳定版本 - crisp_sdk: ^1.1.0 # 使用 crisp_sdk,配合最新的 flutter_inappwebview + crisp_chat: ^2.4.3 # 🔧 P15: 移动平台使用原生 SDK,解决 WebView 黑屏问题 + crisp_sdk: ^1.1.0 # 🔧 P15: 桌面平台继续使用 WebView 实现 protocol_handler_windows: ^0.2.0 share_plus: ^7.2.2