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

如何通过Qlik Sense API直接导出指定工作表PDF,无需点击下载链接?

Qlik Sense API 自动导出并下载工作表PDF(无需手动点击)

问题背景

目前通过Qlik Sense API,可依据应用ID(appId)和工作表ID(sheetId)导出PDF,但导出后必须手动点击链接才能触发下载,无法跳过该操作。需求是通过API直接获取指定工作表的PDF文件并以附件形式自动下载,无需手动交互。

解决方案

原代码中模拟点击隐藏链接的方式可能被部分浏览器拦截,我们可以通过Fetch API获取PDF的Blob对象再触发下载,这种方式兼容性更强,能有效避免浏览器拦截问题。修改后的完整代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Qlik Sense 自动导出PDF</title>
    <meta charset="utf-8">
    <script src="https://pa-qliksense.aviv.gov.il/sense/app/appId"></script>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta name="HandheldFriendly" content="True">
    <meta name="MobileOptimized" content="320">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta http-equiv="cleartype" content="on">
    <script src="../../resources/assets/external/requirejs/require.js"></script>
    <style>
        /* 加载动画CSS */
        .spinner {
            border: 16px solid #f3f3f3;
            border-radius: 50%;
            border-top: 16px solid #3498db;
            width: 120px;
            height: 120px;
            animation: spin 2s linear infinite;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        #spinnerContainer {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(255, 255, 255, 0.8);
            z-index: 9999;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        #spinnerContainer.hidden {
            display: none;
        }
    </style>
</head>
<body style="overflow: auto">
    <div id="spinnerContainer">
        <div class="spinner"></div>
    </div>
    <div id="CurrentSelections" class="qvobjects" style="position:relative; top:0; left:0; width:100%; height:38px;"></div>
    <div id="downloadLinks" style="margin-top: 20px;"></div>

    <!-- 页面加载时自动触发导出 -->
    <script>
        window.onload = function () {
            var urlParams = new URLSearchParams(window.location.search);
            var appId = urlParams.get('appId');
            var sheetId = urlParams.get('sheetId');
            // 批量整理筛选参数,过滤空值
            const fieldParams = Array.from({length:8}, (_,i) => ({
                name: urlParams.get(`fieldName${i+1}`),
                value: urlParams.get(`fieldValue${i+1}`)
            })).filter(item => item.name && item.value);

            exportData(appId, sheetId, fieldParams);
        };

        function exportData(appId, sheetId, fieldParams) {
            var prefix = window.location.pathname.substr(0, window.location.pathname.toLowerCase().lastIndexOf("/extensions") + 1);
            var config = {
                host: window.location.hostname,
                prefix: prefix,
                port: window.location.port,
                isSecure: window.location.protocol === "https:"
            };

            require.config({
                baseUrl: (config.isSecure ? "https://" : "http://") + config.host + (config.port ? ":" + config.port : "") + config.prefix + "resources"
            });

            require(["js/qlik"], function (qlik) {
                qlik.setOnError(function (error) {
                    console.error('错误:', error);
                    alert('错误: ' + error.message);
                });
                qlik.theme.apply('horizon');

                // 打开目标应用
                var app = qlik.openApp(appId, config);

                // 清除原有筛选后应用新条件
                app.clearAll().then(function () {
                    // 批量执行筛选操作,确保所有条件生效后再导出
                    Promise.all(fieldParams.map(param => 
                        app.field(param.name).selectValues([param.value], true, false)
                    )).then(() => {
                        // 获取工作表对象并导出PDF
                        app.visualization.get(sheetId).then(function (object) {
                            object.exportPdf({
                                documentSize: 'A4',
                                aspectRatio: 0,
                                orientation: "landscape",
                                objectSize: { height: 1280, width: 1920 }
                            }).then(function (pdfExportUrl) {
                                // 通过Fetch获取PDF文件Blob,自动触发下载
                                fetch(pdfExportUrl, { credentials: 'include' })
                                    .then(response => response.blob())
                                    .then(blob => {
                                        const tempUrl = window.URL.createObjectURL(blob);
                                        const downloadLink = document.createElement('a');
                                        downloadLink.href = tempUrl;
                                        downloadLink.download = '导出的仪表盘.pdf';
                                        document.body.appendChild(downloadLink);
                                        downloadLink.click();
                                        // 清理临时资源
                                        window.URL.revokeObjectURL(tempUrl);
                                        document.body.removeChild(downloadLink);
                                        // 隐藏加载动画
                                        document.getElementById('spinnerContainer').classList.add('hidden');
                                    })
                                    .catch(fetchError => {
                                        console.error('获取PDF文件失败:', fetchError);
                                        alert('获取PDF文件失败,请查看控制台详情');
                                        document.getElementById('spinnerContainer').classList.add('hidden');
                                    });
                            }).catch(function (pdfError) {
                                console.error('导出PDF失败:', pdfError);
                                alert('导出PDF失败,请查看控制台详情');
                                document.getElementById('spinnerContainer').classList.add('hidden');
                            });
                        }).catch(function (error) {
                            console.error('获取工作表对象失败:', error);
                            alert('获取工作表对象失败,请查看控制台详情');
                            document.getElementById('spinnerContainer').classList.add('hidden');
                        });
                    }).catch(selectError => {
                        console.error('应用筛选条件失败:', selectError);
                        alert('应用筛选条件失败,请查看控制台详情');
                        document.getElementById('spinnerContainer').classList.add('hidden');
                    });
                }).catch(function (error) {
                    console.error('清除筛选失败:', error);
                    alert('清除筛选失败,请查看控制台详情');
                    document.getElementById('spinnerContainer').classList.add('hidden');
                });
            });
        }
    </script>
</body>
</html>

关键修改点

  1. 筛选参数优化:将原有的8个单独参数改为批量处理,过滤空值后统一应用,代码更简洁易维护
  2. 下载逻辑升级:使用Fetch API获取PDF的Blob对象,通过临时URL触发下载,避免浏览器拦截非用户交互的点击事件
  3. 权限保障:Fetch请求中加入credentials: 'include',确保Qlik Sense的会话权限正常传递
  4. 异步流程优化:用Promise.all确保所有筛选条件生效后再执行导出,避免数据不一致问题

注意事项

  • 确保当前页面与Qlik Sense服务器同源,或服务器已配置跨域允许规则
  • 部分严格模式浏览器可能仍会限制自动下载,内部系统环境下该方案可稳定运行;公网环境可添加一个初始触发按钮,由用户主动点击后启动导出流程

内容的提问来源于stack exchange,提问作者Neomi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 17:44:51