关于 datalens-html-pages
用于编写由 DataLens 在受限沙箱中提供的独立 HTML 报告和页面。它说明资源、安全与交互限制,并提供上传前检查页面的验证脚本。
主要功能
- 01编写独立 HTML 页面
- 02遵循沙箱与 CSP 限制
- 03使用允许列表中的外部资源
- 04通过宿主消息处理导出和链接
- 05用 Python 脚本验证页面
- 06支持主题、语言和移动端
适用场景
- 01创建 DataLens 报告页面
- 02上传前检查 HTML
- 03通过 Yandex Maps JS API 2.1 添加地图
- 04准备适配移动端的页面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ datalens-tech/ datalens-skills --skill datalens-html-pages安装后在 Claude Code 中输入/datalens-html-pages确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/dl-dashboard.cssdl-theme-tokens.cssreport.template.htmlreferences/authoring-constraints.mdcsp-and-sandbox.mddesign-principles.mdpublication.mdscripts/validate_page.py详细介绍
DataLens HTML 页面
DataLens 通过将独立 HTML 页面(通常是 AI 生成的报告等完整文档)存储在私有存储桶中,并通过短 TTL 预签名 URL在锁定的沙盒 iframe中呈现这些页面,同时在上传时以 <meta> 标签注入 Content-Security-Policy。你需要编写完整的 <!DOCTYPE html> 页面;脚本可以运行,但只能来自允许列表中的 CDN,而且大多数浏览器 API 都会被屏蔽。
不属于此技能:图表内部的 HTML——标记函数、HTML 标记表格单元格、
ChartEditor.generateHtml。这是另一项功能,有严格的标签/属性允许列表,且完全不允许脚本。此技能仅适用于独立 HTML 页面。
运行时概览
- 沙盒 iframe:
sandbox="allow-scripts"、allow=""、referrerpolicy="no-referrer",并且至关重要的是不含allow-same-origin——页面在不透明源中运行(父页面也会发送Cross-Origin-Opener-Policy: same-origin)。同源隔离依赖于此;切勿假设可以进行同源访问。 - 注入的 CSP(你无需编写,平台会以幂等方式将其置于页面开头):
default-src 'none',并为script/style/font/img/media提供一个小型允许列表;connect-src仅对 Yandex Maps 主机开放;form-action、frame-src、object-src、worker-src、base-uri均为'none'。 完整策略见 references/csp-and-sandbox.md。 - 页面由预签名 GET 提供,该 URL 会在 10–30 秒后过期,且须先通过服务端权限检查。主题/语言会作为签名查询参数一并传递。
最终效果:**让页面完全自包含。**将自己的 CSS/JS 内联(CSP 允许 'unsafe-inline'/'unsafe-eval'),仅从允许列表中的 CDN 加载库,绝不依赖网络、存储或父页面。
编写规则
允许的外部资源(其他资源都会被 CSP 屏蔽):
| 资源 | 允许来源 |
|---|---|
| 脚本 | cdn.jsdelivr.net、cdnjs.cloudflare.com、cdn.tailwindcss.com、yastatic.net、Yandex Maps(api-maps.yandex.ru、*.api-maps.yandex.ru、*.maps.yandex.net、suggest-maps.yandex.ru)(以及内联脚本) |
| 样式 | 上述 CDN 以及 fonts.googleapis.com、blob:(以及内联样式) |
| 字体 | cdn.jsdelivr.net、cdnjs.cloudflare.com、fonts.gstatic.com、data: |
| 图片 | yastatic.net、Yandex Maps 主机、data:、blob: |
| 媒体 | 仅 data:、blob: |
网络(connect-src) | 仅 Yandex Maps 主机——供 Maps API 本身使用,不能用于你的数据 |
按标准方式加载 Google Fonts(通过 fonts.googleapis.com 加载 <link> CSS,字体文件来自 fonts.gstatic.com)。将允许列表之外的库(unpkg、plot.ly、d3js.org、jQuery)镜像到 jsdelivr/cdnjs。
Yandex Maps JS API 2.1 开箱即用:从 api-maps.yandex.ru 加载(如果用户有 API key,则使用该密钥),并在固定高度的容器中渲染。脚本、地图瓦片和 API 自身的请求均被允许。使用 2.1,不要用 v3——v3 没有密钥就无法加载,并且需要沙盒会屏蔽的 worker。沙盒的其他限制仍然适用——你自己的数据必须内联,不能通过请求获取。
<script src="https://api-maps.yandex.ru/2.1/?apikey=YOUR_KEY&lang=ru_RU"></script>
<div id="map" style="height: 400px"></div>
<script>ymaps.ready(() => new ymaps.Map('map', {center: [55.76, 37.64], zoom: 10}));</script>
已屏蔽——请勿使用(这些操作会报错或静默失败):
- 存储:
localStorage、sessionStorage、indexedDB、document.cookie、Cache API → 将所有状态保存在内存中。 - **网络:**除 Yandex Maps 主机外,禁止将
fetch、XMLHttpRequest、WebSocket、EventSource、sendBeacon用于任何对象——connect-src不允许其他连接。将数据内联到页面中,不要通过请求获取。 - **框架/对象/表单:**嵌套的
<iframe>、<object>、<embed>、提交<form>、<base>。 - Workers、弹出窗口、对话框、下载、摄像头/地理位置/全屏功能、导航父框架,以及普通的链接导航——
<a href>不会自行打开;请通过OPEN_URL处理点击(参见下文的打开链接)。
主题和语言——从查询字符串中读取:
const theme = new URLSearchParams(location.search).get('theme'); // 'light' | 'dark' | 'system'
const lang = new URLSearchParams(location.search).get('lang'); // 'ru' | 'en'
设计和生成 HTML 时,请尽量创建支持可用主题和语言且能自适应的页面:部分用户会通过 DataLens 的移动网页版查看页面。
导出文件 — 下载 API 会被阻止;请将数据交给宿主处理。宿主会验证 event.source,应用 MIME 类型白名单和大小上限,清理文件名,然后创建下载:
parent.postMessage({ code: 'EXPORT', data: { name: 'report.csv', mime: 'text/csv', data: csvString } }, '*');
打开链接 — 普通的 <a href> 不会在沙箱内导航(源不透明;即使设置 target="_blank",也只会打开 about:blank)。要打开 URL,请向宿主发出请求:拦截点击、调用 preventDefault(),然后发送 OPEN_URL。一个委托监听器即可处理所有链接 — 仅在页面嵌入框架时启用,这样文件在顶层打开或在本地预览时仍能正常工作。
if (window.parent !== window) {
document.addEventListener('click', (e) => {
const a = e.target.closest('a[href]');
if (!a) return;
const href = a.getAttribute('href');
if (!href || href.startsWith('#')) return; // in-page anchors scroll normally — leave them
e.preventDefault();
parent.postMessage({ code: 'OPEN_URL', data: { url: a.href } }, '*');
});
}
编码和大小(上传时强制检查):
- 使用有效的 UTF-8;请在前 1024 字节内声明
<meta charset="utf-8">。服务器会检测乱码,但不会修复。 - 不要用 Markdown 代码围栏包裹文档(
```html)— 这会提前关闭<head>。 - 页面大小最多约 10 MB,最好控制在约 5 MB 以下。
详尽列表请参阅 references/authoring-constraints.md,合规的起始模板请参阅 assets/report.template.html。
设计 — 构建仪表板或报告页面前,请询问用户是否希望遵循 DataLens 设计原则进行样式设计(除非用户已经说明)。如果无法询问 — 例如在无法获取答复的非交互式运行中 — 默认不应用这些原则:采用简洁朴素的样式,并在输出中说明未采用设计原则。如果用户同意,请遵循 references/design-principles.md — 包括布局网格、图表类型选择、颜色、数字格式和主题。该指南依赖 assets/dl-theme-tokens.css 中的 DataLens 设计令牌,以及 assets/dl-dashboard.css 中的基础仪表板样式 — 请将这两个文件的内容都内联到页面的 <style> 中(页面必须是独立的),并根据 theme 查询参数,在根元素上设置 g-root g-root_theme_light|dark。如果用户拒绝,请跳过这三个文件,按用户要求为页面设置样式;但仍须遵守上述沙箱和 CSP 规则。
上传前验证
根据确切的 CSP 白名单和沙箱限制检查页面 — 检查通过意味着渲染时不会有任何内容被阻止或剥离:
python scripts/validate_page.py report.html # errors + warnings
python scripts/validate_page.py --strict report.html # warnings fail too (CI mode)
cat report.html | python scripts/validate_page.py - # from stdin
该检查器会标记不在白名单中的资源主机、被阻止的 API(存储、fetch、workers 等)、被阻止的标签、缺少字符集、乱码、被代码围栏包裹的文档以及超大页面。运行 python scripts/validate_page.py --self-test 可检查检查器本身。
提供和发布
要上传或发布页面并返回永久链接,请遵循 references/publication.md。
页面由服务器端提供;请了解其工作方式,以便正确编写页面:
- 预签名 GET,有效期 10–30 秒,仅在权限检查通过后生成(不存在 TOCTOU 时间差)。不要构建依赖长期有效 URL 的流程。
- 对象存储时使用
Content-Type: text/html; charset=utf-8、Content-Disposition: inline和Cache-Control: no-store。上传通过服务器端 JSON-RPC 完成(不使用预签名 PUT — CSP 注入和元数据由服务器负责)。 - iframe 会携带上述沙箱属性;父级会发送
COOP: same-origin。
需要在设计中考虑的已接受风险(详情见 references/csp-and-sandbox.md):在顶层打开的 URL(在其有效期内)会失去 iframe 沙箱保护,但仍保留注入的 CSP;页面可以通过 <meta http-equiv="refresh"> 自行导航;白名单中的 CDN 未通过 SRI 固定。由于框架采用同源隔离且无法访问网络,请勿将必须保密的查看者数据、参数或实时 LLM 输出放入页面 — 这只是报告,不是可信的应用界面。
常见问题 → 解决方法
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 库始终无法加载/控制台出现 CSP 错误 | 脚本/样式托管地址不在允许列表中 | 从 jsdelivr / cdnjs / tailwind / yastatic 提供 |
| 数据始终不显示 | 代码调用了 fetch/XHR | 在生成页面时将数据集内联到页面中 |
| “localStorage is not available”/抛出异常 | 沙箱中使用了存储 API | 在内存中保留状态 |
| 字体无法显示 | 字体托管地址不在允许列表中 | 使用 Google Fonts(fonts.googleapis.com CSS + fonts.gstatic.com 文件)或 jsdelivr/cdnjs |
| 图片空白 | 使用了 http:// 或不在允许列表中的托管地址 | 使用 yastatic.net、data: 或 blob: |
| Yandex 地图空白 | 使用了 Maps v3、从镜像加载 API,或容器没有高度 | 使用来自 api-maps.yandex.ru 的 JS API 2.1,并为容器设置明确的高度 |
| 下载按钮不起作用 | 下载 API 被阻止 | 向父级发送 {code:'EXPORT', data:{name,mime,data}} |
| 点击链接不起作用 | 链接无法在沙箱中跳转 | 拦截点击并发送 {code:'OPEN_URL', data:{url}} |
| 页面显示为纯文本/head 损坏 | Markdown 代码围栏嵌套 | 移除 ``` 围栏 |
| 上传被拒绝 | 超过 10 MB 或 UTF-8 无效 | 缩小资源;确保使用 UTF-8,并尽早添加 <meta charset> |