初始化提交

This commit is contained in:
2025-09-23 16:23:15 +08:00
commit 877b18a70f
590 changed files with 53617 additions and 0 deletions
@@ -0,0 +1,11 @@
import 'package:get/get.dart';
import '../controllers/kr_webview_controller.dart';
class KRWebViewBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<KRWebViewController>(
() => KRWebViewController(),
);
}
}
@@ -0,0 +1,192 @@
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;
import 'package:kaer_with_panels/app/services/api_service/api.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_web_api.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
/// WebView 控制器
/// 用于管理 WebView 的状态和行为
class KRWebViewController extends GetxController {
// 页面加载状态
final RxBool kr_isLoading = true.obs;
// 页面标题
final RxString kr_title = ''.obs;
// WebView 控制器
late final WebViewController kr_webViewController;
// 默认URL
static const String kr_defaultUrl = '';
final String kr_url = Get.arguments['url'] as String;
// Web API 实例
final KRWebApi _kr_webApi = KRWebApi();
// 内容类型
final RxBool kr_isHtml = false.obs;
final RxBool kr_isMarkdown = false.obs;
final RxString kr_content = ''.obs;
@override
void onInit() {
super.onInit();
// 根据 URL 类型决定初始化方式
if (kr_url.contains(Api.kr_getSiteTos) || kr_url.contains(Api.kr_getSitePrivacy)) {
// 用户协议和隐私政策页面,直接获取文本内容
if (kr_url.contains(Api.kr_getSiteTos)) {
kr_title.value = AppTranslations.kr_login.termsOfService;
} else {
kr_title.value = AppTranslations.kr_login.privacyPolicy;
}
kr_getWebText();
} else {
// 其他页面,初始化 WebView
kr_initWebView();
}
}
/// 初始化 WebView
void kr_initWebView() {
kr_webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onNavigationRequest: (NavigationRequest request) async {
// 只在移动平台处理支付应用跳转
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
KRLogUtil.kr_i('处理支付链接: ${request.url}', tag: 'WebViewController');
// 处理支付链接
if (await kr_handleUrlLaunch(request.url)) {
return NavigationDecision.prevent;
}
}
return NavigationDecision.navigate;
},
onPageStarted: kr_handlePageStarted,
onPageFinished: kr_handlePageFinished,
),
);
// 检查是否是用户协议或隐私政策
if (kr_url.contains(Api.kr_getSiteTos) || kr_url.contains(Api.kr_getSitePrivacy)) {
kr_getWebText();
} else {
kr_webViewController.loadRequest(Uri.parse(kr_url));
}
}
/// 获取网页文本内容并加载到 WebView
Future<void> kr_getWebText() async {
try {
final response = await _kr_webApi.kr_getWebText(kr_url);
response.fold(
(error) async {
KRLogUtil.kr_e('获取网页内容失败: $error', tag: 'WebViewController');
// 如果获取失败,直接设置错误内容
kr_content.value = 'Failed to load, please try again later';
kr_isLoading.value = false;
},
(content) async {
KRLogUtil.kr_i('获取到内容: $content', tag: 'WebViewController');
// 判断内容类型,优先判断 Markdown
kr_isMarkdown.value = content.contains('**') ||
content.contains('*') ||
content.contains('#') ||
content.contains('- ') ||
content.contains('[');
kr_isHtml.value = !kr_isMarkdown.value && content.contains('<') && content.contains('>');
KRLogUtil.kr_i('内容类型 - Markdown: ${kr_isMarkdown.value}, HTML: ${kr_isHtml.value}', tag: 'WebViewController');
if (kr_isMarkdown.value) {
// 如果是 Markdown 内容,直接使用
kr_content.value = content;
} else if (kr_isHtml.value) {
// 如果是 HTML 内容,直接使用
kr_content.value = content;
} else {
// 如果是普通文本,直接使用
kr_content.value = content;
}
kr_isLoading.value = false;
},
);
} catch (e) {
KRLogUtil.kr_e('获取网页内容出错: $e', tag: 'WebViewController');
kr_content.value = 'Loading error, please try again later';
kr_isLoading.value = false;
}
}
/// 处理页面开始加载事件
void kr_handlePageStarted(String url) {
kr_isLoading.value = true;
}
/// 处理页面加载完成事件
void kr_handlePageFinished(String url) async {
kr_isLoading.value = false;
await kr_updateTitle();
}
/// 更新页面标题
Future<void> kr_updateTitle() async {
final String? kr_pageTitle = await kr_webViewController.getTitle();
kr_title.value = kr_pageTitle ?? '';
}
/// 重新加载页面
Future<void> kr_reloadPage() async {
await kr_webViewController.reload();
}
/// 加载新的URL
Future<void> kr_loadUrl(String url) async {
await kr_webViewController.loadRequest(Uri.parse(url));
}
/// 处理URL启动
Future<bool> kr_handleUrlLaunch(String url) async {
try {
KRLogUtil.kr_i('正在处理URL跳转: $url', tag: 'WebViewController');
final uri = Uri.parse(url);
// 处理支付应用和外部链接
if (uri.scheme == 'alipays' ||
uri.scheme == 'alipay' ||
uri.scheme == 'weixin' ||
uri.scheme == 'wx') {
KRLogUtil.kr_i('检测到支付应用scheme: ${uri.scheme}', tag: 'WebViewController');
// 尝试打开支付应用
if (await canLaunchUrl(uri)) {
return await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
}
// 如果支付应用无法打开,尝试使用外部浏览器打开
final httpUri = Uri.parse('https://${uri.host}${uri.path}?${uri.query}');
KRLogUtil.kr_i('尝试使用浏览器打开: $httpUri', tag: 'WebViewController');
if (await canLaunchUrl(httpUri)) {
return await launchUrl(
httpUri,
mode: LaunchMode.externalApplication,
);
}
KRLogUtil.kr_e('无法启动URL: $url', tag: 'WebViewController');
}
return false;
} catch (e) {
KRLogUtil.kr_e('URL跳转错误: $e', tag: 'WebViewController');
return false;
}
}
}
+177
View File
@@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import '../../../widgets/kr_app_text_style.dart';
import '../controllers/kr_webview_controller.dart';
import '../../../services/api_service/api.dart';
import '../../../utils/kr_log_util.dart';
/// WebView 页面组件
class KRWebView extends GetView<KRWebViewController> {
const KRWebView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
size: 20.sp,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
onPressed: () => Get.back(),
),
centerTitle: true,
title: Text(
controller.kr_title.value,
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
body: _buildBody(),
);
}
/// 构建主体内容
Widget _buildBody() {
return Stack(
children: [
_buildContent(),
_buildLoadingIndicator(),
],
);
}
/// 构建内容组件
Widget _buildContent() {
if (controller.kr_url.contains(Api.kr_getSiteTos) ||
controller.kr_url.contains(Api.kr_getSitePrivacy)) {
return _buildProtocolContent();
} else {
return _buildWebView();
}
}
/// 构建协议内容
Widget _buildProtocolContent() {
return Obx(() {
if (controller.kr_isHtml.value) {
return SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Html(
data: controller.kr_content.value,
style: {
'body': Style(
margin: Margins.all(0),
padding: HtmlPaddings.all(0),
fontSize: FontSize(14.sp),
color: Theme.of(Get.context!).textTheme.bodySmall?.color,
fontFamily: 'AlibabaPuHuiTi-Regular',
lineHeight: LineHeight(1.4),
),
'p': Style(
margin: Margins.only(bottom: 8.h),
),
'b': Style(
fontWeight: FontWeight.bold,
),
'i': Style(
fontStyle: FontStyle.italic,
),
'a': Style(
color: Colors.blue,
textDecoration: TextDecoration.underline,
),
},
shrinkWrap: true,
),
);
} else if (controller.kr_isMarkdown.value) {
return SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: MarkdownBody(
data: controller.kr_content.value,
styleSheet: MarkdownStyleSheet(
p: TextStyle(
fontSize: 14.sp,
color: Theme.of(Get.context!).textTheme.bodySmall?.color,
fontFamily: 'AlibabaPuHuiTi-Regular',
height: 1.4,
),
strong: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Theme.of(Get.context!).textTheme.bodySmall?.color,
fontFamily: 'AlibabaPuHuiTi-Regular',
height: 1.4,
),
em: TextStyle(
fontSize: 14.sp,
fontStyle: FontStyle.italic,
color: Theme.of(Get.context!).textTheme.bodySmall?.color,
fontFamily: 'AlibabaPuHuiTi-Regular',
height: 1.4,
),
a: TextStyle(
fontSize: 14.sp,
color: Colors.blue,
decoration: TextDecoration.underline,
fontFamily: 'AlibabaPuHuiTi-Regular',
height: 1.4,
),
),
),
);
} else {
return SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Text(
controller.kr_content.value,
style: TextStyle(
fontSize: 14.sp,
color: Theme.of(Get.context!).textTheme.bodySmall?.color,
fontFamily: 'AlibabaPuHuiTi-Regular',
height: 1.4,
),
),
);
}
});
}
/// 构建 WebView 组件
Widget _buildWebView() {
return WebViewWidget(
controller: controller.kr_webViewController,
);
}
/// 构建加载指示器
Widget _buildLoadingIndicator() {
return Obx(
() => controller.kr_isLoading.value
? const Center(
child: CircularProgressIndicator(),
)
: const SizedBox.shrink(),
);
}
/// 显示错误提示
void _showErrorSnackbar(String title, String message) {
Get.snackbar(
title,
message,
snackPosition: SnackPosition.BOTTOM,
);
}
}