You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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属性也未得到有效结果。

核心错误原因

  1. 未加载Power BI JavaScript SDK:代码中直接调用window['powerbi-client'].models,但页面未引入Power BI官方SDK,导致无法找到models对象。
  2. 未定义变量引用:代码中使用了EMBED_ACCESS_TOKEN、TOKEN_TYPE、embedReportId三个未定义变量,导致后续逻辑中断。
  3. 容器元素获取错误:document.querySelector('#embedContainer')[0]写法错误,querySelector返回单个元素,不需要索引访问。
  4. 嵌入方式冲突:同时使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:37:02