如何在Flutter WebView中嵌入Power BI移动端版本?问题求助
Flutter WebView嵌入Power BI移动端布局问题解决
问题概述
使用Flutter的webview_flutter:4.7.0或flutter_inappwebview:6.0.0嵌入Power BI报表,尝试通过layoutType: models.LayoutType.MobilePortrait实现移动端竖屏布局时,出现Uncaught TypeError: Cannot read properties of undefined (reading 'models')错误,且无法按预期显示Power BI内容;直接设置iframe的src属性也未得到有效结果。
核心错误原因
- 未加载Power BI JavaScript SDK:代码中直接调用
window['powerbi-client'].models,但页面未引入Power BI官方SDK,导致无法找到models对象。 - 未定义变量引用:代码中使用了
EMBED_ACCESS_TOKEN、TOKEN_TYPE、embedReportId三个未定义变量,导致后续逻辑中断。 - 容器元素获取错误:
document.querySelector('#embedContainer')[0]写法错误,querySelector返回单个元素,不需要索引访问。 - 嵌入方式冲突:同时使用iframe直接加载
embedUrl和powerbi.embed方法,两种嵌入方式冲突,导致配置无法生效。
解决方案步骤
1. 引入Power BI JavaScript SDK
在HTML的<head>标签中添加Power BI SDK的CDN引用:
<script src="https://microsoft.github.io/PowerBI-JavaScript/dist/powerbi.min.js"></script>
2. 替换未定义变量
将EMBED_ACCESS_TOKEN、TOKEN_TYPE、embedReportId替换为实际有效值:
EMBED_ACCESS_TOKEN:你的Power BI嵌入令牌TOKEN_TYPE:根据令牌类型设为'0'(Aad)或'1'(Embed)embedReportId:要嵌入的报表ID
3. 修正容器获取逻辑
将let embedContainer = document.querySelector('#embedContainer')[0];改为:
let embedContainer = document.querySelector('#embedContainer');
4. 移除iframe,使用纯容器嵌入
删除HTML中的iframe,保留空的embedContainer div,由powerbi.embed方法负责渲染内容。
5. 确保WebView配置正确
保证WebView开启JavaScript支持,允许跨域资源加载(Power BI需要加载外部资源)。
修改后的完整代码
Flutter部分
import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; void main() => runApp(const MaterialApp(home: WebViewExample(), debugShowCheckedModeBanner: false)); const String kLocalExamplePage = ''' <!DOCTYPE html> <html lang="en"> <head> <title>Test Bi</title> <!-- 引入Power BI SDK --> <script src="https://microsoft.github.io/PowerBI-JavaScript/dist/powerbi.min.js"></script> </head> <body> <!-- 空容器,用于嵌入Power BI报表 --> <div id="embedContainer" style="width: 100%; height: 100vh;"></div> <script> let loadedResolve, reportLoaded = new Promise((res, rej) => { loadedResolve = res; }); let renderedResolve, reportRendered = new Promise((res, rej) => { renderedResolve = res; }); // 等待SDK加载完成 document.addEventListener('DOMContentLoaded', function() { const models = window.powerbi.models; // 替换为实际的嵌入令牌 let accessToken = 'YOUR_EMBED_ACCESS_TOKEN'; // 报表的嵌入URL let embedUrl = 'https://app.powerbi.com/view?r=eyJrIjoiYzZmM2ViNGEtNzI2OS00YTUyLThjMDUtNTViOWU5ZTAyN2IwIiwidCI6ImM1ZDI4NTZmLWJlOTYtNGQwZi1hY2FiLWZlOTJkZTY2MjhiZCJ9'; // 令牌类型:0为Aad,1为Embed let tokenType = '1'; // 权限设置 let permissions = models.Permissions.All; let config = { type: 'report', tokenType: tokenType == '0' ? models.TokenType.Aad : models.TokenType.Embed, accessToken: accessToken, embedUrl: embedUrl, id: 'YOUR_REPORT_ID', // 替换为实际报表ID permissions: permissions, settings: { layoutType: models.LayoutType.MobilePortrait, panes: { filters: { visible: false }, pageNavigation: { visible: false } }, bars: { statusBar: { visible: false } } } }; // 获取容器元素 let embedContainer = document.querySelector('#embedContainer'); // 嵌入报表 const report = powerbi.embed(embedContainer, config); // 监听加载事件 report.off("loaded"); report.on("loaded", function () { loadedResolve(); report.off("loaded"); console.log('报表加载完成'); }); // 监听错误事件 report.off("error"); report.on("error", function (event) { console.error('报表加载错误:', event.detail); }); // 监听渲染完成事件 report.off("rendered"); report.on("rendered", function () { renderedResolve(); report.off("rendered"); console.log('报表渲染完成'); }); }); </script> </body> </html> '''; class WebViewExample extends StatefulWidget { const WebViewExample({super.key}); @override State<WebViewExample> createState() => _WebViewExampleState(); } class _WebViewExampleState extends State<WebViewExample> { late final WebViewController _controller; @override void initState() { super.initState(); late final PlatformWebViewControllerCreationParams params; if (WebViewPlatform.instance is WebKitWebViewPlatform) { params = WebKitWebViewControllerCreationParams( allowsInlineMediaPlayback: true, mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{}, ); } else { params = const PlatformWebViewControllerCreationParams(); } final WebViewController controller = WebViewController.fromPlatformCreationParams(params); controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate( NavigationDelegate( onProgress: (int progress) { debugPrint('WebView加载进度: $progress%'); }, onPageStarted: (String url) { debugPrint('页面开始加载: $url'); }, onPageFinished: (String url) { debugPrint('页面加载完成: $url'); }, onWebResourceError: (WebResourceError error) { debugPrint(''' 资源加载错误: 错误码: ${error.errorCode} 描述: ${error.description} 错误类型: ${error.errorType} 是否主框架: ${error.isForMainFrame} '''); }, onNavigationRequest: (NavigationRequest request) { debugPrint('允许导航到: ${request.url}'); return NavigationDecision.navigate; }, ), ) ..addJavaScriptChannel( 'Toaster', onMessageReceived: (JavaScriptMessage message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }, ) ..loadHtmlString(kLocalExamplePage); if (controller.platform is AndroidWebViewController) { AndroidWebViewController.enableDebugging(true); (controller.platform as AndroidWebViewController).setMediaPlaybackRequiresUserGesture(false); } _controller = controller; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, backgroundColor: Colors.green.shade700, title: const Text( 'Power BI测试', style: TextStyle(color: Colors.white), ), ), body: WebViewWidget(controller: _controller), ); } }
额外注意事项
- 确保你的Power BI嵌入令牌有效,且权限足够访问目标报表。
- 如果使用
flutter_inappwebview,配置逻辑类似,需确保开启JavaScript和允许跨域。 - 移动端测试时,可开启WebView调试(Android端已在代码中开启),通过Chrome DevTools查看控制台错误,方便排查问题。
内容的提问来源于stack exchange,提问作者Eduardo Henrique
相关产品推荐
相关产品推荐

