fix:第一轮跑

This commit is contained in:
jingyun committed 2026-08-24 19:07:31 +08:00
1 parent e834ec7586
commit 33235f450b
56 files changed
+4357 -210

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
/// Shared HTTPS WebView host. Mall-specific residency and attribution are
/// layered above this page so ordinary H5 links never receive mall state.
class AppWebPage extends StatefulWidget {
const AppWebPage({
super.key,
required this.initialUri,
this.hideTitle = false,
this.allowedHosts = const <String>{},
});
final Uri initialUri;
final bool hideTitle;
final Set<String> allowedHosts;
@override
State<AppWebPage> createState() => _AppWebPageState();
}
class _AppWebPageState extends State<AppWebPage> {
late final WebViewController _controller;
var _progress = 0;
String? _error;
String _title = '';
@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'JinzhiBridge',
onMessageReceived: (message) {
if (message.message == 'close' && mounted) context.pop();
},
)
..setNavigationDelegate(
NavigationDelegate(
onProgress: (progress) => setState(() => _progress = progress),
onPageFinished: (_) async {
final title = await _controller.getTitle();
if (mounted && title != null && title.trim().isNotEmpty) {
setState(() => _title = title.trim());
}
},
onWebResourceError: (error) {
if (error.isForMainFrame == true && mounted) {
setState(() => _error = error.description);
}
},
onNavigationRequest: _onNavigationRequest,
),
)
..loadRequest(widget.initialUri);
}
NavigationDecision _onNavigationRequest(NavigationRequest request) {
final uri = Uri.tryParse(request.url);
if (uri == null || !_isHttp(uri)) return NavigationDecision.prevent;
if (_allowsInApp(uri)) return NavigationDecision.navigate;
launchUrl(uri, mode: LaunchMode.externalApplication);
return NavigationDecision.prevent;
}
bool _allowsInApp(Uri uri) {
final host = uri.host.toLowerCase();
if (host == widget.initialUri.host.toLowerCase()) return true;
return widget.allowedHosts.any(
(allowed) => host == allowed || host.endsWith('.$allowed'),
);
}
bool _isHttp(Uri uri) =>
uri.hasAuthority && (uri.scheme == 'https' || uri.scheme == 'http');
Future<bool> _onPop() async {
if (await _controller.canGoBack()) {
await _controller.goBack();
return false;
}
return true;
}
@override
Widget build(BuildContext context) {
final body = _error == null
? Stack(
children: [
WebViewWidget(controller: _controller),
if (_progress < 100)
LinearProgressIndicator(value: _progress / 100),
],
)
: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text('页面加载失败\n$_error', textAlign: TextAlign.center),
),
);
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) async {
if (!didPop && await _onPop() && context.mounted) context.pop();
},
child: Scaffold(
appBar: widget.hideTitle
? null
: AppBar(title: Text(_title.isEmpty ? '加载中' : _title)),
body: body,
),
);
}
}
Uri? parseSafeWebUri(String? raw) {
final uri = Uri.tryParse(raw?.trim() ?? '');
if (uri == null || !uri.hasAuthority) return null;
return uri.scheme == 'https' || uri.scheme == 'http' ? uri : null;
}