commit ac82f67e0929bda4990bd43f2d26518667d43fc4 Author: qwenpaw-skills Date: Sun Aug 9 00:36:02 2026 +0000 Publish dataease via gitea-publish skill diff --git a/README.md b/README.md new file mode 100644 index 0000000..bcbf816 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +# DataEase V2 全能技能 + +一站式 DataEase 自动化解决方案,融合图表部署与资源管理能力。 + +## 🎯 功能特性 + +| 功能 | 命令 | 说明 | +|------|------|------| +| 📊 数据探索 | `inspect_data.py` | 查询数据集、字段信息 | +| 📈 图表部署 | `deploy.py` | 创建图表并自动截图 | +| 📋 多图看板 | `multi_deploy.py` | 创建多图表仪表板 | +| 🏢 组织管理 | `capture_dashboard.py` | 查询/切换组织 | +| 📑 资源列表 | `capture_dashboard.py` | 列出仪表板/大屏 | +| 📸 截图导出 | `capture_dashboard.py` | 导出截图或PDF | + +## 🚀 快速开始 + +### 1. 安装依赖 + +```bash +npm install +npx playwright install chromium +``` + +### 2. 配置环境 + +```bash +cp .env.example .env +``` + +编辑 `.env` 文件: + +```bash +DATAEASE_BASE_URL=https://your-dataease.example.com +DATAEASE_API_PREFIX=/de2api +DATAEASE_ACCESS_KEY=your_access_key +DATAEASE_SECRET_KEY=your_secret_key +DATAEASE_USERNAME=admin +DATAEASE_PASSWORD=your_password +DATAEASE_LOGIN_ORIGIN=0 +``` + +**认证方式(二选一):** +- AK/SK:配置 `ACCESS_KEY` + `SECRET_KEY` +- 密码登录:配置 `USERNAME` + `PASSWORD` + +### 3. 使用示例 + +```bash +# 查询数据集 +python3 scripts/inspect_data.py --list-datasets + +# 查询字段 +python3 scripts/inspect_data.py --dataset "销售数据" + +# 创建图表(自动截图) +python3 scripts/deploy.py bar '各产品销售额' '销售数据' '产品' '实际销售' + +# 创建多图表看板 +python3 scripts/multi_deploy.py '销售分析' '[{"type":"bar","title":"销售","dataset_name":"销售数据","x_axis":["产品"],"y_axis":["销售额"]}]' + +# 查询组织 +python3 scripts/capture_dashboard.py list-orgs + +# 导出截图 +python3 scripts/capture_dashboard.py capture --resource-id --busi-type dashboard --output-dir ./output + +# 导出 PDF +python3 scripts/capture_dashboard.py capture --resource-id --busi-type dashboard --result-format 1 --output-dir ./output +``` + +## 📁 目录结构 + +``` +dataease-v2-chart-skill/ +├── SKILL.md # 技能文档(Agent 读取) +├── README.md # 本文档 +├── .env.example # 环境变量模板 +├── package.json # Node 依赖 +├── scripts/ +│ ├── inspect_data.py # 数据探索 +│ ├── deploy.py # 图表部署 +│ ├── multi_deploy.py # 多图表部署 +│ ├── engine.py # 图表引擎 +│ ├── client.py # API 客户端 +│ ├── capture_dashboard.py # 截图/资源管理 +│ └── browser_capture.mjs # 浏览器截图 +├── templates/ # 图表模板 +├── references/ # API 参考 +└── agents/ # Agent 配置 +``` + +## 📊 支持的图表类型 + +- `bar` - 柱状图 +- `line` - 折线图 +- `pie` - 饼图 +- `table_info` - 明细表 + +## 📸 截图参数 + +- `--pixel`: 分辨率,默认 `1920*1080`,可设 `2560*1440` +- `--ext-wait-time`: 额外等待秒数 +- `--result-format`: `0`=JPEG, `1`=PDF + +## 📝 许可证 + +MIT diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..fb059e7 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,363 @@ +--- +name: dataease +description: DataEase V2 全能技能 - 创建图表看板、查询数据集、管理组织、导出截图/PDF。支持柱状图、折线图、饼图、明细表,部署后自动截图展示。 +environment: + required: + - DATAEASE_ACCESS_KEY + - DATAEASE_SECRET_KEY + - DATAEASE_BASE_URL +security: + requiresSecrets: true + sensitiveEnvironment: true + externalNetworkAccess: true +--- + +# DataEase V2 全能技能 + +一站式 DataEase 自动化解决方案,融合图表部署与资源管理能力。 + +## 🎯 核心功能 + +| 功能模块 | 命令 | 说明 | +|---------|------|------| +| 📊 数据探索 | `inspect_data.py` | 查询数据集列表、字段信息 | +| 📈 图表部署 | `deploy.py` | 创建单图表并自动截图 | +| 📋 多图看板 | `multi_deploy.py` | 创建多图表仪表板并自动截图 | +| 🏢 组织管理 | `capture_dashboard.py list-orgs/switch-org` | 查询/切换组织 | +| 📑 资源列表 | `capture_dashboard.py list-resources` | 列出仪表板/大屏 | +| 📸 截图导出 | `capture_dashboard.py capture` | 导出仪表板截图或PDF | + +## 🎭 沟通风格 (Persona) + +作为 DataEase 自动化专家: +1. **执行优先**: 直接运行工具,不预先展示代码 +2. **结果导向**: 优先展示截图,再说明逻辑 +3. **单次往复**: 一次完成从探索到部署的全过程 + +## 🚀 环境配置 + +### 1. 配置凭据 + +复制 `.env.example` 为 `.env`,填写: + +```bash +DATAEASE_BASE_URL=https://your-dataease.example.com +DATAEASE_ACCESS_KEY=your_access_key +DATAEASE_SECRET_KEY=your_secret_key +DATAEASE_USERNAME=admin # 可选,用于密码登录 +DATAEASE_PASSWORD=your_password # 可选 +DATAEASE_LOGIN_ORIGIN=0 # 登录源 + +# 截图输出目录(必须在 OpenClaw workspace 内才能用 MEDIA: 展示,不输入默认直接在 OpenClaw workspace 内创建) +DATAEASE_OUTPUT_DIR=/Users/username/.openclaw/workspace/dataease-output +``` + +### 2. 安装截图依赖 + +首次使用需安装 Playwright 浏览器: + +```bash +cd {baseDir} +npm install +npx playwright install chromium +``` + +### 3. 验证连接 + +```bash +python3 scripts/inspect_data.py --list-datasets +``` + +--- + +## 📊 数据探索 + +### 查询所有数据集 +```bash +python3 scripts/inspect_data.py --list-datasets +``` + +### 查询特定数据集字段 +```bash +python3 scripts/inspect_data.py --dataset "<数据集名称或ID>" +``` + +**返回示例**: +```json +[ + {"name": "产品", "type": 0, "id": "..."}, + {"name": "销售额", "type": 2, "id": "..."} +] +``` + +字段类型:`0`=文本, `1`=日期, `2`=指标, `3`=数值 + +--- + +## 📈 图表部署 + +### 单图表部署(自动截图) +```bash +python3 scripts/deploy.py <dataset> <x_fields> <y_fields> +``` + +**参数说明**: +- `type`: `bar`(柱状图), `line`(折线图), `pie`(饼图), `table_info`(明细表) +- `title`: 图表标题 +- `dataset`: 数据集名称或ID +- `x_fields`: 维度字段(逗号分隔) +- `y_fields`: 指标字段(逗号分隔) + +**示例**: +```bash +python3 scripts/deploy.py bar '各产品销售额' '销售数据' '产品' '实际销售' +python3 scripts/deploy.py bar '销售对比' '销售数据' '公司' '期望销售,实际销售' +python3 scripts/deploy.py pie '产品占比' '销售数据' '产品类别' '实际销售' +``` + +**可选参数**: +- `--no-screenshot`: 跳过截图,仅返回链接 + +**输出**: +- 自动截图并返回 JSON 结果(包含 `screenshot` 路径) +- Agent 应使用 `MEDIA:<screenshot_path>` 展示截图 + +### 多图表看板部署 +```bash +python3 scripts/multi_deploy.py <dashboard_title> '<charts_json>' +``` + +**JSON 结构**: +```json +[ + { + "type": "bar", + "title": "图表标题", + "dataset_name": "数据集名", + "x_axis": ["维度字段"], + "y_axis": ["指标1", "指标2"] + } +] +``` + +**示例**: +```bash +python3 scripts/multi_deploy.py '销售分析看板' '[ + {"type":"bar","title":"各产品销售","dataset_name":"销售数据","x_axis":["产品"],"y_axis":["实际销售"]}, + {"type":"pie","title":"类别占比","dataset_name":"销售数据","x_axis":["产品类别"],"y_axis":["实际销售"]} +]' +``` + +--- + +## 🏢 组织管理 + +### 查询组织列表 +```bash +python3 scripts/capture_dashboard.py list-orgs +``` + +**返回**:所有可用组织及其 ID + +### 切换组织 +```bash +python3 scripts/capture_dashboard.py switch-org --org-id <组织ID> +``` + +切换后,后续操作将在新组织上下文中执行。 + +--- + +## 📑 资源列表 + +### 列出仪表板 +```bash +python3 scripts/capture_dashboard.py list-resources --busi-type dashboard +``` + +### 列出数据大屏 +```bash +python3 scripts/capture_dashboard.py list-resources --busi-type dataV +``` + +### 指定组织查询 +```bash +python3 scripts/capture_dashboard.py list-resources --org-id <组织ID> --busi-type dashboard +``` + +--- + +## 📸 截图导出 + +### 导出仪表板截图 +```bash +python3 scripts/capture_dashboard.py capture \ + --resource-id <仪表板ID> \ + --busi-type dashboard \ + --output-dir <输出目录> +``` + +### 导出数据大屏 +```bash +python3 scripts/capture_dashboard.py capture \ + --resource-id <大屏ID> \ + --busi-type dataV \ + --output-dir <输出目录> +``` + +### 导出 PDF +```bash +python3 scripts/capture_dashboard.py capture \ + --resource-id <ID> \ + --busi-type dashboard \ + --result-format 1 \ + --output-dir <输出目录> +``` + +### 按名称匹配导出 +```bash +python3 scripts/capture_dashboard.py capture \ + --resource-name "销售总览" \ + --busi-type dashboard +``` + +### 高级参数 +- `--pixel`: 分辨率,默认 `1920*1080`,可用 `2560*1440` +- `--ext-wait-time`: 额外等待时间(秒),用于复杂图表 +- `--org-id`: 指定组织 ID + +### ⚠️ 导出结果处理 + +**重要:导出的图片和 PDF 必须直接发送到对话中展示!** + +截图或 PDF 导出成功后,返回的 JSON 中包含 `saved_file` 字段: +```json +{ + "ok": true, + "saved_file": "/path/to/file.jpg" // 或 .pdf +} +``` + +**Agent 必须执行:** +1. 读取 `saved_file` 路径 +2. 使用 `MEDIA:<saved_file>` 语法直接发送到对话中 + +**示例:** +``` +MEDIA:/path/to/output/仪表板名_123456.jpg +MEDIA:/path/to/output/仪表板名_123456.pdf +``` + +**不要只返回文件路径,必须使用 MEDIA: 发送文件!** + +--- + +## 🛠 参数规范 + +### 图表类型 +| 类型 | 说明 | 适用场景 | +|-----|------|---------| +| `bar` | 柱状图 | 分类对比、排名 | +| `line` | 折线图 | 趋势分析、时序数据 | +| `pie` | 饼图 | 占比分析、构成分布 | +| `table_info` | 明细表 | 数据明细展示 | + +### 维度与指标 +- **维度 (x_axis)**: 类别、日期等分组字段 +- **指标 (y_axis)**: 数值、金额等聚合字段 +- 明细表的 `y_axis` 传空,`x_axis` 列出所有展示字段 + +### 业务类型 +- `dashboard`: 仪表板 +- `dataV`: 数据大屏 + +--- + +## 📸 结果处理 + +### 图表部署结果 + +部署成功后输出 JSON: +```json +{ + "ok": true, + "dashboard_id": "1243929230048366592", + "url": "https://...", + "screenshot": "/path/to/screenshot.jpg", + "title": "图表标题" +} +``` + +**Agent 处理流程**: +1. 读取 `screenshot` 路径图片 +2. 使用 `MEDIA:<screenshot_path>` 展示 +3. 同时提供访问链接 + +### 截图导出结果 + +```json +{ + "ok": true, + "resource_id": "...", + "resource_name": "...", + "saved_file": "/path/to/file.jpg", + "pixel": "1920*1080", + "capture_engine": "local_playwright" +} +``` + +--- + +## ⚠️ 常见问题 + +### SSL 证书错误 +macOS 上 Python 可能缺少系统证书,运行: +```bash +/Applications/Python\ 3.13/Install\ Certificates.command +``` + +### Playwright 浏览器未安装 +```bash +npx playwright install chromium +``` + +### 截图超时 +使用 `--ext-wait-time` 增加等待时间: +```bash +python3 scripts/capture_dashboard.py capture --resource-id <ID> --ext-wait-time 5 --busi-type dashboard +``` + +### 资源名称匹配失败 +使用 `--resource-id` 精确指定,或查看 `candidates` 字段获取候选列表。 + +--- + +## 📁 文件结构 + +``` +skills/dataease/ +├── SKILL.md # 本文档 +├── .env # 环境配置 +├── .env.example # 配置模板 +├── package.json # Node 依赖 +├── scripts/ +│ ├── inspect_data.py # 数据探索 +│ ├── deploy.py # 单图表部署 +│ ├── multi_deploy.py # 多图表部署 +│ ├── engine.py # 图表引擎 +│ ├── multi_engine.py # 多图表引擎 +│ ├── client.py # API 客户端 +│ ├── capture_dashboard.py # 截图/资源管理 +│ └── browser_capture.mjs # 浏览器截图 +├── templates/ # 图表模板 +│ ├── chart_bar/ +│ ├── chart_line/ +│ ├── chart_pie/ +│ └── chart_table_info/ +├── references/ # 参考文档 +│ ├── api.md +│ └── resource_aliases.json +├── agents/ +│ └── openai.yaml +└── output/ # 截图输出目录 +``` diff --git a/agents/openai.yaml b/agents/openai.yaml new file mode 100644 index 0000000..01ead7d --- /dev/null +++ b/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "DataEase 全能技能" + short_description: "创建图表看板、查询数据、管理组织、导出截图/PDF" + icon: "chart-bar" + color: "blue" + default_prompt: "Use dataease skill to explore datasets, create charts and dashboards, manage organizations, list resources, and export screenshots or PDFs. Charts are automatically captured and displayed." diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0dba287 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,102 @@ +{ + "name": "dataease-skills", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dataease-skills", + "version": "1.0.0", + "dependencies": { + "pdf-lib": "^1.17.1", + "playwright": "^1.59.1" + } + }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.10" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "license": "MIT", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b68f9d3 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "dataease-skills", + "version": "1.0.0", + "description": "DataEase v2 Chart Skill with auto-screenshot", + "dependencies": { + "pdf-lib": "^1.17.1", + "playwright": "^1.59.1" + } +} diff --git a/references/api.md b/references/api.md new file mode 100644 index 0000000..48161ea --- /dev/null +++ b/references/api.md @@ -0,0 +1,145 @@ +# DataEase 接口说明 + +## 服务地址 + +- 技能执行时建议从环境变量 `DATAEASE_BASE_URL` 读取,避免把环境地址写死到脚本参数中。 + +## 1. 查询组织树 + +### 接口地址 + +`POST /de2api/org/page/tree` + +### 请求参数 + +- `keyword` + - 组织名称关键字 +- `desc` + - 是否倒序 + +### 本技能的用途 + +用于查看当前凭证可访问的组织树,并辅助确认目标资源是否位于其他组织。 + +## 2. 切换组织 + +### 接口地址 + +`POST /de2api/user/switch/{orgId}` + +### 本技能的用途 + +在用户显式提供 `orgId` 时切换组织上下文,并使用响应中的 `data.token` 作为后续资源树查询和本地预览截图的 `x-de-token`。 + +## 3. 查询可视化资源树 + +### 接口地址 + +`POST /de2api/dataVisualization/tree` + +### 请求参数 + +- `busiFlag` + - `dashboard`:仪表板 + - `dataV`:数据大屏 +- `resourceTable` + - 默认值:`core` + +### 响应字段 + +- `code` +- `msg` +- `data` +- `id` +- `name` +- `leaf` +- `type` +- `children` + +### 本技能的用途 + +通过该接口获取 dashboard 或 dataV 的资源树,并展开为资源列表,供查询和导出使用。 + +## 4. 本地预览截图 + +### 预览页地址 + +- `/#/preview?dvId={resourceId}` +- 当 `busiType=dashboard` 时追加 `&report=true` + +### 本技能的用途 + +`capture` 命令不再调用 `/de2api/report/export`,而是: + +1. 通过 `/de2api/dataVisualization/tree` 定位目标资源 +2. 获取可用于前端预览页的 `x-de-token` +3. 打开预览页并将 token 注入 `localStorage.user.token` +4. 等待 `.canvas-container` 渲染完成后本地导出 + +### 导出参数 + +- `pixel` + - 浏览器视口大小,格式为 `宽*高` +- `extWaitTime` + - 预览画布可见后额外等待的秒数 +- `resultFormat` + - `0`:JPEG + - `1`:PDF + +### 默认值 + +- `busiType=dashboard` +- `pixel=1920*1080` +- `extWaitTime=0` +- `resultFormat=0` + +## 5. 鉴权说明 + +当前按以下配置项接入: + +- `DATAEASE_BASE_URL` +- `DATAEASE_ACCESS_KEY` +- `DATAEASE_SECRET_KEY` +- `DATAEASE_USERNAME` +- `DATAEASE_PASSWORD` +- `DATAEASE_LOGIN_ORIGIN` + +### 用户名密码登录方式 + +- 先通过 `GET /de2api/dekey` 获取前端加密所需的 dekey +- 使用 dekey 解出 RSA 公钥后,对用户名和密码做 RSA 加密 +- 再调用 `POST /de2api/login/localLogin` +- 响应中的 `data.token` 可直接作为业务接口和预览页使用的 `x-de-token` +- `origin=0` 表示本地账号,`origin=1` 表示 LDAP + +### ask-token 生成方式 + +- 原始签名串格式:`<accessKey>|<uuid>|<timestamp_ms>` +- 使用 `secretKey` 作为 AES key,`accessKey` 作为 IV +- 加密算法:`AES/CBC/PKCS5Padding` +- 将加密结果做 Base64,得到 `signature` +- 使用 `secretKey` 对 JWT 进行 `HS256` 签名 +- JWT claim 包含: + - `accessKey` + - `signature` + +### 请求头 + +默认请求头: + +- `accessKey` +- `signature` +- `x-de-ask-token` + +切换组织后查询资源或导出时: + +- `x-de-token` + +## 6. 当前脚本对应动作 + +- `list-orgs` +- `switch-org` +- `list-resources` +- `capture` + +如果实际网关存在额外签名规则,请按部署环境调整 `scripts/capture_dashboard.py`。 diff --git a/references/resource_aliases.json b/references/resource_aliases.json new file mode 100644 index 0000000..06c2fa8 --- /dev/null +++ b/references/resource_aliases.json @@ -0,0 +1,5 @@ +{ + "销售总览": "销售经营总览看板", + "华东分析": "华东区域经营分析", + "门店大屏": "门店运营监控" +} diff --git a/scripts/browser_capture.mjs b/scripts/browser_capture.mjs new file mode 100644 index 0000000..94febf8 --- /dev/null +++ b/scripts/browser_capture.mjs @@ -0,0 +1,503 @@ +#!/usr/bin/env node + +import fs from 'node:fs/promises'; + +const MAX_DEBUG_LOGS = 20; +const RENDER_SETTLE_POLL_MS = 500; +const RENDER_SETTLE_IDLE_MS = 2000; +const MAX_CAPTURE_VIEWPORT_HEIGHT = 12000; + +function printHelp() { + console.log(`Usage: node scripts/browser_capture.mjs --url <url> --token <x-de-token> --width 1920 --height 1080 --wait-seconds 0 --result-format 0 --output /abs/path/file.jpg + +Options: + --url DataEase preview URL + --token X-DE-TOKEN to inject into localStorage as user.token + --width Browser viewport width in pixels + --height Browser viewport height in pixels + --wait-seconds Extra wait time after canvas is visible + --result-format 0=jpeg, 1=pdf + --output Absolute or relative output path + -h, --help Show this help message +`); +} + +function parseArgs(argv) { + const args = {}; + for (let index = 0; index < argv.length; index += 1) { + const current = argv[index]; + if (current === '-h' || current === '--help') { + args.help = true; + continue; + } + if (!current.startsWith('--')) { + throw new Error(`Unexpected argument: ${current}`); + } + const key = current.slice(2); + const value = argv[index + 1]; + if (value == null || value.startsWith('--')) { + throw new Error(`Missing value for --${key}`); + } + args[key] = value; + index += 1; + } + return args; +} + +function parsePositiveInteger(value, name) { + const number = Number.parseInt(value, 10); + if (!Number.isFinite(number) || number <= 0) { + throw new Error(`${name} must be a positive integer`); + } + return number; +} + +function parseWaitSeconds(value) { + const number = Number.parseInt(value ?? '0', 10); + if (!Number.isFinite(number) || number < 0) { + throw new Error('wait-seconds must be a non-negative integer'); + } + return number; +} + +async function waitForCanvasReady(page, selector, timeout) { + const locator = page.locator(selector).first(); + await locator.waitFor({ state: 'visible', timeout }); + await page.waitForFunction( + targetSelector => { + const element = document.querySelector(targetSelector); + if (!element) { + return false; + } + const rect = element.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0; + }, + selector, + { timeout } + ); + await page.waitForTimeout(500); + return locator; +} + +function buildWsCacheItem(value, now = Date.now(), expiresAt = 253402300799000) { + return JSON.stringify({ + c: now, + e: expiresAt, + v: JSON.stringify(value) + }); +} + +async function collectDiagnostics(page, debugLogs) { + const state = await page.evaluate(() => { + const app = document.querySelector('#app'); + const visible = element => { + if (!element) { + return false; + } + const style = window.getComputedStyle(element); + const rect = element.getBoundingClientRect(); + return style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity || '1') > 0 && rect.width > 0 && rect.height > 0; + }; + return { + href: location.href, + title: document.title, + hasCanvas: !!document.querySelector('.canvas-container'), + hasContent: !!document.querySelector('.content'), + hasEmpty: !!document.querySelector('.empty-background, .el-empty'), + visibleLoadingMasks: Array.from(document.querySelectorAll('.el-loading-mask,.ed-loading-mask,.v-loading-mask')).filter(visible).length, + unfinishedReportLoads: document.querySelectorAll('.report-load:not(.report-load-finish)').length, + bodyText: (document.body?.innerText || '').slice(0, 400), + appHtml: (app?.innerHTML || '').slice(0, 1200) + }; + }); + return { + ...state, + logs: debugLogs.slice(-MAX_DEBUG_LOGS) + }; +} + +async function getRenderState(page, selector) { + return page.evaluate(targetSelector => { + const visible = element => { + if (!element) { + return false; + } + const style = window.getComputedStyle(element); + const rect = element.getBoundingClientRect(); + return style.display !== 'none' && style.visibility !== 'hidden' && Number(style.opacity || '1') > 0 && rect.width > 0 && rect.height > 0; + }; + const canvas = document.querySelector(targetSelector); + const loadingSelectors = '.el-loading-mask,.ed-loading-mask,.v-loading-mask,[class*="loading-mask"]'; + const visibleLoadingMasks = Array.from(document.querySelectorAll(loadingSelectors)).filter(visible).length; + const unfinishedReportLoads = document.querySelectorAll('.report-load:not(.report-load-finish)').length; + const box = canvas?.getBoundingClientRect(); + return { + hasCanvas: !!canvas, + visibleLoadingMasks, + unfinishedReportLoads, + width: Math.round(box?.width || 0), + height: Math.round(box?.height || 0) + }; + }, selector); +} + +async function waitForRenderSettled(page, selector, timeout, debugLogs, getPendingRequests) { + const startedAt = Date.now(); + let stableSince = 0; + let lastSnapshot = ''; + + while (Date.now() - startedAt < timeout) { + const state = await getRenderState(page, selector); + const pendingRequests = getPendingRequests(); + const snapshot = JSON.stringify({ ...state, pendingRequests }); + if (snapshot !== lastSnapshot) { + debugLogs.push(`render-state ${snapshot}`); + lastSnapshot = snapshot; + } + + const settled = + state.hasCanvas && + state.width > 0 && + state.height > 0 && + state.visibleLoadingMasks === 0 && + state.unfinishedReportLoads === 0 && + pendingRequests === 0; + + if (settled) { + if (!stableSince) { + stableSince = Date.now(); + } + if (Date.now() - stableSince >= RENDER_SETTLE_IDLE_MS) { + return state; + } + } else { + stableSince = 0; + } + + await page.waitForTimeout(RENDER_SETTLE_POLL_MS); + } + + const state = await getRenderState(page, selector); + throw new Error( + `render settle timeout: ${JSON.stringify({ + ...state, + pendingRequests: getPendingRequests() + })}` + ); +} + +async function expandScrollableCapture(page, selector, viewportWidth, viewportHeight) { + return page.evaluate( + ({ targetSelector, maxViewportHeight, minViewportHeight, viewportWidth }) => { + const canvas = document.querySelector(targetSelector); + if (!canvas) { + return null; + } + + const isScrollable = element => { + if (!element) { + return false; + } + const style = window.getComputedStyle(element); + const overflowY = style.overflowY; + return ['auto', 'scroll', 'overlay'].includes(overflowY) && element.scrollHeight - element.clientHeight > 2; + }; + + const applyStyle = (element, key, value) => { + const styleKey = key; + element.style[styleKey] = value; + }; + + let scrollableAncestor = null; + let current = canvas.parentElement; + while (current && current !== document.body) { + if (isScrollable(current)) { + scrollableAncestor = current; + break; + } + current = current.parentElement; + } + + const scrollingElement = document.scrollingElement || document.documentElement; + const initialRect = canvas.getBoundingClientRect(); + + if (scrollableAncestor) { + scrollableAncestor.scrollTop = 0; + applyStyle(scrollableAncestor, 'overflowY', 'visible'); + applyStyle(scrollableAncestor, 'height', `${scrollableAncestor.scrollHeight}px`); + applyStyle(scrollableAncestor, 'maxHeight', 'none'); + } + + if (scrollingElement) { + scrollingElement.scrollTop = 0; + } + window.scrollTo(0, 0); + + const rect = canvas.getBoundingClientRect(); + const fullHeight = Math.max( + Math.ceil(rect.height), + Math.ceil(canvas.scrollHeight || 0), + Math.ceil(scrollableAncestor?.scrollHeight || 0), + Math.ceil(document.documentElement.scrollHeight || 0) + ); + + return { + hadScrollableAncestor: Boolean(scrollableAncestor), + initialHeight: Math.ceil(initialRect.height), + finalHeight: Math.ceil(rect.height), + captureHeight: fullHeight, + viewportHeight: Math.min(Math.max(fullHeight, minViewportHeight), maxViewportHeight), + viewportWidth: viewportWidth + }; + }, + { + targetSelector: selector, + maxViewportHeight: MAX_CAPTURE_VIEWPORT_HEIGHT, + minViewportHeight: viewportHeight, + viewportWidth: viewportWidth + } + ); +} + +async function toPdfBuffer(pngBytes, PDFDocument) { + const pdfDoc = await PDFDocument.create(); + const image = await pdfDoc.embedPng(pngBytes); + const page = pdfDoc.addPage([image.width, image.height]); + page.drawImage(image, { + x: 0, + y: 0, + width: image.width, + height: image.height + }); + const pdfBytes = await pdfDoc.save(); + return Buffer.from(pdfBytes); +} + +async function main() { + let browser; + let page; + const debugLogs = []; + try { + const args = parseArgs(process.argv.slice(2)); + if (args.help) { + printHelp(); + return; + } + + const url = args.url; + const token = args.token; + const output = args.output; + if (!url || !token || !output) { + throw new Error('url, token and output are required'); + } + + const width = parsePositiveInteger(args.width ?? '1920', 'width'); + const height = parsePositiveInteger(args.height ?? '1080', 'height'); + const waitSeconds = parseWaitSeconds(args['wait-seconds']); + const resultFormat = Number.parseInt(args['result-format'] ?? '0', 10); + if (![0, 1].includes(resultFormat)) { + throw new Error('result-format must be 0 or 1'); + } + + const [{ chromium }, { PDFDocument }] = await Promise.all([ + import('playwright'), + import('pdf-lib') + ]); + + browser = await chromium.launch({ + headless: true, + executablePath: process.env.CHROMIUM_PATH || undefined + }); + const context = await browser.newContext({ + viewport: { width, height }, + deviceScaleFactor: 1, + ignoreHTTPSErrors: true, + extraHTTPHeaders: { + 'X-DE-TOKEN': token + } + }); + await context.route('**/de2api/**', async route => { + const request = route.request(); + const headers = { + ...request.headers(), + 'X-DE-TOKEN': token + }; + const url = request.url(); + if (url.includes('/de2api/outerParams/getOuterParamsInfo/')) { + try { + const response = await route.fetch({ headers }); + if (response.status() < 500) { + await route.fulfill({ response }); + return; + } + debugLogs.push(`outerParams fallback: ${response.status()} ${url}`); + } catch (error) { + debugLogs.push(`outerParams fallback error: ${error?.message || String(error)}`); + } + await route.fulfill({ + status: 200, + contentType: 'application/json;charset=UTF-8', + body: JSON.stringify({ + code: 0, + msg: 'success', + data: { + outerParamsInfoMap: {}, + outerParamsInfoBaseMap: {} + } + }) + }); + return; + } + await route.continue({ headers }); + }); + page = await context.newPage(); + const selector = '.canvas-container'; + const timeout = 120000; + const pendingRequests = new Set(); + const trackRequest = request => { + const resourceType = request.resourceType(); + if (!['fetch', 'xhr'].includes(resourceType)) { + return false; + } + return request.url().includes('/de2api/'); + }; + page.on('request', request => { + if (trackRequest(request)) { + pendingRequests.add(request); + } + }); + page.on('requestfinished', request => { + pendingRequests.delete(request); + }); + page.on('requestfailed', request => { + pendingRequests.delete(request); + }); + page.on('response', response => { + if (response.status() >= 400 && response.url().includes('/de2api/')) { + debugLogs.push(`response ${response.status()} ${response.url()}`); + } + }); + page.on('pageerror', error => { + debugLogs.push(`pageerror ${error.message}`); + }); + page.on('console', message => { + if (message.type() === 'error') { + debugLogs.push(`console ${message.text()}`); + } + }); + + const now = Date.now(); + await page.addInitScript( + ({ injectedToken, cacheToken, cacheExp, cacheTime }) => { + localStorage.setItem('user.token', cacheToken); + localStorage.setItem('user.exp', cacheExp); + localStorage.setItem('user.time', cacheTime); + localStorage.setItem('__de_raw_token__', injectedToken); + }, + { + injectedToken: token, + cacheToken: buildWsCacheItem(token, now), + cacheExp: buildWsCacheItem(now + 3600 * 1000, now), + cacheTime: buildWsCacheItem(now, now) + } + ); + + await page.goto(url, { waitUntil: 'domcontentloaded', timeout }); + try { + await page.waitForLoadState('networkidle', { timeout: 10000 }); + } catch { + // Preview pages may keep network connections alive; selector checks below are stricter. + } + + const locator = await waitForCanvasReady(page, selector, timeout); + const renderState = await waitForRenderSettled( + page, + selector, + timeout, + debugLogs, + () => pendingRequests.size + ); + const expandedCapture = await expandScrollableCapture(page, selector, width, height); + if (expandedCapture?.captureHeight > height) { + await page.setViewportSize({ + width, + height: expandedCapture.viewportHeight + }); + await page.waitForTimeout(500); + await waitForRenderSettled( + page, + selector, + timeout, + debugLogs, + () => pendingRequests.size + ); + } + if (waitSeconds > 0) { + await page.waitForTimeout(waitSeconds * 1000); + } + const box = await locator.boundingBox(); + if (!box || box.width < 1 || box.height < 1) { + throw new Error('canvas container is empty'); + } + + if (resultFormat === 1) { + const pngBytes = await locator.screenshot({ + type: 'png', + animations: 'disabled' + }); + const pdfBytes = await toPdfBuffer(pngBytes, PDFDocument); + await fs.writeFile(output, pdfBytes); + console.log(JSON.stringify({ + ok: true, + format: 'pdf', + width: Math.round(box.width), + height: Math.round(box.height), + renderState, + expandedCapture, + output + })); + return; + } + + await locator.screenshot({ + path: output, + type: 'jpeg', + quality: 90, + animations: 'disabled' + }); + console.log(JSON.stringify({ + ok: true, + format: 'jpeg', + width: Math.round(box.width), + height: Math.round(box.height), + renderState, + expandedCapture, + output + })); + } catch (error) { + if (error && error.code === 'ERR_MODULE_NOT_FOUND') { + console.error('Missing runtime dependency. Please run `npm install` and `npx playwright install chromium` first.'); + process.exitCode = 1; + return; + } + try { + const diagnostics = page + ? await collectDiagnostics(page, debugLogs) + : null; + if (diagnostics) { + console.error(`${error && error.stack ? error.stack : String(error)}\nDiagnostics: ${JSON.stringify(diagnostics)}`); + } else { + console.error(error && error.stack ? error.stack : String(error)); + } + } catch { + console.error(error && error.stack ? error.stack : String(error)); + } + process.exitCode = 1; + } finally { + if (browser) { + await browser.close(); + } + } +} + +main(); diff --git a/scripts/capture_dashboard.py b/scripts/capture_dashboard.py new file mode 100644 index 0000000..b83f961 --- /dev/null +++ b/scripts/capture_dashboard.py @@ -0,0 +1,1130 @@ +#!/usr/bin/env python3 +import argparse +import base64 +import hashlib +import hmac +import json +import os +import re +import shutil +import subprocess +import sys +import tempfile +import time +import uuid +from difflib import SequenceMatcher +from pathlib import Path +from urllib.error import HTTPError, URLError +from urllib.parse import urlparse +from urllib.request import Request, urlopen + + +ROOT_DIR = Path(__file__).resolve().parent.parent +DEFAULT_ALIAS_FILE = ROOT_DIR / "references" / "resource_aliases.json" +BROWSER_CAPTURE_SCRIPT = ROOT_DIR / "scripts" / "browser_capture.mjs" +RSA_KEY_SEPARATOR = base64.urlsafe_b64encode(b"-pk_separator-").decode("ascii") + + +class ApiError(Exception): + def __init__(self, message, method, url, status_code=None, body=""): + super().__init__(message) + self.method = method + self.url = url + self.status_code = status_code + self.body = body + + +def print_json(data, code): + print(json.dumps(data, ensure_ascii=False, indent=2)) + sys.exit(code) + + +def error_to_dict(stage, err, extra=None): + payload = { + "ok": False, + "stage": stage, + "error": str(err), + } + if isinstance(err, ApiError): + payload.update({ + "method": err.method, + "url": err.url, + "status_code": err.status_code, + "response_body": err.body, + }) + if extra: + payload.update(extra) + return payload + + +def load_dotenv(path): + if not path.exists(): + return + for raw in path.read_text(encoding="utf-8").splitlines(): + line = raw.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, value = line.split("=", 1) + key = key.strip() + value = value.strip() + if not key or key in os.environ: + continue + if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}: + value = value[1:-1] + os.environ[key] = value + + +def normalize(text): + text = (text or "").strip().lower() + text = text.replace("“", '"').replace("”", '"').replace("‘", "'").replace("’", "'") + text = re.sub(r"\s+", "", text) + return re.sub(r"[()()【】\[\]{}·,,。.::!!??'\"-_/\\]", "", text) + + +def load_aliases(path): + alias_file = Path(path) + if not alias_file.exists(): + return {} + with alias_file.open("r", encoding="utf-8") as handle: + return json.load(handle) + + +def flatten_tree(nodes, result=None): + result = result or [] + for node in nodes or []: + result.append({ + "id": node.get("id"), + "name": node.get("name", ""), + "leaf": bool(node.get("leaf", False)), + "type": node.get("type"), + }) + flatten_tree(node.get("children") or [], result) + return result + + +def flatten_org_tree(nodes, result=None): + result = result or [] + for node in nodes or []: + children = node.get("children") or [] + result.append({ + "id": node.get("id"), + "name": node.get("name", ""), + "create_time": node.get("createTime"), + "read_only": node.get("readOnly"), + "leaf": len(children) == 0, + }) + flatten_org_tree(children, result) + return result + + +def extract_response_data(payload, stage): + if isinstance(payload, dict): + if payload.get("code") not in (None, 0): + raise ValueError(f"{stage}接口返回失败: code={payload.get('code')}, msg={payload.get('msg')}") + return payload.get("data") + return payload + + +def extract_tree_nodes(payload): + data = extract_response_data(payload, "资源树") + if isinstance(data, list): + return data + raise ValueError("资源树接口返回格式不符合预期,未找到 data 列表") + + +def base64url(raw): + return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") + + +def sign_jwt(payload, secret_key): + header = {"alg": "HS256", "typ": "JWT"} + header_part = base64url(json.dumps(header, separators=(",", ":")).encode("utf-8")) + payload_part = base64url(json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")) + signing_input = f"{header_part}.{payload_part}".encode("ascii") + signature = hmac.new(secret_key.encode("utf-8"), signing_input, hashlib.sha256).digest() + return f"{header_part}.{payload_part}.{base64url(signature)}" + + +def aes_cipher_name(secret_key): + length = len(secret_key.encode("utf-8")) + if length == 16: + return "aes-128-cbc" + if length == 24: + return "aes-192-cbc" + if length == 32: + return "aes-256-cbc" + raise ValueError("Secret Key 长度必须是 16、24 或 32 字节") + + +def aes_encrypt(plain_text, secret_key, iv): + if shutil.which("openssl") is None: + raise RuntimeError("当前环境缺少 openssl 命令,无法生成鉴权签名") + if len(iv.encode("utf-8")) != 16: + raise ValueError("Access Key 长度必须是 16 字节,才能作为 AES IV") + + cmd = [ + "openssl", + "enc", + f"-{aes_cipher_name(secret_key)}", + "-base64", + "-A", + "-nosalt", + "-K", + secret_key.encode("utf-8").hex(), + "-iv", + iv.encode("utf-8").hex(), + ] + proc = subprocess.run(cmd, input=plain_text.encode("utf-8"), capture_output=True, check=False) + if proc.returncode != 0: + stderr = proc.stderr.decode("utf-8", errors="replace").strip() + raise RuntimeError(stderr or "openssl 加密失败") + return proc.stdout.decode("utf-8").strip() + + +def build_ask_auth(access_key, secret_key): + source = f"{access_key}|{uuid.uuid4()}|{int(time.time() * 1000)}" + signature = aes_encrypt(source, secret_key, access_key) + token = sign_jwt({"accessKey": access_key, "signature": signature}, secret_key) + return { + "access_key": access_key, + "signature": signature, + "x_de_ask_token": token, + } + + +def build_headers(ask_auth): + return { + "Accept": "application/json;charset=UTF-8", + "Content-Type": "application/json", + "accessKey": ask_auth["access_key"], + "signature": ask_auth["signature"], + "X-DE-ASK-TOKEN": ask_auth["x_de_ask_token"], + } + + +def build_switch_headers(ask_auth): + return { + "Accept": "application/json;charset=UTF-8", + "Content-Type": "application/json", + "X-DE-ASK-TOKEN": ask_auth["x_de_ask_token"], + } + + +def build_token_headers(x_de_token): + return { + "Accept": "application/json;charset=UTF-8", + "Content-Type": "application/json", + "X-DE-TOKEN": x_de_token, + } + + +def get_header(headers, name, default=None): + target = name.lower() + for key, value in headers.items(): + if key.lower() == target: + return value + return default + + +def post_json(url, payload, headers, timeout=60): + data = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8") + forwarded_headers = dict(headers) + forwarded_headers.setdefault("X-Forwarded-Uri", urlparse(url).path) + forwarded_headers.setdefault("X-Forwarded-Method", "POST") + request = Request(url, data=data, headers=forwarded_headers, method="POST") + try: + with urlopen(request, timeout=timeout) as response: + return response.status, dict(response.headers), response.read() + except HTTPError as err: + body = err.read().decode("utf-8", errors="replace") + raise ApiError(str(err), "POST", url, err.code, body) + except URLError as err: + raise ApiError(str(err), "POST", url, None, "") + + +def get_bytes(url, headers=None, timeout=60): + request = Request(url, headers=dict(headers or {}), method="GET") + try: + with urlopen(request, timeout=timeout) as response: + return response.status, dict(response.headers), response.read() + except HTTPError as err: + body = err.read().decode("utf-8", errors="replace") + raise ApiError(str(err), "GET", url, err.code, body) + except URLError as err: + raise ApiError(str(err), "GET", url, None, "") + + +def request_with_fallback(base_url, method, paths, payload=None, headers=None, timeout=60): + last_error = None + for path in paths: + url = f"{base_url.rstrip('/')}{path}" + try: + if method == "GET": + return get_bytes(url, headers=headers, timeout=timeout) + if method == "POST": + return post_json(url, payload, headers or {}, timeout=timeout) + raise ValueError(f"不支持的请求方法: {method}") + except ApiError as err: + last_error = err + if err.status_code not in (404, None): + raise + if last_error: + raise last_error + raise ValueError("未提供可用的请求路径") + + +def fetch_dekey(base_url): + _, _, body = request_with_fallback( + base_url, + "GET", + ["/de2api/dekey", "/dekey"], + headers={"Accept": "application/json;charset=UTF-8"}, + timeout=60, + ) + payload = json.loads(body.decode("utf-8")) + data = extract_response_data(payload, "dekey") + if not isinstance(data, str) or not data: + raise ValueError("dekey 接口未返回有效字符串") + return data + + +def aes_decrypt(cipher_text, secret_key): + if shutil.which("openssl") is None: + raise RuntimeError("当前环境缺少 openssl 命令,无法执行账号密码登录加密") + secret_key_bytes = secret_key.encode("utf-8") + if len(secret_key_bytes) not in (16, 24, 32): + raise ValueError("dekey 中的 AES key 长度不合法") + + cmd = [ + "openssl", + "enc", + f"-{aes_cipher_name(secret_key)}", + "-d", + "-base64", + "-A", + "-nosalt", + "-K", + secret_key_bytes.hex(), + "-iv", + b"0000000000000000".hex(), + ] + proc = subprocess.run(cmd, input=cipher_text.encode("utf-8"), capture_output=True, check=False) + if proc.returncode != 0: + stderr = proc.stderr.decode("utf-8", errors="replace").strip() + raise RuntimeError(stderr or "openssl 解密 dekey 失败") + return proc.stdout.decode("utf-8") + + +def split_dekey(dekey): + for separator in (RSA_KEY_SEPARATOR, RSA_KEY_SEPARATOR.rstrip("=")): + if separator and separator in dekey: + parts = dekey.split(separator, 1) + if len(parts) == 2 and parts[0] and parts[1]: + return parts[0], parts[1] + raise ValueError("dekey 格式不符合预期,无法解析公钥信息") + + +def format_public_key(public_key): + body = "\n".join(public_key[index:index + 64] for index in range(0, len(public_key), 64)) + return f"-----BEGIN PUBLIC KEY-----\n{body}\n-----END PUBLIC KEY-----\n" + + +def rsa_encrypt(plain_text, public_key): + if shutil.which("openssl") is None: + raise RuntimeError("当前环境缺少 openssl 命令,无法执行账号密码登录加密") + with tempfile.TemporaryDirectory(prefix="dataease-pubkey-") as tmpdir: + key_path = Path(tmpdir) / "public.pem" + key_path.write_text(format_public_key(public_key), encoding="utf-8") + proc = subprocess.run( + ["openssl", "pkeyutl", "-encrypt", "-pubin", "-inkey", str(key_path)], + input=plain_text.encode("utf-8"), + capture_output=True, + check=False, + ) + if proc.returncode != 0: + stderr = proc.stderr.decode("utf-8", errors="replace").strip() + raise RuntimeError(stderr or "openssl RSA 加密失败") + return base64.b64encode(proc.stdout).decode("ascii") + + +def encrypt_login_field(value, dekey): + encrypted_public_key, aes_key = split_dekey(dekey) + public_key = aes_decrypt(encrypted_public_key, aes_key).strip() + if not public_key: + raise ValueError("dekey 解密后未得到有效公钥") + return rsa_encrypt(value, public_key) + + +def login_with_password(base_url, username, password, login_origin): + dekey = fetch_dekey(base_url) + payload = { + "name": encrypt_login_field(username, dekey), + "pwd": encrypt_login_field(password, dekey), + "origin": int(login_origin), + } + _, _, body = request_with_fallback( + base_url, + "POST", + ["/de2api/login/localLogin", "/login/localLogin"], + payload=payload, + headers={"Accept": "application/json;charset=UTF-8", "Content-Type": "application/json"}, + timeout=60, + ) + result = json.loads(body.decode("utf-8")) + data = extract_response_data(result, "账号密码登录") + if not isinstance(data, dict): + raise ValueError("账号密码登录接口返回格式不符合预期") + mfa = data.get("mfa") or {} + if isinstance(mfa, dict) and mfa.get("enabled"): + raise ValueError("当前账号开启了 MFA,skill 暂不支持 MFA 登录") + if data.get("invalidPwd"): + raise ValueError("当前账号需要修改无效密码后才能继续登录") + token = data.get("token") + if not token: + raise ValueError("账号密码登录成功,但接口未返回 token") + return { + "auth_mode": "password", + "x_de_token": token, + "token_exp": data.get("exp"), + "login_origin": int(login_origin), + } + + +def exchange_de_token(base_url, ask_auth, target_path, payload=None, target_method="POST"): + url = f"{base_url.rstrip('/')}/de2api/apisix/check" + headers = build_headers(ask_auth) + headers["X-Forwarded-Uri"] = target_path + headers["X-Forwarded-Method"] = target_method + _, response_headers, _ = post_json(url, payload, headers, timeout=60) + x_de_token = get_header(response_headers, "X-DE-TOKEN") + if not x_de_token: + raise ValueError("apisix/check 未返回 X-DE-TOKEN") + return x_de_token + + +def query_org_tree(base_url, headers, keyword="", desc=True): + url = f"{base_url.rstrip('/')}/de2api/org/page/tree" + _, _, body = post_json(url, {"keyword": keyword, "desc": bool(desc)}, headers, timeout=60) + return json.loads(body.decode("utf-8")) + + +def switch_organization(base_url, headers, org_id): + url = f"{base_url.rstrip('/')}/de2api/user/switch/{org_id}" + _, _, body = post_json(url, None, headers, timeout=60) + return json.loads(body.decode("utf-8")) + + +def query_resource_tree(base_url, headers, busi_type, resource_table): + url = f"{base_url.rstrip('/')}/de2api/dataVisualization/tree" + _, _, body = post_json( + url, + {"busiFlag": busi_type, "resourceTable": resource_table}, + headers, + timeout=60, + ) + return json.loads(body.decode("utf-8")) + + +def parse_pixel(pixel_text): + parts = [part.strip() for part in (pixel_text or "").split("*", 1)] + if len(parts) != 2 or not parts[0] or not parts[1]: + raise ValueError("pixel 格式必须是 宽*高,例如 1920*1080") + try: + width = int(parts[0]) + height = int(parts[1]) + except ValueError as err: + raise ValueError("pixel 宽高必须是整数") from err + if width <= 0 or height <= 0: + raise ValueError("pixel 宽高必须大于 0") + return width, height + + +def build_preview_url(base_url, resource_id, busi_type): + url = f"{base_url.rstrip('/')}/#/preview?dvId={resource_id}&dvType={busi_type}" + if (busi_type or "").lower() == "dashboard": + url += "&report=true" + return url + + +def resolve_capture_token(args, auth_context, target_path, target_payload=None): + request_mode = resolve_request_mode(args) + if getattr(args, "x_de_token", ""): + return args.x_de_token, { + "used_x_de_token": True, + "used_org_id": "", + "token_source": "user_supplied", + "auth_mode": "token", + "request_mode": request_mode, + } + + if auth_context.get("auth_mode") == "password": + base_token = auth_context["x_de_token"] + if getattr(args, "org_id", ""): + switch_result = switch_organization(args.base_url, build_token_headers(base_token), args.org_id) + switch_data = extract_response_data(switch_result, "切换组织") + x_de_token = switch_data.get("token") if isinstance(switch_data, dict) else None + if not x_de_token: + raise ValueError("切换组织接口未返回 data.token") + return x_de_token, { + "used_x_de_token": True, + "used_org_id": str(args.org_id), + "token_exp": switch_data.get("exp"), + "token_source": "switched_org", + "auth_mode": "password", + "request_mode": request_mode, + } + return base_token, { + "used_x_de_token": True, + "used_org_id": "", + "token_exp": auth_context.get("token_exp"), + "token_source": "password_login", + "auth_mode": "password", + "request_mode": request_mode, + } + + if getattr(args, "org_id", ""): + if request_mode == "gateway": + switch_headers = build_headers(auth_context) + else: + de_token = exchange_de_token(args.base_url, auth_context, f"/de2api/user/switch/{args.org_id}") + switch_headers = build_token_headers(de_token) + switch_result = switch_organization(args.base_url, switch_headers, args.org_id) + switch_data = extract_response_data(switch_result, "切换组织") + x_de_token = switch_data.get("token") if isinstance(switch_data, dict) else None + if not x_de_token: + raise ValueError("切换组织接口未返回 data.token") + return x_de_token, { + "used_x_de_token": True, + "used_org_id": str(args.org_id), + "token_exp": switch_data.get("exp"), + "token_source": "switched_org", + "auth_mode": "ask_token", + "request_mode": request_mode, + } + + x_de_token = exchange_de_token(args.base_url, auth_context, target_path, target_payload) + return x_de_token, { + "used_x_de_token": True, + "used_org_id": "", + "token_source": "apisix_check", + "auth_mode": "ask_token", + "request_mode": request_mode, + } + +# fixed by xuhuanqing +''' +def run_browser_capture(preview_url, x_de_token, pixel, ext_wait_time, result_format, output_path): + if shutil.which("node") is None: + raise RuntimeError("当前环境缺少 node 命令,无法执行本地浏览器截图") + if not BROWSER_CAPTURE_SCRIPT.exists(): + raise RuntimeError(f"未找到浏览器截图脚本: {BROWSER_CAPTURE_SCRIPT}") + + width, height = parse_pixel(pixel) + cmd = [ + "node", + str(BROWSER_CAPTURE_SCRIPT), + "--url", + preview_url, + "--token", + x_de_token, + "--width", + str(width), + "--height", + str(height), + "--wait-seconds", + str(ext_wait_time), + "--result-format", + str(result_format), + "--output", + str(output_path), + ] + proc = subprocess.run( + cmd, + cwd=str(ROOT_DIR), + capture_output=True, + text=True, + check=False, + ) + stdout = proc.stdout.strip() + stderr = proc.stderr.strip() + if proc.returncode != 0: + detail = stderr or stdout or "浏览器截图失败" + raise RuntimeError(detail) + if not output_path.exists(): + raise RuntimeError("浏览器截图命令执行成功,但未生成输出文件") + if stdout: + try: + return json.loads(stdout) + except json.JSONDecodeError: + return {"raw_output": stdout} + return {} +''' + +def run_browser_capture(preview_url, x_de_token, pixel, ext_wait_time, result_format, output_path): + if shutil.which("node") is None: + raise RuntimeError("当前环境缺少 node 命令,无法执行本地浏览器截图") + if not BROWSER_CAPTURE_SCRIPT.exists(): + raise RuntimeError(f"未找到浏览器截图脚本: {BROWSER_CAPTURE_SCRIPT}") + + width, height = parse_pixel(pixel) + cmd = [ + "node", + str(BROWSER_CAPTURE_SCRIPT), + "--url", + preview_url, + "--token", + x_de_token, + "--width", + str(width), + "--height", + str(height), + "--wait-seconds", + str(ext_wait_time), + "--result-format", + str(result_format), + "--output", + str(output_path), + ] + # 移除 text=True,原始bytes捕获,规避GBK编码崩溃 + proc = subprocess.run( + cmd, + cwd=str(ROOT_DIR), + capture_output=True, + check=False, + ) + # 容错解码stdout + try: + stdout = proc.stdout.decode("utf-8", errors="replace").strip() + except Exception: + stdout = "" + # 容错解码stderr + try: + stderr = proc.stderr.decode("utf-8", errors="replace").strip() + except Exception: + stderr = "" + + if proc.returncode != 0: + detail = stderr or stdout or "浏览器截图失败" + raise RuntimeError(detail) + if not output_path.exists(): + raise RuntimeError("浏览器截图命令执行成功,但未生成输出文件") + if stdout: + try: + return json.loads(stdout) + except json.JSONDecodeError: + return {"raw_output": stdout} + return {} + + +def score_resource(item, query): + name = item.get("name", "") + name_norm = normalize(name) + query_norm = normalize(query) + score = SequenceMatcher(None, query_norm, name_norm).ratio() + if query_norm == name_norm: + score += 1.0 + elif query_norm and query_norm in name_norm: + score += 0.25 + if item.get("leaf"): + score += 0.05 + return score + + +def search_resources(resources, query, top_n=20): + scored = [] + for item in resources: + if not item.get("name"): + continue + scored.append((score_resource(item, query), item)) + scored.sort(key=lambda item: item[0], reverse=True) + return scored[:top_n] + + +def resolve_resource(resources, query, alias_file, min_score): + aliases = load_aliases(alias_file) + resolved_query = aliases.get(query, query) + leaf_resources = [item for item in resources if item.get("leaf")] + query_norm = normalize(resolved_query) + + exact_matches = [ + item for item in leaf_resources + if normalize(item.get("name")) == query_norm + ] + if len(exact_matches) == 1: + return { + "ok": True, + "resolved_query": resolved_query, + "resource": exact_matches[0], + "candidates": [{"score": 2.05, **exact_matches[0]}], + }, 0 + if len(exact_matches) > 1: + return { + "ok": False, + "stage": "match", + "error": "存在多个同名资源,无法唯一确定导出目标", + "query": resolved_query, + "candidates": [{"score": 2.05, **item} for item in exact_matches], + }, 2 + + candidates = search_resources(leaf_resources, resolved_query, top_n=5) + if not candidates: + return { + "ok": False, + "stage": "match", + "error": "资源树中没有找到任何候选资源", + "query": resolved_query, + }, 2 + + candidate_payload = [ + { + "score": round(score, 4), + "id": item.get("id"), + "name": item.get("name"), + "leaf": item.get("leaf"), + "type": item.get("type"), + } + for score, item in candidates + ] + best_score, best_item = candidates[0] + second_score = candidates[1][0] if len(candidates) > 1 else None + + if best_score < min_score: + return { + "ok": False, + "stage": "match", + "error": "没有找到足够可信的匹配结果", + "query": resolved_query, + "candidates": candidate_payload, + }, 2 + + if second_score is not None and abs(best_score - second_score) < 0.08: + return { + "ok": False, + "stage": "match", + "error": "存在多个相似资源,无法安全猜测导出目标", + "query": resolved_query, + "candidates": candidate_payload, + }, 2 + + return { + "ok": True, + "resolved_query": resolved_query, + "resource": best_item, + "candidates": candidate_payload, + }, 0 + + +def guess_extension(result_format, content_type): + content_type = (content_type or "").lower() + if result_format == 1 or "pdf" in content_type: + return ".pdf" + return ".jpg" + + +def load_auth(args): + if not args.base_url: + print_json({ + "ok": False, + "stage": "config", + "error": "缺少必需配置,请通过命令行参数、系统环境变量或 .env 提供", + "missing": ["DATAEASE_BASE_URL"], + }, 1) + + if getattr(args, "x_de_token", ""): + return {"auth_mode": "token"} + + has_password_auth = bool(args.username or args.password) + if has_password_auth: + missing = [] + if not args.username: + missing.append("DATAEASE_USERNAME") + if not args.password: + missing.append("DATAEASE_PASSWORD") + if missing: + print_json({ + "ok": False, + "stage": "config", + "error": "用户名密码登录配置不完整,请同时提供用户名和密码", + "missing": missing, + }, 1) + try: + return login_with_password(args.base_url, args.username.strip(), args.password, args.login_origin) + except Exception as err: + print_json({"ok": False, "stage": "auth", "error": str(err)}, 1) + + missing = [] + if not args.access_key: + missing.append("DATAEASE_ACCESS_KEY") + if not args.secret_key: + missing.append("DATAEASE_SECRET_KEY") + if missing: + missing.extend(["DATAEASE_USERNAME", "DATAEASE_PASSWORD"]) + print_json({ + "ok": False, + "stage": "config", + "error": "缺少鉴权配置,请提供 accessKey/secretKey 或 username/password", + "missing": missing, + }, 1) + + try: + ask_auth = build_ask_auth(args.access_key, args.secret_key) + ask_auth["auth_mode"] = "ask_token" + return ask_auth + except Exception as err: + print_json({"ok": False, "stage": "auth", "error": str(err)}, 1) + + +def add_common_auth_args(parser): + parser.add_argument("--base-url", default=os.getenv("DATAEASE_BASE_URL", "")) + parser.add_argument("--access-key", default=os.getenv("DATAEASE_ACCESS_KEY", "")) + parser.add_argument("--secret-key", default=os.getenv("DATAEASE_SECRET_KEY", "")) + parser.add_argument("--username", default=os.getenv("DATAEASE_USERNAME", "")) + parser.add_argument("--password", default=os.getenv("DATAEASE_PASSWORD", "")) + parser.add_argument("--login-origin", type=int, default=int(os.getenv("DATAEASE_LOGIN_ORIGIN", "0"))) + parser.add_argument("--request-mode", default=os.getenv("DATAEASE_REQUEST_MODE", "auto"), choices=["auto", "gateway", "backend"]) + + +def add_runtime_args(parser): + parser.add_argument("--org-id", default="") + parser.add_argument("--x-de-token", default="") + + +def add_resource_tree_args(parser): + add_runtime_args(parser) + parser.add_argument("--busi-type", default="dashboard", choices=["dashboard", "dataV"]) + parser.add_argument("--resource-table", default="core") + + +def build_parser(): + parser = argparse.ArgumentParser(description="查询 DataEase 组织、资源并导出截图或 PDF") + subparsers = parser.add_subparsers(dest="command") + + list_orgs = subparsers.add_parser("list-orgs", help="查询组织树") + add_common_auth_args(list_orgs) + list_orgs.add_argument("--org-keyword", default="") + + switch_org = subparsers.add_parser("switch-org", help="切换组织并返回 x-de-token") + add_common_auth_args(switch_org) + switch_org.add_argument("--org-id", required=True) + + list_resources = subparsers.add_parser("list-resources", help="查询组织下的仪表板或大屏列表") + add_common_auth_args(list_resources) + add_resource_tree_args(list_resources) + list_resources.add_argument("--resource-name", default="") + list_resources.add_argument("--limit", type=int, default=100) + list_resources.add_argument("--alias-file", default=str(DEFAULT_ALIAS_FILE)) + + capture = subparsers.add_parser("capture", help="导出截图或 PDF") + add_common_auth_args(capture) + add_resource_tree_args(capture) + name_or_id = capture.add_mutually_exclusive_group(required=True) + name_or_id.add_argument("--resource-name") + name_or_id.add_argument("--resource-id") + capture.add_argument("--alias-file", default=str(DEFAULT_ALIAS_FILE)) + capture.add_argument("--min-score", type=float, default=0.55) + capture.add_argument("--pixel", default="1920*1080") + capture.add_argument("--ext-wait-time", type=int, default=0) + capture.add_argument("--result-format", type=int, default=0, choices=[0, 1]) + capture.add_argument("--output-dir", default="outputs") + + return parser + + +def parse_args(): + parser = build_parser() + argv = sys.argv[1:] + commands = {"list-orgs", "switch-org", "list-resources", "capture"} + if not argv: + parser.print_help() + parser.exit(0) + if argv[0] not in commands and argv[0] not in {"-h", "--help"}: + argv = ["capture"] + argv + return parser.parse_args(argv) + + +def infer_request_mode(base_url): + port = urlparse(base_url).port + if port == 8100: + return "backend" + return "gateway" + + +def resolve_request_mode(args): + if getattr(args, "request_mode", "auto") != "auto": + return args.request_mode + return infer_request_mode(args.base_url) + + +def resolve_runtime_headers(args, auth_context, target_path, target_payload=None): + request_mode = resolve_request_mode(args) + if getattr(args, "x_de_token", ""): + return build_token_headers(args.x_de_token), { + "used_x_de_token": True, + "used_org_id": "", + "token_source": "user_supplied", + "auth_mode": "token", + "request_mode": request_mode, + } + + if auth_context.get("auth_mode") == "password": + base_token = auth_context["x_de_token"] + if getattr(args, "org_id", ""): + switch_result = switch_organization(args.base_url, build_token_headers(base_token), args.org_id) + switch_data = extract_response_data(switch_result, "切换组织") + x_de_token = switch_data.get("token") if isinstance(switch_data, dict) else None + if not x_de_token: + raise ValueError("切换组织接口未返回 data.token") + return build_token_headers(x_de_token), { + "used_x_de_token": True, + "used_org_id": str(args.org_id), + "token_exp": switch_data.get("exp"), + "token_source": "switched_org", + "auth_mode": "password", + "request_mode": request_mode, + } + return build_token_headers(base_token), { + "used_x_de_token": True, + "used_org_id": "", + "token_exp": auth_context.get("token_exp"), + "token_source": "password_login", + "auth_mode": "password", + "request_mode": request_mode, + } + + if request_mode == "gateway": + if getattr(args, "org_id", ""): + switch_result = switch_organization(args.base_url, build_headers(auth_context), args.org_id) + switch_data = extract_response_data(switch_result, "切换组织") + x_de_token = switch_data.get("token") if isinstance(switch_data, dict) else None + if not x_de_token: + raise ValueError("切换组织接口未返回 data.token") + return build_token_headers(x_de_token), { + "used_x_de_token": True, + "used_org_id": str(args.org_id), + "token_exp": switch_data.get("exp"), + "token_source": "switched_org", + "auth_mode": "ask_token", + "request_mode": request_mode, + } + + return build_headers(auth_context), { + "used_x_de_token": False, + "used_org_id": "", + "token_source": "ask_token", + "auth_mode": "ask_token", + "request_mode": request_mode, + } + + if getattr(args, "org_id", ""): + de_token = exchange_de_token(args.base_url, auth_context, f"/de2api/user/switch/{args.org_id}") + switch_result = switch_organization(args.base_url, build_token_headers(de_token), args.org_id) + switch_data = extract_response_data(switch_result, "切换组织") + x_de_token = switch_data.get("token") if isinstance(switch_data, dict) else None + if not x_de_token: + raise ValueError("切换组织接口未返回 data.token") + return build_token_headers(x_de_token), { + "used_x_de_token": True, + "used_org_id": str(args.org_id), + "token_exp": switch_data.get("exp"), + "token_source": "switched_org", + "auth_mode": "ask_token", + "request_mode": request_mode, + } + + de_token = exchange_de_token(args.base_url, auth_context, target_path, target_payload) + return build_token_headers(de_token), { + "used_x_de_token": True, + "used_org_id": "", + "token_source": "apisix_check", + "auth_mode": "ask_token", + "request_mode": request_mode, + } + + +def command_list_orgs(args, auth_context): + try: + request_payload = {"keyword": args.org_keyword, "desc": True} + headers, runtime_info = resolve_runtime_headers(args, auth_context, "/de2api/org/page/tree", request_payload) + org_tree = query_org_tree(args.base_url, headers, args.org_keyword) + org_data = extract_response_data(org_tree, "组织树") + organizations = flatten_org_tree(org_data) + print_json({ + "ok": True, + "stage": "org_tree", + "org_keyword": args.org_keyword, + "organizations": organizations, + "total": len(organizations), + **runtime_info, + }, 0) + except Exception as err: + print_json(error_to_dict("org_tree", err, {"base_url": args.base_url}), 1) + + +def command_switch_org(args, auth_context): + try: + request_mode = resolve_request_mode(args) + if auth_context.get("auth_mode") == "password": + switch_headers = build_token_headers(auth_context["x_de_token"]) + auth_mode = "password" + elif request_mode == "gateway": + switch_headers = build_headers(auth_context) + auth_mode = "ask_token" + else: + de_token = exchange_de_token(args.base_url, auth_context, f"/de2api/user/switch/{args.org_id}") + switch_headers = build_token_headers(de_token) + auth_mode = "ask_token" + switch_result = switch_organization(args.base_url, switch_headers, args.org_id) + switch_data = extract_response_data(switch_result, "切换组织") + x_de_token = switch_data.get("token") if isinstance(switch_data, dict) else None + if not x_de_token: + raise ValueError("切换组织接口未返回 data.token") + print_json({ + "ok": True, + "stage": "switch_org", + "org_id": str(args.org_id), + "x_de_token": x_de_token, + "token_exp": switch_data.get("exp"), + "token_source": "switched_org", + "auth_mode": auth_mode, + "request_mode": request_mode, + }, 0) + except Exception as err: + print_json(error_to_dict("switch_org", err, {"org_id": args.org_id}), 1) + + +def command_list_resources(args, auth_context): + try: + request_payload = {"busiFlag": args.busi_type, "resourceTable": args.resource_table} + headers, runtime_info = resolve_runtime_headers(args, auth_context, "/de2api/dataVisualization/tree", request_payload) + resource_tree = query_resource_tree(args.base_url, headers, args.busi_type, args.resource_table) + resources = [item for item in flatten_tree(extract_tree_nodes(resource_tree)) if item.get("leaf")] + + if args.resource_name: + aliases = load_aliases(args.alias_file) + resolved_query = aliases.get(args.resource_name, args.resource_name) + candidates = [ + { + "score": round(score, 4), + "id": item.get("id"), + "name": item.get("name"), + "leaf": item.get("leaf"), + "type": item.get("type"), + } + for score, item in search_resources(resources, resolved_query, top_n=args.limit) + ] + print_json({ + "ok": True, + "stage": "resource_list", + "busi_type": args.busi_type, + "resource_name": args.resource_name, + "resolved_query": resolved_query, + "resources": candidates, + "total": len(candidates), + **runtime_info, + }, 0) + + resource_list = sorted(resources, key=lambda item: (item.get("name") or "").lower())[:args.limit] + print_json({ + "ok": True, + "stage": "resource_list", + "busi_type": args.busi_type, + "resources": resource_list, + "total": len(resource_list), + **runtime_info, + }, 0) + except Exception as err: + print_json(error_to_dict("resource_list", err, {"busi_type": args.busi_type}), 1) + + +def command_capture(args, auth_context): + try: + request_payload = {"busiFlag": args.busi_type, "resourceTable": args.resource_table} + x_de_token, runtime_info = resolve_capture_token(args, auth_context, "/de2api/dataVisualization/tree", request_payload) + headers = build_token_headers(x_de_token) + resource_tree = query_resource_tree(args.base_url, headers, args.busi_type, args.resource_table) + resources = flatten_tree(extract_tree_nodes(resource_tree)) + + if args.resource_id: + target = next((item for item in resources if str(item.get("id")) == str(args.resource_id)), None) + if not target: + raise ValueError(f"资源树中未找到 resource_id={args.resource_id}") + if not target.get("leaf"): + raise ValueError(f"resource_id={args.resource_id} 对应的是目录节点,不能直接导出") + resolved_query = target.get("name") + candidates = [{ + "score": None, + "id": target.get("id"), + "name": target.get("name"), + "leaf": target.get("leaf"), + "type": target.get("type"), + }] + else: + resolved, code = resolve_resource(resources, args.resource_name, args.alias_file, args.min_score) + if code != 0: + print_json({**resolved, **runtime_info}, code) + target = resolved["resource"] + resolved_query = resolved["resolved_query"] + candidates = resolved["candidates"] + + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + safe_name = re.sub(r"[^\w\u4e00-\u9fff-]+", "_", target["name"]).strip("_") or "capture" + ext = guess_extension(args.result_format, "") + output_path = (output_dir / f"{safe_name}_{target['id']}{ext}").resolve() + preview_url = build_preview_url(args.base_url, target["id"], args.busi_type) + browser_result = run_browser_capture( + preview_url, + x_de_token, + args.pixel, + args.ext_wait_time, + args.result_format, + output_path, + ) + except Exception as err: + extra = { + "busi_type": args.busi_type, + } + if getattr(args, "resource_name", None): + extra["resource_name"] = args.resource_name + if getattr(args, "resource_id", None): + extra["resource_id"] = args.resource_id + print_json(error_to_dict("capture", err, extra), 1) + + print_json({ + "ok": True, + "stage": "capture", + "resource_id": target["id"], + "resource_name": target["name"], + "resolved_query": resolved_query, + "busi_type": args.busi_type, + "pixel": args.pixel, + "ext_wait_time": args.ext_wait_time, + "result_format": args.result_format, + "preview_url": preview_url, + "saved_file": str(output_path), + "candidates": candidates, + "capture_engine": "local_playwright", + "capture_meta": browser_result, + **runtime_info, + }, 0) + + +def main(): + load_dotenv(ROOT_DIR / ".env") + args = parse_args() + auth_context = load_auth(args) + + if args.command == "list-orgs": + command_list_orgs(args, auth_context) + elif args.command == "switch-org": + command_switch_org(args, auth_context) + elif args.command == "list-resources": + command_list_resources(args, auth_context) + elif args.command == "capture": + command_capture(args, auth_context) + else: + print_json({"ok": False, "stage": "args", "error": f"不支持的命令: {args.command}"}, 1) + + +if __name__ == "__main__": + main() diff --git a/scripts/client.py b/scripts/client.py new file mode 100644 index 0000000..e5f866d --- /dev/null +++ b/scripts/client.py @@ -0,0 +1,82 @@ +import json +import time +import uuid +import base64 +import os +import requests +import jwt +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.backends import default_backend +import urllib3 + +urllib3.disable_warnings() + +class DataEaseClient: + def __init__(self, base_url, access_key, secret_key): + # Separate base URL and API prefix + self.base_url = base_url.rstrip('/') + self.api_prefix = os.environ.get('DATAEASE_API_PREFIX', '/de2api') + self.access_key = access_key + self.secret_key = secret_key + + def _get_signature(self, uid, timestamp): + src_str = f"{self.access_key}|{uid}|{timestamp}" + padder = padding.PKCS7(128).padder() + padded_data = padder.update(src_str.encode('utf-8')) + padder.finalize() + cipher = Cipher( + algorithms.AES(self.secret_key.encode('utf-8')), + modes.CBC(self.access_key.encode('utf-8')), + backend=default_backend() + ) + encryptor = cipher.encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + return base64.b64encode(ciphertext).decode('utf-8') + + def _get_headers(self): + timestamp = str(int(time.time() * 1000)) + uid = str(uuid.uuid4()) + signature = self._get_signature(uid, timestamp) + claims = { + "accessKey": self.access_key, + "signature": signature + } + token = jwt.encode(claims, self.secret_key, algorithm="HS256") + if isinstance(token, bytes): + token = token.decode('utf-8') + + return { + "accessKey": self.access_key, + "signature": signature, + "x-de-ask-token": token, + "timestamp": timestamp, + "nonce": uid, + "Content-Type": "application/json" + } + + def get(self, path, params=None): + url = f"{self.base_url}{self.api_prefix}{path}" + response = requests.get(url, headers=self._get_headers(), params=params, verify=False) + return response + + def post(self, path, payload=None): + url = f"{self.base_url}{self.api_prefix}{path}" + response = requests.post(url, headers=self._get_headers(), json=payload, verify=False) + return response + + def get_dataset_fields(self, dataset_id): + return self.post(f"/datasetField/listByDatasetGroup/{dataset_id}").json() + + def update_publish_status(self, dashboard_id, name, status=1, type='dashboard', mobile_layout=False, active_view_ids=None): + url = f"{self.base_url}{self.api_prefix}/dataVisualization/updatePublishStatus" + payload = { + "id": dashboard_id, + "name": name, + "mobileLayout": mobile_layout, + "activeViewIds": active_view_ids or [], + "status": status, + "type": type + } + response = requests.post(url, headers=self._get_headers(), json=payload, verify=False) + response.raise_for_status() + return response.json() diff --git a/scripts/deploy.py b/scripts/deploy.py new file mode 100644 index 0000000..91537e7 --- /dev/null +++ b/scripts/deploy.py @@ -0,0 +1,158 @@ +import sys +import os +import json +import subprocess + +# Add local path for engine import +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from engine import DataEaseChartEngine + +# --- Configuration from Environment --- +def load_dotenv(): + env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env") + if os.path.exists(env_path): + with open(env_path) as f: + for line in f: + line = line.strip() + if line and not line.startswith('#') and '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip().strip('"').strip("'") + +load_dotenv() + +ACCESS_KEY = os.environ.get("DATAEASE_ACCESS_KEY") +SECRET_KEY = os.environ.get("DATAEASE_SECRET_KEY") +BASE_URL = os.environ.get("DATAEASE_BASE_URL") +USERNAME = os.environ.get("DATAEASE_USERNAME") +PASSWORD = os.environ.get("DATAEASE_PASSWORD") +LOGIN_ORIGIN = os.environ.get("DATAEASE_LOGIN_ORIGIN", "0") + +# Screenshot configuration - auto-detect OpenClaw workspace for MEDIA: display +def _default_output_dir(): + """优先使用 OpenClaw workspace 目录,否则用 skill 本地 output 目录""" + # 1. 环境变量显式指定 + env_dir = os.environ.get("DATAEASE_OUTPUT_DIR") + if env_dir: + return env_dir + # 2. 自动检测 OpenClaw workspace + home = os.path.expanduser("~") + workspace_dir = os.path.join(home, ".openclaw", "workspace", "dataease-output") + if os.path.isdir(os.path.join(home, ".openclaw", "workspace")): + os.makedirs(workspace_dir, exist_ok=True) + return workspace_dir + # 3. 回退到 skill 本地 output + return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "output") + +OUTPUT_DIR = _default_output_dir() +DEFAULT_PIXEL = "2560*1440" # 更高分辨率,图片更清晰 + +def capture_dashboard(dashboard_id, output_format="jpeg"): + """调用 capture_dashboard.py 进行截图""" + scripts_dir = os.path.dirname(os.path.abspath(__file__)) + capture_script = os.path.join(scripts_dir, "capture_dashboard.py") + + if not os.path.exists(capture_script): + return None, "Screenshot script not found" + + # 确保输出目录存在 + os.makedirs(OUTPUT_DIR, exist_ok=True) + + # capture_dashboard.py 使用相同的 BASE_URL(不含 /de2api 后缀) + capture_base_url = BASE_URL.rstrip("/") + + # 调用截图脚本 + result_format = "0" if output_format == "jpeg" else "1" # 0=jpeg, 1=pdf + cmd = [ + "python3", capture_script, + "capture", + "--resource-id", str(dashboard_id), + "--busi-type", "dashboard", + "--output-dir", OUTPUT_DIR, + "--result-format", result_format, + "--base-url", capture_base_url, + "--pixel", DEFAULT_PIXEL + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + if result.returncode == 0: + output = json.loads(result.stdout) + if output.get("ok"): + return output.get("saved_file"), None + else: + return None, output.get("error", "Unknown capture error") + else: + return None, result.stderr or "Capture failed" + except subprocess.TimeoutExpired: + return None, "Capture timeout" + except Exception as e: + return None, str(e) + +def main(): + if not BASE_URL: + print("Error: Missing required environment variable DATAEASE_BASE_URL.") + sys.exit(1) + if not ACCESS_KEY and not USERNAME: + print("Error: Missing authentication. Please set either:") + print(" DATAEASE_ACCESS_KEY + DATAEASE_SECRET_KEY") + print(" or DATAEASE_USERNAME + DATAEASE_PASSWORD") + sys.exit(1) + + if len(sys.argv) < 5: + print("Usage: python3 deploy.py <type> <title> <dataset_name_or_id> <x_fields> <y_fields> [--no-screenshot]") + print("Example: python3 deploy.py line 'Skill Test' '电商用户购买行为' '访问平台' '访问次数'") + sys.exit(1) + + chart_type = sys.argv[1] + title = sys.argv[2] + dataset_id = sys.argv[3] + x_axis = [f.strip() for f in sys.argv[4].split(',') if f.strip()] + y_axis = [f.strip() for f in sys.argv[5].split(',') if f.strip()] + + no_screenshot = "--no-screenshot" in sys.argv + + if ACCESS_KEY and SECRET_KEY: + engine = DataEaseChartEngine(BASE_URL, ACCESS_KEY, SECRET_KEY) + else: + engine = DataEaseChartEngine(BASE_URL, USERNAME, PASSWORD, auth_mode="password") + + try: + did, url = engine.deploy(chart_type, title, dataset_id, x_axis, y_axis) + print(f"\n✅ Successfully deployed!") + print(f"Dashboard ID: {did}") + print(f"URL: {url}") + + # 自动截图 + if not no_screenshot: + print("\n📸 Capturing screenshot...") + screenshot_path, error = capture_dashboard(did) + if screenshot_path: + print(f"Screenshot saved: {screenshot_path}") + # 输出 JSON 结果供 Agent 解析 + result = { + "ok": True, + "dashboard_id": str(did), + "url": url, + "screenshot": screenshot_path, + "title": title + } + print(f"\n__RESULT_JSON__\n{json.dumps(result, ensure_ascii=False)}\n__END_JSON__") + else: + print(f"⚠️ Screenshot failed: {error}") + result = { + "ok": True, + "dashboard_id": str(did), + "url": url, + "screenshot": None, + "screenshot_error": error, + "title": title + } + print(f"\n__RESULT_JSON__\n{json.dumps(result, ensure_ascii=False)}\n__END_JSON__") + + except Exception as e: + print(f"\n❌ Deployment failed: {str(e)}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/engine.py b/scripts/engine.py new file mode 100644 index 0000000..71f5883 --- /dev/null +++ b/scripts/engine.py @@ -0,0 +1,326 @@ +import json, re, time, random, os, sys, requests +import urllib3 +urllib3.disable_warnings() + +# Local SDK client +from client import DataEaseClient + +class DataEaseChartEngine: + def __init__(self, base_url, ak_or_user, sk_or_password, auth_mode=None): + self.base_url = base_url.rstrip('/') + self.api_prefix = os.environ.get('DATAEASE_API_PREFIX', '/de2api') + self.auth_mode = auth_mode or 'aksk' + + if self.auth_mode == 'password': + import importlib.util + spec = importlib.util.spec_from_file_location("inspect_data", os.path.join(os.path.dirname(__file__), "inspect_data.py")) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + + x_de_token = mod.login_with_password(base_url, self.api_prefix, ak_or_user, sk_or_password) + self.headers = mod.get_headers_token(x_de_token) + self.client = None + else: + self.client = DataEaseClient(base_url, ak_or_user, sk_or_password) + self.headers = None + + def _get_headers(self): + if self.headers: + return self.headers + return self._get_headers() + + def _post(self, path, payload=None): + url = f"{self.base_url}{self.api_prefix}{path}" + return requests.post(url, headers=self._get_headers(), json=payload, verify=False, timeout=30) + + def _get(self, path, params=None): + url = f"{self.base_url}{self.api_prefix}{path}" + return requests.get(url, headers=self._get_headers(), params=params, verify=False, timeout=30) + + def resolve_dataset_id(self, name_or_id): + """Resolve dataset name to ID using /datasetTree/tree if needed""" + if str(name_or_id).isdigit() and len(str(name_or_id)) > 10: + return name_or_id + + url = f"{self.base_url}{self.api_prefix}/datasetTree/tree" + headers = self._get_headers() + resp = requests.post(url, headers=headers, json={"busiFlag": "dataset"}, verify=False, timeout=30) + + if resp.status_code != 200: + raise Exception(f"Failed to fetch dataset tree: {resp.text}") + + nodes = resp.json().get('data', []) + + def find_in_tree(items, target_name): + for item in items: + if item.get('name') == target_name: + return item.get('id') + children = item.get('children', []) + if children: + found = find_in_tree(children, target_name) + if found: return found + return None + + dataset_id = find_in_tree(nodes, name_or_id) + if not dataset_id: + raise ValueError(f"Dataset '{name_or_id}' not found in DataEase") + + return dataset_id + + def get_dataset_ctx(self, dataset_name_or_id, x_names, y_names): + """Fetch dataset metadata and build rendering context""" + dataset_id = self.resolve_dataset_id(dataset_name_or_id) + + # DataEase v2 often uses datasetGroup for the tree structure + url = f"{self.base_url}{self.api_prefix}/datasetTree/details/{dataset_id}" + headers = self._get_headers() + resp = requests.get(url, headers=headers, verify=False, timeout=30) + + if resp.status_code != 200: + # Fallback to the original method if the specific tree detail endpoint fails + resp = self._post(f"/datasetField/listByDatasetGroup/{dataset_id}") + fields = resp.json().get('data', []) + else: + data = resp.json().get('data', {}) + fields = data.get('allFields', []) + if not fields: + # Try to get from datasetField/listByDatasetGroup if tree details is empty + resp = self._post(f"/datasetField/listByDatasetGroup/{dataset_id}") + fields = resp.json().get('data', []) + + if not fields: + raise ValueError(f"No fields found for dataset {dataset_id}") + + f_map = {f['name']: f for f in fields} + + # Get table/datasource IDs from the first field (they should be common) + datasource_id = fields[0].get('datasourceId', "") + table_id = fields[0].get('datasetTableId', "") + + ctx = { + "DATASET_GROUP_ID": dataset_id, + "DATASOURCE_ID": datasource_id, + "DATASET_TABLE_ID": table_id, + } + + # Map axis fields (supports multi-measure if needed) + for i, name in enumerate(x_names): + suffix = "" if i == 0 else str(i+1) + f = f_map.get(name) + if not f: raise ValueError(f"X-Axis Field '{name}' not found") + ctx[f"XAXIS{suffix}_FIELD_ID"] = f['id'] + ctx[f"XAXIS{suffix}_DE_NAME"] = f['dataeaseName'] + + for i, name in enumerate(y_names): + suffix = "" if i == 0 else str(i+1) + f = f_map.get(name) + if not f: raise ValueError(f"Y-Axis Field '{name}' not found") + ctx[f"YAXIS{suffix}_FIELD_ID"] = f['id'] + ctx[f"YAXIS{suffix}_DE_NAME"] = f['dataeaseName'] + ctx[f"YAXIS{suffix}_SERIES_ID"] = f"{f['id']}-yAxis" + + # Additional keys for deep parameterization + if i == 0: + ctx["YAXIS_FIELD_ID"] = f['id'] + ctx["YAXIS_DE_NAME"] = f['dataeaseName'] + ctx["YAXIS_SERIES_ID"] = f"{f['id']}-yAxis" + elif i == 1: + ctx["YAXIS2_FIELD_ID"] = f['id'] + ctx["YAXIS2_DE_NAME"] = f['dataeaseName'] + ctx["YAXIS2_SERIES_ID"] = f"{f['id']}-yAxis" + + return ctx + + def deploy(self, chart_type, title, dataset_id, x_names, y_names, layout=None): + # 1. Directory standardization + # Use relative path from this script (scripts/engine.py) to templates/ + base_dir = os.path.dirname(os.path.abspath(__file__)) + tpl_dir = os.path.join(base_dir, "..", "templates", f"chart_{chart_type}") + + if not os.path.exists(tpl_dir): + raise FileNotFoundError(f"Template directory {tpl_dir} not found") + + with open(os.path.join(tpl_dir, "template.j2")) as f: template_str = f.read() + with open(os.path.join(tpl_dir, "params.json")) as f: + raw_params = json.load(f) + + # 2. Build Context (Flatten params + dynamic IDs) + ctx = {} + # Flatten nested params.json + def flatten_dict(d): + items = {} + for k, v in d.items(): + if isinstance(v, dict): + items.update(flatten_dict(v)) + elif not k.startswith("_"): + items[k] = v + return items + + ctx.update(flatten_dict(raw_params)) + ctx.update(self.get_dataset_ctx(dataset_id, x_names, y_names)) + + # 3. Runtime Randomization + view_id, content_id = self.rand_id(), self.rand_id() + ctx.update({"VIEW_ID": view_id, "CONTENT_ID": content_id, "SCENE_ID": "0"}) + + # 4. Thorough Parameterization (Global substitution) + def rep(m): + k = m.group(1).strip() + if k in ctx: + return str(ctx[k]) + return m.group(0) # Keep placeholder if not found + + rendered = re.sub(r"\{\{\s*(\w+)\s*\}\}", rep, template_str) + payload = json.loads(rendered) + + # 5. Type Closure & Payload Finalization + board_name = f"{title}_{int(time.time())}" + + # Override title in canvasViewInfo + if "canvasViewInfo" in payload and view_id in payload["canvasViewInfo"]: + view_info = payload["canvasViewInfo"][view_id] + view_info["title"] = title + + # Deep update all field names (xAxis, yAxis, labels, tooltips, etc.) + def deep_update_field_names(obj, target_id, new_name): + if isinstance(obj, dict): + if str(obj.get("id")) == str(target_id): + obj["name"] = new_name + if "description" in obj: obj["description"] = new_name + if "originName" in obj: obj["originName"] = new_name + # 核心修复:DataEase V2 在某些场景下会回退到 dbFieldName,强制设为 null 或同步 + if "dbFieldName" in obj: obj["dbFieldName"] = None + + # 特殊修复:处理 DataEase 特有的标签和提示框拼接字段 (如 "访问平台(求和)") + # 我们无法预知原始模板里的名字,所以这里尝试匹配常见的拼接模式 + if "optionLabel" in obj: + obj["optionLabel"] = new_name + (obj["optionLabel"].partition("(")[1] + obj["optionLabel"].partition("(")[2] if "(" in obj["optionLabel"] else "") + if "optionShowName" in obj: + obj["optionShowName"] = new_name + (obj["optionShowName"].partition("(")[1] + obj["optionShowName"].partition("(")[2] if "(" in obj["optionShowName"] else "") + + for v in obj.values(): + deep_update_field_names(v, target_id, new_name) + elif isinstance(obj, list): + for item in obj: + deep_update_field_names(item, target_id, new_name) + + # Apply updates for X and Y axis fields + # We use the field IDs obtained from dataset metadata to find and replace + dataset_ctx = self.get_dataset_ctx(dataset_id, x_names, y_names) + + # Pre-scan templates/params.json for the old names to ensure replacement works + # or use common default names from templates + old_names_to_clear = ["访问次数", "访问平台", "浏览量"] + + for i, x_name in enumerate(x_names): + f_id = dataset_ctx.get(f"XAXIS{'' if i==0 else i+1}_FIELD_ID") + if f_id: + deep_update_field_names(view_info, f_id, x_name) + + for i, y_name in enumerate(y_names): + f_id = dataset_ctx.get(f"YAXIS{'' if i==0 else i+1}_FIELD_ID") + if f_id: + deep_update_field_names(view_info, f_id, y_name) + + # 最终保底:如果还有残留的“访问次数”或“访问平台”,强行全局替换 + def brute_force_replace(obj, search_list, replace_list): + if isinstance(obj, dict): + for k, v in obj.items(): + if isinstance(v, str): + for s, r in zip(search_list, replace_list): + if s in v: + obj[k] = v.replace(s, r) + else: + brute_force_replace(v, search_list, replace_list) + elif isinstance(obj, list): + for item in obj: + brute_force_replace(item, search_list, replace_list) + + # Use primary X/Y names for brute force fallback + if x_names and y_names: + brute_force_replace(view_info, ["访问平台", "访问次数", "浏览量"], [x_names[0], y_names[0], y_names[0]]) + + # If layout is provided, override it in componentData + if "componentData" in payload: + components = json.loads(payload["componentData"]) + if components: + target = components[0] + # Update component name to title + target["name"] = title + target["label"] = title + + # 单图表模式:充满整个画布 + if layout is None and len(components) == 1: + # DataEase 单图表网格 72x36,让图表充满整个画布 + target["x"] = 1 + target["y"] = 1 + target["sizeX"] = 72 + target["sizeY"] = 36 + if "style" not in target: + target["style"] = {} + target["style"]["width"] = 1920 + target["style"]["height"] = 1080 + target["style"]["left"] = 0 + target["style"]["top"] = 0 + elif layout: + if "x" in layout: target["x"] = layout["x"] + if "y" in layout: target["y"] = layout["y"] + if "sizeX" in layout: target["sizeX"] = layout["sizeX"] + if "sizeY" in layout: target["sizeY"] = layout["sizeY"] + if "style" not in target: target["style"] = {} + if "width" in layout: target["style"]["width"] = layout["width"] + if "height" in layout: target["style"]["height"] = layout["height"] + if "left" in layout: target["style"]["left"] = layout["left"] + if "top" in layout: target["style"]["top"] = layout["top"] + payload["componentData"] = json.dumps(components, separators=(',', ':'), ensure_ascii=False) + + # Ensure canvasStyleData is also compact + if "canvasStyleData" in payload: + style = json.loads(payload["canvasStyleData"]) + payload["canvasStyleData"] = json.dumps(style, separators=(',', ':'), ensure_ascii=False) + + payload.update({ + "id": None, + "name": board_name, + "type": "dashboard", + "status": 0, + "dataState": "ready", + "selfWatermarkStatus": True, + "checkVersion": "2.10.20", + "pid": "0", + "mobileLayout": False + }) + + headers = self._get_headers() + print(f"Deploying chart '{title}' (Type: {chart_type})...") + + save_r = requests.post( + f"{self.base_url}{self.api_prefix}/dataVisualization/saveCanvas", + headers=headers, json=payload, verify=False, timeout=30 + ) + + if save_r.status_code != 200: + raise Exception(f"saveCanvas failed: {save_r.text}") + + dashboard_id = str(save_r.json()["data"]) + + # 6. Publish + requests.post( + f"{self.base_url}{self.api_prefix}/dataVisualization/updatePublishStatus", + headers=headers, verify=False, timeout=30, + json={ + "id": dashboard_id, + "name": board_name, + "activeViewIds": [view_id], + "status": 1, + "type": "dashboard", + "mobileLayout": False + } + ) + + # Generate preview URL + return dashboard_id, f"{self.base_url}/#/preview?dvId={dashboard_id}&dvType=dashboard&ignoreParams=true" + + @staticmethod + def rand_id(): + return str(int(time.time() * 1000) + random.randint(1000, 9999)) diff --git a/scripts/inspect_data.py b/scripts/inspect_data.py new file mode 100644 index 0000000..56b2bbe --- /dev/null +++ b/scripts/inspect_data.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python3 +""" +DataEase 数据集探索脚本 +支持 AK/SK 和密码登录两种认证方式 +""" +import os +import json +import sys +import argparse +import subprocess +import base64 +import hashlib +import hmac +import time +import uuid +import shutil +from pathlib import Path +from urllib.request import Request, urlopen +from urllib.error import HTTPError, URLError + +# Add scripts to path for engine import +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + + +def load_dotenv(): + """加载 .env 文件""" + env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env") + if os.path.exists(env_path): + with open(env_path, encoding="utf8") as f: + for line in f: + line = line.strip() + if line and not line.startswith('#') and '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip().strip('"').strip("'") + + +def base64url(raw): + """Base64 URL-safe 编码""" + return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii") + + +def sign_jwt(payload, secret_key): + """使用 HMAC-SHA256 签名 JWT""" + header = {"alg": "HS256", "typ": "JWT"} + header_part = base64url(json.dumps(header, separators=(",", ":")).encode("utf-8")) + payload_part = base64url(json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")) + signing_input = f"{header_part}.{payload_part}".encode("ascii") + signature = hmac.new(secret_key.encode("utf-8"), signing_input, hashlib.sha256).digest() + return f"{header_part}.{payload_part}.{base64url(signature)}" + + +def aes_cipher_name(secret_key): + """根据密钥长度返回 AES 加密算法名称""" + length = len(secret_key.encode("utf-8")) + if length == 16: + return "aes-128-cbc" + if length == 24: + return "aes-192-cbc" + if length == 32: + return "aes-256-cbc" + raise ValueError("Secret Key 长度必须是 16、24 或 32 字节") + + +def aes_encrypt(plain_text, secret_key, iv): + """AES 加密""" + if shutil.which("openssl") is None: + raise RuntimeError("当前环境缺少 openssl 命令,无法生成鉴权签名") + if len(iv.encode("utf-8")) != 16: + raise ValueError("Access Key 长度必须是 16 字节,才能作为 AES IV") + + cmd = [ + "openssl", "enc", f"-{aes_cipher_name(secret_key)}", + "-base64", "-A", "-nosalt", + "-K", secret_key.encode("utf-8").hex(), + "-iv", iv.encode("utf-8").hex(), + ] + proc = subprocess.run(cmd, input=plain_text.encode("utf-8"), capture_output=True, check=False) + if proc.returncode != 0: + stderr = proc.stderr.decode("utf-8", errors="replace").strip() + raise RuntimeError(stderr or "openssl 加密失败") + return proc.stdout.decode("utf-8").strip() + + +def build_ask_auth(access_key, secret_key): + """构建 ASK 认证信息""" + source = f"{access_key}|{uuid.uuid4()}|{int(time.time() * 1000)}" + signature = aes_encrypt(source, secret_key, access_key) + token = sign_jwt({"accessKey": access_key, "signature": signature}, secret_key) + return { + "access_key": access_key, + "signature": signature, + "x_de_ask_token": token, + } + + +def get_headers_ask(ask_auth): + """构建 ASK 认证请求头""" + return { + "Accept": "application/json;charset=UTF-8", + "Content-Type": "application/json", + "accessKey": ask_auth["access_key"], + "signature": ask_auth["signature"], + "X-DE-ASK-TOKEN": ask_auth["x_de_ask_token"], + } + + +def fetch_dekey(base_url, api_prefix): + """获取 dekey 用于密码登录""" + url = f"{base_url.rstrip('/')}{api_prefix}/dekey" + request = Request(url, headers={"Accept": "application/json;charset=UTF-8"}, method="GET") + try: + with urlopen(request, timeout=30) as response: + payload = json.loads(response.read().decode("utf-8")) + data = payload.get("data") + if not isinstance(data, str) or not data: + raise ValueError("dekey 接口未返回有效字符串") + return data + except HTTPError as err: + raise RuntimeError(f"获取 dekey 失败: {err.code}") + + +def split_dekey(dekey): + """拆分 dekey""" + separator = base64.urlsafe_b64encode(b"-pk_separator-").decode("ascii") + if separator and separator in dekey: + parts = dekey.split(separator, 1) + if len(parts) == 2 and parts[0] and parts[1]: + return parts[0], parts[1] + raise ValueError("dekey 格式不符合预期") + + +def format_public_key(public_key): + """格式化公钥""" + body = "\n".join(public_key[index:index + 64] for index in range(0, len(public_key), 64)) + return f"-----BEGIN PUBLIC KEY-----\n{body}\n-----END PUBLIC KEY-----\n" + +# fixed by xuhuanqing +''' +def rsa_encrypt(plain_text, public_key): + """RSA 加密""" + import tempfile + if shutil.which("openssl") is None: + raise RuntimeError("当前环境缺少 openssl 命令") + + with tempfile.TemporaryDirectory(prefix="dataease-pubkey-") as tmpdir: + key_path = Path(tmpdir) / "public.pem" + key_path.write_text(format_public_key(public_key), encoding="utf-8") + proc = subprocess.run( + ["openssl", "pkeyutl", "-encrypt", "-pubin", "-inkey", str(key_path)], + input=plain_text.encode("utf-8"), + capture_output=True, + check=False, + ) + if proc.returncode != 0: + raise RuntimeError(proc.stderr.decode("utf-8", errors="replace").strip() or "RSA 加密失败") + return base64.b64encode(proc.stdout).decode("ascii") +''' + +def rsa_encrypt(plain_text, public_key): + """RSA加密,兼容OpenSSL pkeyutl参数规范,修复no private key报错""" + import tempfile + import subprocess + import base64 + from shutil import which + from pathlib import Path + + if which("openssl") is None: + raise RuntimeError("当前环境缺少 openssl 命令") + + # 格式化标准PEM公钥 + def format_public_key(pub_raw): + body = "\n".join(pub_raw[index:index + 64] for index in range(0, len(pub_raw), 64)) + return f"-----BEGIN PUBLIC KEY-----\n{body}\n-----END PUBLIC KEY-----\n" + + pub_pem = format_public_key(public_key) + # 创建临时公钥文件 + with tempfile.NamedTemporaryFile(mode="w", suffix=".pem", delete=False, encoding="utf-8") as tmp_key: + tmp_key.write(pub_pem) + tmp_key_path = tmp_key.name + try: + # 标准规范参数:-inkey 指定公钥文件、-pubin 标识文件类型为公钥 + proc = subprocess.run( + [ + "openssl", "pkeyutl", + "-encrypt", + "-pubin", + "-inkey", tmp_key_path + ], + input=plain_text.encode("utf-8"), + capture_output=True, + check=False + ) + if proc.returncode != 0: + err_msg = proc.stderr.decode("utf-8", errors="replace").strip() + raise RuntimeError(f"RSA加密失败: {err_msg}") + return base64.b64encode(proc.stdout).decode("ascii") + finally: + # 执行完毕强制删除临时密钥文件 + Path(tmp_key_path).unlink(missing_ok=True) + + +def login_with_password(base_url, api_prefix, username, password): + """使用密码登录获取 x-de-token""" + dekey = fetch_dekey(base_url, api_prefix) + encrypted_pk, aes_key_str = split_dekey(dekey) + + # 解密获取公钥 + cmd = [ + "openssl", "enc", f"-{aes_cipher_name(aes_key_str)}", "-d", + "-base64", "-A", "-nosalt", + "-K", aes_key_str.encode("utf-8").hex(), + "-iv", b"0000000000000000".hex(), + ] + proc = subprocess.run(cmd, input=encrypted_pk.encode("utf-8"), capture_output=True, check=False) + if proc.returncode != 0: + raise RuntimeError("解密 dekey 失败") + public_key = proc.stdout.decode("utf-8").strip() + + # RSA 加密用户名和密码 + # fixed by xuhuanqing + # encrypted_name = rsa_encrypt(username, public_key) + # encrypted_pwd = rsa_encrypt(password, public_key) + encrypted_name = rsa_encrypt(username, public_key) + encrypted_pwd = rsa_encrypt(password, public_key) + + login_origin = int(os.environ.get("DATAEASE_LOGIN_ORIGIN", "0")) + + payload = { + "name": encrypted_name, + "pwd": encrypted_pwd, + "origin": login_origin, + } + + url = f"{base_url.rstrip('/')}{api_prefix}/login/localLogin" + request = Request( + url, + data=json.dumps(payload, ensure_ascii=False).encode("utf-8"), + headers={"Content-Type": "application/json"}, + method="POST" + ) + + try: + with urlopen(request, timeout=30) as response: + result = json.loads(response.read().decode("utf-8")) + if result.get("code") not in (None, 0): + raise RuntimeError(f"登录失败: {result.get('msg', '未知错误')}") + # fixed by xuhuanqing + # return result.get("data") + return result.get("data")["token"] + except HTTPError as err: + body = err.read().decode("utf-8", errors="replace") + raise RuntimeError(f"登录请求失败: {err.code} - {body}") + + +def get_headers_token(x_de_token): + """构建 Token 认证请求头""" + return { + "Accept": "application/json;charset=UTF-8", + "Content-Type": "application/json", + "X-DE-TOKEN": x_de_token, + } + + +def make_request(url, headers, payload=None, method="POST"): + """发送 HTTP 请求""" + data = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8") + request = Request(url, data=data, headers=headers, method=method) + try: + with urlopen(request, timeout=30) as response: + return json.loads(response.read().decode("utf-8")) + except HTTPError as err: + body = err.read().decode("utf-8", errors="replace") + raise RuntimeError(f"请求失败: {err.code} - {body}") + + +def list_datasets(base_url, api_prefix, headers): + """列出所有数据集""" + url = f"{base_url.rstrip('/')}{api_prefix}/datasetTree/tree" + payload = {"busiFlag": "dataset"} + result = make_request(url, headers, payload) + + nodes = result.get('data', []) + datasets = [] + + def collect_leaf(items): + for item in items: + if item.get('leaf'): + datasets.append({"name": item.get('name'), "id": item.get('id')}) + children = item.get('children', []) + if children: + collect_leaf(children) + + collect_leaf(nodes) + return datasets + + +def get_dataset_fields(base_url, api_prefix, headers, dataset_id): + """获取数据集字段""" + # 先尝试详情接口 + url = f"{base_url.rstrip('/')}{api_prefix}/datasetTree/details/{dataset_id}" + request = Request(url, headers=headers, method="GET") + try: + with urlopen(request, timeout=30) as response: + result = json.loads(response.read().decode("utf-8")) + fields = result.get('data', {}).get('allFields', []) + if fields: + return fields + except Exception: + pass + + # 回退到字段列表接口 + url = f"{base_url.rstrip('/')}{api_prefix}/datasetField/listByDatasetGroup/{dataset_id}" + result = make_request(url, headers, method="POST") + return result.get('data', []) + + +def resolve_dataset_id(base_url, api_prefix, headers, name_or_id): + """解析数据集名称为 ID""" + if str(name_or_id).isdigit() and len(str(name_or_id)) > 10: + return name_or_id + + datasets = list_datasets(base_url, api_prefix, headers) + for ds in datasets: + if ds['name'] == name_or_id: + return ds['id'] + + raise ValueError(f"未找到数据集: {name_or_id}") + + +def main(): + load_dotenv() + + base_url = os.environ.get("DATAEASE_BASE_URL", "") + api_prefix = os.environ.get("DATAEASE_API_PREFIX", "/de2api") + access_key = os.environ.get("DATAEASE_ACCESS_KEY", "") + secret_key = os.environ.get("DATAEASE_SECRET_KEY", "") + username = os.environ.get("DATAEASE_USERNAME", "") + password = os.environ.get("DATAEASE_PASSWORD", "") + + if not base_url: + print("Error: 请设置 DATAEASE_BASE_URL") + sys.exit(1) + + parser = argparse.ArgumentParser(description="探索 DataEase 数据集和字段") + parser.add_argument("--list-datasets", action="store_true", help="列出所有数据集") + parser.add_argument("--dataset", type=str, help="查看指定数据集的字段") + args = parser.parse_args() + + # 认证:优先 AK/SK,否则用密码登录 + try: + if access_key and secret_key: + ask_auth = build_ask_auth(access_key, secret_key) + headers = get_headers_ask(ask_auth) + auth_mode = "AK/SK" + elif username and password: + x_de_token = login_with_password(base_url, api_prefix, username, password) + headers = get_headers_token(x_de_token) + auth_mode = "密码登录" + else: + print("Error: 请配置 AK/SK (DATAEASE_ACCESS_KEY + DATAEASE_SECRET_KEY) 或用户名密码 (DATAEASE_USERNAME + DATAEASE_PASSWORD)") + sys.exit(1) + except Exception as e: + print(f"认证失败: {e}") + sys.exit(1) + + if args.list_datasets: + try: + datasets = list_datasets(base_url, api_prefix, headers) + print(json.dumps(datasets, ensure_ascii=False, indent=2)) + except Exception as e: + print(f"获取数据集列表失败: {e}") + sys.exit(1) + + elif args.dataset: + try: + dataset_id = resolve_dataset_id(base_url, api_prefix, headers, args.dataset) + fields = get_dataset_fields(base_url, api_prefix, headers, dataset_id) + result = [ + {"name": f['name'], "id": f['id'], "type": f.get('deType'), "dataeaseName": f.get('dataeaseName')} + for f in fields + ] + print(json.dumps(result, ensure_ascii=False, indent=2)) + except Exception as e: + print(f"获取数据集字段失败: {e}") + sys.exit(1) + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/scripts/multi_deploy.py b/scripts/multi_deploy.py new file mode 100644 index 0000000..1c5a62e --- /dev/null +++ b/scripts/multi_deploy.py @@ -0,0 +1,148 @@ +import sys +import os +import json +import subprocess + +# Add local path for engine import +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from multi_engine import MultiDataEaseChartEngine + +# --- Configuration from Environment --- +def load_dotenv(): + env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env") + if os.path.exists(env_path): + with open(env_path) as f: + for line in f: + line = line.strip() + if line and not line.startswith('#') and '=' in line: + key, value = line.split('=', 1) + os.environ[key.strip()] = value.strip().strip('"').strip("'") + +load_dotenv() + +ACCESS_KEY = os.environ.get("DATAEASE_ACCESS_KEY") +SECRET_KEY = os.environ.get("DATAEASE_SECRET_KEY") +BASE_URL = os.environ.get("DATAEASE_BASE_URL") + +# Screenshot configuration - auto-detect OpenClaw workspace for MEDIA: display +def _default_output_dir(): + """优先使用 OpenClaw workspace 目录,否则用 skill 本地 output 目录""" + env_dir = os.environ.get("DATAEASE_OUTPUT_DIR") + if env_dir: + return env_dir + home = os.path.expanduser("~") + workspace_dir = os.path.join(home, ".openclaw", "workspace", "dataease-output") + if os.path.isdir(os.path.join(home, ".openclaw", "workspace")): + os.makedirs(workspace_dir, exist_ok=True) + return workspace_dir + return os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "output") + +OUTPUT_DIR = _default_output_dir() +DEFAULT_PIXEL = "2560*1440" # 更高分辨率,图片更清晰 + +def capture_dashboard(dashboard_id, output_format="jpeg"): + """调用 capture_dashboard.py 进行截图""" + scripts_dir = os.path.dirname(os.path.abspath(__file__)) + capture_script = os.path.join(scripts_dir, "capture_dashboard.py") + + if not os.path.exists(capture_script): + return None, "Screenshot script not found" + + # 确保输出目录存在 + os.makedirs(OUTPUT_DIR, exist_ok=True) + + # capture_dashboard.py 使用相同的 BASE_URL(不含 /de2api 后缀) + capture_base_url = BASE_URL.rstrip("/") + + # 调用截图脚本 + result_format = "0" if output_format == "jpeg" else "1" # 0=jpeg, 1=pdf + cmd = [ + "python3", capture_script, + "capture", + "--resource-id", str(dashboard_id), + "--busi-type", "dashboard", + "--output-dir", OUTPUT_DIR, + "--result-format", result_format, + "--base-url", capture_base_url, + "--pixel", DEFAULT_PIXEL + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) + if result.returncode == 0: + output = json.loads(result.stdout) + if output.get("ok"): + return output.get("saved_file"), None + else: + return None, output.get("error", "Unknown capture error") + else: + return None, result.stderr or "Capture failed" + except subprocess.TimeoutExpired: + return None, "Capture timeout" + except Exception as e: + return None, str(e) + +def main(): + if not all([ACCESS_KEY, SECRET_KEY, BASE_URL]): + print("Error: Missing required environment variables.") + sys.exit(1) + + if len(sys.argv) < 3: + print("Usage: python3 multi_deploy.py <dashboard_title> <charts_json_config> [--no-screenshot]") + print("Example: python3 multi_deploy.py '综合分析' '[{\"type\":\"bar\",\"dataset_name\":\"ds1\",\"x_axis\":[\"f1\"],\"y_axis\":[\"f2\"]}]'") + sys.exit(1) + + title = sys.argv[1] + try: + charts_config = json.loads(sys.argv[2]) + except Exception as e: + print(f"Error parsing JSON config: {e}") + sys.exit(1) + + # 检查是否禁用截图 + no_screenshot = "--no-screenshot" in sys.argv + + engine = MultiDataEaseChartEngine(BASE_URL, ACCESS_KEY, SECRET_KEY) + + try: + did, url = engine.deploy_multi(title, charts_config) + print(f"\n✅ Successfully deployed multi-chart dashboard!") + print(f"Dashboard ID: {did}") + print(f"URL: {url}") + + # 自动截图 + if not no_screenshot: + print("\n📸 Capturing screenshot...") + screenshot_path, error = capture_dashboard(did) + if screenshot_path: + print(f"Screenshot saved: {screenshot_path}") + # 输出 JSON 结果供 Agent 解析 + result = { + "ok": True, + "dashboard_id": str(did), + "url": url, + "screenshot": screenshot_path, + "title": title, + "charts_count": len(charts_config) + } + print(f"\n__RESULT_JSON__\n{json.dumps(result, ensure_ascii=False)}\n__END_JSON__") + else: + print(f"⚠️ Screenshot failed: {error}") + result = { + "ok": True, + "dashboard_id": str(did), + "url": url, + "screenshot": None, + "screenshot_error": error, + "title": title, + "charts_count": len(charts_config) + } + print(f"\n__RESULT_JSON__\n{json.dumps(result, ensure_ascii=False)}\n__END_JSON__") + + except Exception as e: + print(f"\n❌ Deployment failed: {str(e)}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/multi_engine.py b/scripts/multi_engine.py new file mode 100644 index 0000000..1112784 --- /dev/null +++ b/scripts/multi_engine.py @@ -0,0 +1,247 @@ +import json, re, time, random, os, sys, requests +from typing import List, Dict, Any + +# Local SDK client +from client import DataEaseClient +from engine import DataEaseChartEngine + +class MultiDataEaseChartEngine(DataEaseChartEngine): + def __init__(self, base_url, ak, sk): + super().__init__(base_url, ak, sk) + + def extract_chart_payload(self, chart_type: str, dataset_id: str, x_names: list, y_names: list, view_id: str, layout: dict, title: str = None): + """ + Reads the single chart template, applies parameters, and extracts the componentData & canvasViewInfo. + """ + base_dir = os.path.dirname(os.path.abspath(__file__)) + tpl_dir = os.path.join(base_dir, "..", "templates", f"chart_{chart_type}") + + if not os.path.exists(tpl_dir): + raise FileNotFoundError(f"Template directory {tpl_dir} not found") + + with open(os.path.join(tpl_dir, "template.j2")) as f: + template_str = f.read() + + with open(os.path.join(tpl_dir, "params.json")) as f: + raw_params = json.load(f) + + # Build Context (Flatten params + dynamic IDs) + ctx = {} + def flatten_dict(d): + items = {} + for k, v in d.items(): + if isinstance(v, dict): + items.update(flatten_dict(v)) + elif not k.startswith("_"): + items[k] = v + return items + + ctx.update(flatten_dict(raw_params)) + ctx.update(self.get_dataset_ctx(dataset_id, x_names, y_names)) + + # Runtime Randomization + # SCENE_ID is usually "0" for base + ctx.update({"VIEW_ID": view_id, "SCENE_ID": "0"}) + + # Sub placeholder + def rep(m): + k = m.group(1).strip() + if k in ctx: + return str(ctx[k]) + return m.group(0) + + rendered = re.sub(r"\{\{\s*(\w+)\s*\}\}", rep, template_str) + payload = json.loads(rendered) + + # Extract canvasViewInfo + canvas_view_info = payload.get("canvasViewInfo", {}) + target_view_info = canvas_view_info.get(view_id) + if not target_view_info: + raise ValueError(f"View ID {view_id} not found in the rendered template!") + + # Override Chart Title in view_info if provided + if title: + target_view_info["title"] = title + + # --- Fix: Robust field name replacement --- + def deep_update_field_names(obj, target_id, new_name): + if isinstance(obj, dict): + if str(obj.get("id")) == str(target_id): + obj["name"] = new_name + if "description" in obj: obj["description"] = new_name + if "originName" in obj: obj["originName"] = new_name + if "dbFieldName" in obj: obj["dbFieldName"] = None + if "optionLabel" in obj: + obj["optionLabel"] = new_name + (obj["optionLabel"].partition("(")[1] + obj["optionLabel"].partition("(")[2] if "(" in obj["optionLabel"] else "") + if "optionShowName" in obj: + obj["optionShowName"] = new_name + (obj["optionShowName"].partition("(")[1] + obj["optionShowName"].partition("(")[2] if "(" in obj["optionShowName"] else "") + for v in obj.values(): + deep_update_field_names(v, target_id, new_name) + elif isinstance(obj, list): + for item in obj: + deep_update_field_names(item, target_id, new_name) + + dataset_ctx = self.get_dataset_ctx(dataset_id, x_names, y_names) + for i, x_name in enumerate(x_names): + f_id = dataset_ctx.get(f"XAXIS{'' if i==0 else i+1}_FIELD_ID") + if f_id: deep_update_field_names(target_view_info, f_id, x_name) + + for i, y_name in enumerate(y_names): + f_id = dataset_ctx.get(f"YAXIS{'' if i==0 else i+1}_FIELD_ID") + if f_id: deep_update_field_names(target_view_info, f_id, y_name) + + def brute_force_replace(obj, search_list, replace_list): + if isinstance(obj, dict): + for k, v in obj.items(): + if isinstance(v, str): + for s, r in zip(search_list, replace_list): + if s in v: obj[k] = v.replace(s, r) + else: brute_force_replace(v, search_list, replace_list) + elif isinstance(obj, list): + for item in obj: brute_force_replace(item, search_list, replace_list) + + if x_names and y_names: + brute_force_replace(target_view_info, ["访问平台", "访问次数", "浏览量"], [x_names[0], y_names[0], y_names[0]]) + # --- End Fix --- + + # Extract componentData + component_data_str = payload.get("componentData", "[]") + components = json.loads(component_data_str) + if not components: + raise ValueError(f"No component data found in the rendered template") + + # We only take the first component as it was a single chart template + target_component = components[0] + + # Override component name/label if title provided + if title: + target_component["name"] = title + target_component["label"] = title + + # Override layout + if "layout" in layout: + layout = layout["layout"] # in case it's nested + + if "style" not in target_component: + target_component["style"] = {} + + if "x" in layout: target_component["x"] = layout["x"] + if "y" in layout: target_component["y"] = layout["y"] + if "sizeX" in layout: target_component["sizeX"] = layout["sizeX"] + if "sizeY" in layout: target_component["sizeY"] = layout["sizeY"] + if "width" in layout: target_component["style"]["width"] = layout["width"] + if "height" in layout: target_component["style"]["height"] = layout["height"] + if "left" in layout: target_component["style"]["left"] = layout["left"] + if "top" in layout: target_component["style"]["top"] = layout["top"] + + return target_component, target_view_info + + def deploy_multi(self, title: str, charts_config: List[Dict[str, Any]]): + """ + Deploy multiple charts into a single dashboard. + charts_config supports optional 'layout'. If missing, it will auto-layout. + """ + content_id = self.rand_id() + board_name = f"{title}_{int(time.time())}" + + # Load base dashboard style from template + base_dir = os.path.dirname(os.path.abspath(__file__)) + dashboard_tpl_path = os.path.join(base_dir, "..", "templates", "dashboard", "base.json") + if os.path.exists(dashboard_tpl_path): + with open(dashboard_tpl_path) as f: + base_tpl = json.load(f) + canvas_style_data = json.loads(base_tpl.get("canvasStyleData", "{}")) + else: + canvas_style_data = { + "width": 1920, "height": 1080, "screenAdaptor": "widthFirst", + "dashboard": {"gap": "yes", "gapSize": 5, "matrixBase": 4} + } + + final_component_data = [] + final_canvas_view_info = {} + active_view_ids = [] + + # Auto-layout calculation constants + GRID_COLS = 72 + CHART_WIDTH = 36 + CHART_HEIGHT = 14 + + # Process each chart sequentially + for idx, c_conf in enumerate(charts_config): + view_id = self.rand_id() + active_view_ids.append(view_id) + + # Calculate auto-layout if not provided + layout = c_conf.get("layout", {}) + if not layout: + row = idx // 2 + col = idx % 2 + layout = { + "x": col * CHART_WIDTH + 1, + "y": row * CHART_HEIGHT + 1, + "sizeX": CHART_WIDTH, + "sizeY": CHART_HEIGHT, + "width": 519, "height": 65, # Standard relative sizes + "left": col * 519, "top": row * 65 + } + + comp_data, view_info = self.extract_chart_payload( + chart_type=c_conf["type"], + dataset_id=c_conf["dataset_name"], + x_names=c_conf.get("x_axis", []), + y_names=c_conf.get("y_axis", []), + view_id=view_id, + layout=layout, + title=c_conf.get("title") + ) + + comp_data["_dragId"] = idx + final_component_data.append(comp_data) + final_canvas_view_info[view_id] = view_info + + dashboard_payload = { + "id": None, + "name": board_name, + "type": "dashboard", + "status": 0, + "dataState": "ready", + "selfWatermarkStatus": True, + "checkVersion": "2.10.20", + "pid": "0", + "mobileLayout": False, + "canvasStyleData": json.dumps(canvas_style_data, separators=(',', ':'), ensure_ascii=False), + "componentData": json.dumps(final_component_data, separators=(',', ':'), ensure_ascii=False), + "canvasViewInfo": final_canvas_view_info, + "contentId": content_id + } + + headers = self.client._get_headers() + print(f"Deploying multi-chart dashboard '{title}' with {len(charts_config)} charts...") + + save_r = requests.post( + f"{self.base_url}{self.api_prefix}/dataVisualization/saveCanvas", + headers=headers, json=dashboard_payload, verify=False, timeout=30 + ) + + if save_r.status_code != 200: + raise Exception(f"saveCanvas failed: {save_r.text}") + + dashboard_id = str(save_r.json()["data"]) + + # Publish + requests.post( + f"{self.base_url}{self.api_prefix}/dataVisualization/updatePublishStatus", + headers=headers, verify=False, timeout=30, + json={ + "id": dashboard_id, + "name": board_name, + "activeViewIds": active_view_ids, + "status": 1, + "type": "dashboard", + "mobileLayout": False + } + ) + + return dashboard_id, f"{self.base_url}/#/preview?dvId={dashboard_id}&dvType=dashboard&ignoreParams=true" + + return dashboard_id, f"{self.base_url}/#/preview?dvId={dashboard_id}&dvType=dashboard&ignoreParams=true" diff --git a/templates/chart_bar/params.json b/templates/chart_bar/params.json new file mode 100644 index 0000000..35eaf81 --- /dev/null +++ b/templates/chart_bar/params.json @@ -0,0 +1,28 @@ +{ + "_comment": "chart_bar 模板参数(来源:DataEase 抓包 + 参数化)", + "view": { + "VIEW_ID": "7447105738385657856", + "CONTENT_ID": "7447106138476122112", + "SCENE_ID": "0" + }, + "dataset": { + "DATASOURCE_ID": "1166370337189924864", + "DATASET_TABLE_ID": "7387069976563159040", + "DATASET_GROUP_ID": "1178801986976485376" + }, + "fields": { + "xAxis": { + "XAXIS_FIELD_ID": "1761214724097", + "XAXIS_DE_NAME": "f_3319f6ba3e484e93" + }, + "yAxis": { + "YAXIS_FIELD_ID": "1761214724096", + "YAXIS_DE_NAME": "f_97666027e11d7dc5", + "YAXIS_SERIES_ID": "1761214724096-yAxis" + }, + "yAxis2": { + "YAXIS2_FIELD_ID": "1761214724100", + "YAXIS2_DE_NAME": "f_c4c3c6a1a3d6688b" + } + } +} \ No newline at end of file diff --git a/templates/chart_bar/template.j2 b/templates/chart_bar/template.j2 new file mode 100644 index 0000000..0189245 --- /dev/null +++ b/templates/chart_bar/template.j2 @@ -0,0 +1,1156 @@ +{ + "canvasStyleData": "{\"width\":1920,\"height\":1080,\"refreshBrowserEnable\":false,\"refreshBrowserUnit\":\"minute\",\"refreshBrowserTime\":5,\"refreshViewEnable\":false,\"refreshViewLoading\":true,\"refreshUnit\":\"minute\",\"refreshTime\":5,\"popupAvailable\":true,\"popupButtonAvailable\":true,\"suspensionButtonAvailable\":false,\"screenAdaptor\":\"widthFirst\",\"dashboardAdaptor\":\"keepHeightAndWidth\",\"scale\":15,\"scaleWidth\":23,\"scaleHeight\":23,\"backgroundColorSelect\":true,\"backgroundImageEnable\":false,\"backgroundType\":\"backgroundColor\",\"background\":\"\",\"openCommonStyle\":true,\"opacity\":1,\"fontSize\":14,\"fontFamily\":\"PingFang\",\"themeId\":\"10001\",\"color\":\"#000000\",\"backgroundColor\":\"#f5f6f7\",\"dashboard\":{\"gap\":\"yes\",\"gapSize\":5,\"gapMode\":\"middle\",\"showGrid\":false,\"matrixBase\":4,\"resultMode\":\"all\",\"resultCount\":1000,\"themeColor\":\"light\",\"mobileSetting\":{\"customSetting\":false,\"imageUrl\":null,\"backgroundType\":\"image\",\"color\":\"#000\"}},\"component\":{\"chartTitle\":{\"show\":true,\"fontSize\":16,\"hPosition\":\"left\",\"vPosition\":\"top\",\"isItalic\":false,\"isBolder\":true,\"remarkShow\":false,\"remark\":\"\",\"fontFamily\":\"\",\"letterSpace\":\"0\",\"fontShadow\":false,\"color\":\"#000000\",\"remarkBackgroundColor\":\"#ffffff\"},\"chartColor\":{\"basicStyle\":{\"colorScheme\":\"default\",\"colors\":[\"#1E90FF\",\"#90EE90\",\"#00CED1\",\"#E2BD84\",\"#7A90E0\",\"#3BA272\",\"#2BE7FF\",\"#0A8ADA\",\"#FFD700\"],\"alpha\":100,\"gradient\":false,\"mapStyle\":\"normal\",\"areaBaseColor\":\"#FFFFFF\",\"areaBorderColor\":\"#303133\",\"gaugeStyle\":\"default\",\"tableBorderColor\":\"#E6E7E4\",\"tableScrollBarColor\":\"rgba(0, 0, 0, 0.15)\",\"zoomButtonColor\":\"#aaa\",\"zoomBackground\":\"#fff\"},\"misc\":{\"flowMapConfig\":{\"lineConfig\":{\"mapLineAnimate\":true,\"mapLineGradient\":false,\"mapLineSourceColor\":\"#146C94\",\"mapLineTargetColor\":\"#576CBC\"}},\"nameFontColor\":\"#000000\",\"valueFontColor\":\"#5470c6\"},\"tableHeader\":{\"tableHeaderBgColor\":\"#1E90FF\",\"tableHeaderCornerBgColor\":\"#1E90FF\",\"tableHeaderColBgColor\":\"#1E90FF\",\"tableHeaderFontColor\":\"#000000\",\"tableHeaderCornerFontColor\":\"#000000\",\"tableHeaderColFontColor\":\"#000000\"},\"tableCell\":{\"tableItemBgColor\":\"#FFFFFF\",\"tableFontColor\":\"#000000\",\"tableItemSubBgColor\":\"#1E90FF\"}},\"chartCommonStyle\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"filterStyle\":{\"layout\":\"horizontal\",\"titleLayout\":\"left\",\"labelColor\":\"#1f2329\",\"titleColor\":\"#1f2329\",\"color\":\"#1f2329\",\"borderColor\":\"#bbbfc4\",\"text\":\"#1f2329\",\"bgColor\":\"#FFFFFF\"},\"tabStyle\":{\"headPosition\":\"left\",\"headFontColor\":\"#000000\",\"headFontActiveColor\":\"#000000\",\"headBorderColor\":\"#ffffff\",\"headBorderActiveColor\":\"#ffffff\"},\"seniorStyleSetting\":{\"linkageIconColor\":\"#a6a6a6\",\"drillLayerColor\":\"#a6a6a6\",\"pagerColor\":\"#a6a6a6\"},\"formatterItem\":{\"type\":\"auto\",\"unitLanguage\":\"ch\",\"unit\":1,\"suffix\":\"\",\"decimalCount\":2,\"thousandSeparator\":true}},\"dialogBackgroundColor\":\"rgba(255, 255, 255, 1)\",\"dialogButton\":\"#020408\"}", + "componentData": "[{\"animations\":[],\"canvasId\":\"canvas-main\",\"events\":{\"checked\":false,\"showTips\":false,\"type\":\"jump\",\"typeList\":[{\"key\":\"jump\",\"label\":\"jump\"},{\"key\":\"download\",\"label\":\"download\"},{\"key\":\"share\",\"label\":\"share\"},{\"key\":\"fullScreen\",\"label\":\"fullScreen\"},{\"key\":\"showHidden\",\"label\":\"showHidden\"},{\"key\":\"refreshDataV\",\"label\":\"refreshDataV\"},{\"key\":\"refreshView\",\"label\":\"refreshView\"}],\"jump\":{\"value\":\"https://\",\"type\":\"_blank\"},\"download\":{\"value\":true},\"share\":{\"value\":true},\"showHidden\":{\"value\":true},\"refreshDataV\":{\"value\":true},\"refreshView\":{\"value\":true,\"target\":\"all\"}},\"carousel\":{\"enable\":false,\"time\":10},\"multiDimensional\":{\"enable\":false,\"x\":0,\"y\":0,\"z\":0},\"groupStyle\":{},\"isLock\":false,\"maintainRadio\":false,\"aspectRatio\":1,\"isShow\":true,\"dashboardHidden\":false,\"category\":\"base\",\"dragging\":false,\"resizing\":false,\"collapseName\":[\"position\",\"background\",\"style\",\"picture\",\"frameLinks\",\"videoLinks\",\"streamLinks\",\"carouselInfo\",\"events\",\"decoration_style\"],\"linkage\":{\"duration\":0,\"data\":[{\"id\":\"\",\"label\":\"\",\"event\":\"\",\"style\":[{\"key\":\"\",\"value\":\"\"}]}]},\"component\":\"UserView\",\"name\":\"基础柱状图\",\"label\":\"基础柱状图\",\"propValue\":{\"textValue\":\"\",\"urlList\":[]},\"icon\":\"bar\",\"innerType\":\"bar\",\"editing\":false,\"canvasActive\":false,\"actionSelection\":{\"linkageActive\":\"custom\"},\"x\":1,\"y\":1,\"sizeX\":36,\"sizeY\":14,\"style\":{\"rotate\":0,\"opacity\":1,\"borderActive\":false,\"borderWidth\":1,\"borderRadius\":5,\"borderStyle\":\"solid\",\"borderColor\":\"rgba(204, 204, 204, 1)\",\"adaptation\":\"adaptation\",\"width\":519,\"height\":65.33333333333334,\"left\":0,\"top\":0},\"matrixStyle\":{},\"commonBackground\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"state\":\"ready\",\"render\":\"antv\",\"isPlugin\":false,\"id\":\"{{ VIEW_ID }}\",\"_dragId\":0,\"linkageFilters\":[],\"expand\":false,\"resizeInnerKeep\":false}]", + "canvasViewInfo": { + "{{ VIEW_ID }}": { + "id": "{{ VIEW_ID }}", + "title": "基础柱状图", + "sceneId": "{{ SCENE_ID }}", + "tableId": "{{ DATASET_GROUP_ID }}", + "type": "bar", + "render": "antv", + "resultCount": 1000, + "resultMode": "custom", + "extStack": [], + "extBubble": [], + "extLabel": [], + "extTooltip": [], + "customAttr": { + "basicStyle": { + "alpha": 100, + "tableBorderColor": "#E6E7E4", + "tableScrollBarColor": "rgba(0, 0, 0, 0.15)", + "tableColumnMode": "adapt", + "tableColumnWidth": 100, + "tableFieldWidth": [], + "tablePageMode": "page", + "tablePageStyle": "simple", + "tablePageSize": 20, + "gaugeStyle": "default", + "colorScheme": "default", + "colors": [ + "#1E90FF", + "#90EE90", + "#00CED1", + "#E2BD84", + "#7A90E0", + "#3BA272", + "#2BE7FF", + "#0A8ADA", + "#FFD700" + ], + "mapVendor": "amap", + "gradient": false, + "lineWidth": 2, + "lineSymbol": "circle", + "lineSymbolSize": 4, + "lineSmooth": true, + "barDefault": true, + "radiusColumnBar": "rightAngle", + "columnBarRightAngleRadius": 20, + "columnWidthRatio": 60, + "barWidth": 40, + "barGap": 0.4, + "lineType": "solid", + "scatterSymbol": "circle", + "scatterSymbolSize": 8, + "radarShape": "polygon", + "mapStyle": "normal", + "heatMapType": "heatmap", + "heatMapIntensity": 2, + "heatMapRadius": 20, + "areaBorderColor": "#303133", + "areaBaseColor": "#FFFFFF", + "mapSymbolOpacity": 0.7, + "mapSymbolStrokeWidth": 2, + "mapSymbol": "circle", + "mapSymbolSize": 6, + "radius": 80, + "innerRadius": 60, + "showZoom": true, + "zoomButtonColor": "#aaa", + "zoomBackground": "#fff", + "tableLayoutMode": "grid", + "defaultExpandLevel": 1, + "calcTopN": false, + "topN": 5, + "topNLabel": "其他", + "gaugeAxisLine": true, + "gaugePercentLabel": true, + "showSummary": false, + "summaryLabel": "总计", + "seriesColor": [], + "layout": "horizontal", + "mapSymbolSizeMin": 4, + "mapSymbolSizeMax": 30, + "showLabel": true, + "mapStyleUrl": "", + "autoFit": true, + "mapCenter": { + "longitude": 117.232, + "latitude": 39.354 + }, + "zoomLevel": 7, + "customIcon": "", + "showHoverStyle": true, + "autoWrap": false, + "maxLines": 3, + "radarShowPoint": true, + "radarPointSize": 4, + "radarAreaColor": true, + "circleBorderColor": "#fff", + "circleBorderWidth": 0, + "circlePadding": 0, + "quotaPosition": "col", + "quotaColLabel": "数值", + "tableRowHeaderMode": "adapt", + "tableRowHeaderWidth": 120, + "tableRowHeaderWidthPercent": 20 + }, + "misc": { + "pieInnerRadius": 0, + "pieOuterRadius": 80, + "radarShape": "polygon", + "radarSize": 80, + "gaugeMinType": "fix", + "gaugeMinField": { + "id": "", + "summary": "" + }, + "gaugeMin": 0, + "gaugeMaxType": "dynamic", + "gaugeMaxField": { + "id": "", + "summary": "" + }, + "gaugeStartAngle": 225, + "gaugeEndAngle": -45, + "nameFontSize": 18, + "valueFontSize": 18, + "nameValueSpace": 10, + "valueFontColor": "#5470c6", + "valueFontFamily": "Microsoft YaHei", + "valueFontIsBolder": false, + "valueFontIsItalic": false, + "valueLetterSpace": 0, + "valueFontShadow": false, + "showName": true, + "nameFontColor": "#000000", + "nameFontFamily": "Microsoft YaHei", + "nameFontIsBolder": false, + "nameFontIsItalic": false, + "nameLetterSpace": "0", + "nameFontShadow": false, + "treemapWidth": 80, + "treemapHeight": 80, + "liquidMaxType": "dynamic", + "liquidMaxField": { + "id": "", + "summary": "" + }, + "liquidSize": 80, + "liquidShape": "circle", + "hPosition": "center", + "vPosition": "center", + "mapPitch": 0, + "wordSizeRange": [ + 8, + 32 + ], + "wordSpacing": 6, + "mapAutoLegend": true, + "mapLegendMax": 0, + "mapLegendMin": 0, + "mapLegendNumber": 9, + "mapLegendRangeType": "quantize", + "mapLegendCustomRange": [], + "flowMapConfig": { + "lineConfig": { + "mapLineAnimate": true, + "mapLineType": "arc", + "mapLineWidth": 1, + "mapLineAnimateDuration": 3, + "mapLineGradient": false, + "mapLineSourceColor": "#146C94", + "mapLineTargetColor": "#576CBC", + "alpha": 100 + }, + "pointConfig": { + "text": { + "color": "#146C94", + "fontSize": 10 + }, + "point": { + "color": "#146C94", + "size": 4, + "animate": false, + "speed": 0.01 + } + } + }, + "wordCloudAxisValueRange": { + "auto": true, + "min": 0, + "max": 0 + }, + "bullet": { + "bar": { + "ranges": { + "fill": [ + "rgba(0,128,255,0.3)" + ], + "size": 20, + "showType": "dynamic", + "fixedRangeNumber": 3, + "symbol": "circle", + "symbolSize": 4 + }, + "measures": { + "fill": [ + "rgba(0,128,255,1)" + ], + "size": 15, + "symbol": "circle", + "symbolSize": 4 + }, + "target": { + "fill": "#000000", + "size": 20, + "showType": "dynamic", + "value": 0, + "symbol": "line", + "symbolSize": 4 + } + } + }, + "liquidShowBorder": false, + "liquidBorderWidth": 4, + "liquidBorderDistance": 8 + }, + "label": { + "show": false, + "childrenShow": true, + "position": "top", + "color": "#000000", + "fontSize": 12, + "formatter": "", + "labelLine": { + "show": true + }, + "labelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "reserveDecimalCount": 2, + "labelShadow": false, + "labelBgColor": "", + "labelShadowColor": "", + "quotaLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "showDimension": true, + "showQuota": false, + "showProportion": true, + "seriesLabelFormatter": [ + { + "id": "{{ YAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_count", + "name": "访问次数", + "dbFieldName": null, + "description": "访问次数", + "dataeaseName": "{{ YAXIS_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ YAXIS_DE_NAME }}", + "groupList": [], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "axisType": "yAxis", + "seriesId": "{{ YAXIS_FIELD_ID }}-yAxis", + "optionLabel": "访问次数(求和)", + "optionShowName": "访问次数(求和)", + "show": true, + "color": "#000000", + "fontSize": 12, + "showExtremum": false, + "position": "top" + } + ], + "conversionTag": { + "show": false, + "precision": 2, + "text": "转化率" + }, + "showTotal": false, + "totalFontSize": 12, + "totalColor": "#FFF", + "totalFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "showStackQuota": false, + "fullDisplay": false, + "proportionSeriesFormatter": { + "show": false, + "color": "#000000", + "fontSize": 12, + "formatterCfg": { + "decimalCount": 2 + } + } + }, + "tooltip": { + "show": true, + "trigger": "item", + "confine": true, + "fontSize": 12, + "color": "#000000", + "tooltipFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "backgroundColor": "#FFFFFF", + "seriesTooltipFormatter": [ + { + "id": "{{ YAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_count", + "name": "访问次数", + "dbFieldName": null, + "description": "访问次数", + "dataeaseName": "{{ YAXIS_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ YAXIS_DE_NAME }}", + "groupList": [], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "seriesId": "{{ YAXIS_FIELD_ID }}-yAxis", + "show": true, + "axisType": "yAxis" + }, + { + "id": "1761214724100", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "page_views", + "name": "浏览量", + "dbFieldName": null, + "description": "浏览量", + "dataeaseName": "f_c4c3c6a1a3d6688b", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "f_c4c3c6a1a3d6688b", + "groupList": [], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "seriesId": "1761214724100", + "show": false + }, + { + "id": "-1", + "datasourceId": null, + "datasetTableId": null, + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "*", + "name": "记录数*", + "dbFieldName": null, + "description": null, + "dataeaseName": "*", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": null, + "extField": 1, + "checked": true, + "columnIndex": 999, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": null, + "groupList": null, + "otherGroup": null, + "desensitized": null, + "orderChecked": null, + "params": null, + "summary": "count", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "seriesId": "-1", + "show": false + } + ], + "carousel": { + "enable": false, + "stayTime": 3, + "intervalTime": 1 + } + }, + "tableTotal": { + "row": { + "showGrandTotals": true, + "showSubTotals": true, + "reverseLayout": false, + "reverseSubLayout": false, + "label": "总计", + "subLabel": "小计", + "subTotalsDimensions": [], + "subTotalsDimensionsNew": true, + "calcTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "calcSubTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "totalSort": "none", + "totalSortField": "" + }, + "col": { + "showGrandTotals": true, + "showSubTotals": true, + "reverseLayout": false, + "reverseSubLayout": false, + "label": "总计", + "subLabel": "小计", + "subTotalsDimensions": [], + "calcTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "calcSubTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "totalSort": "none", + "totalSortField": "" + } + }, + "tableHeader": { + "indexLabel": "序号", + "showIndex": false, + "tableHeaderAlign": "left", + "tableHeaderCornerAlign": "left", + "tableHeaderColAlign": "left", + "tableHeaderBgColor": "#1E90FF", + "tableHeaderCornerBgColor": "#1E90FF", + "tableHeaderColBgColor": "#1E90FF", + "tableHeaderFontColor": "#000000", + "tableHeaderCornerFontColor": "#000000", + "tableHeaderColFontColor": "#000000", + "tableTitleFontSize": 12, + "tableTitleCornerFontSize": 12, + "tableTitleColFontSize": 12, + "tableTitleHeight": 36, + "tableHeaderSort": false, + "showColTooltip": false, + "showRowTooltip": false, + "showTableHeader": true, + "showHorizonBorder": true, + "showVerticalBorder": true, + "isItalic": false, + "isCornerItalic": false, + "isColItalic": false, + "isBolder": true, + "isCornerBolder": true, + "isColBolder": true, + "headerGroup": false, + "headerGroupConfig": { + "columns": [], + "meta": [] + }, + "rowHeaderFreeze": true, + "alignConfig": [] + }, + "tableCell": { + "tableFontColor": "#000000", + "tableItemAlign": "right", + "tableItemBgColor": "#FFFFFF", + "tableItemFontSize": 12, + "tableItemHeight": 36, + "enableTableCrossBG": false, + "tableItemSubBgColor": "#1E90FF", + "showTooltip": false, + "showHorizonBorder": true, + "showVerticalBorder": true, + "isItalic": false, + "isBolder": false, + "tableFreeze": false, + "tableColumnFreezeHead": 0, + "tableRowFreezeHead": 0, + "mergeCells": true, + "alignConfig": [] + }, + "indicator": { + "show": true, + "fontSize": 20, + "color": "#5470C6ff", + "hPosition": "center", + "vPosition": "center", + "isItalic": false, + "isBolder": true, + "fontFamily": "Microsoft YaHei", + "letterSpace": 0, + "fontShadow": false, + "backgroundColor": "", + "suffixEnable": true, + "suffix": "", + "suffixFontSize": 14, + "suffixColor": "#5470C6ff", + "suffixIsItalic": false, + "suffixIsBolder": true, + "suffixFontFamily": "Microsoft YaHei", + "suffixLetterSpace": 0, + "suffixFontShadow": false + }, + "indicatorName": { + "show": true, + "fontSize": 18, + "color": "#ffffffff", + "isItalic": false, + "isBolder": true, + "fontFamily": "Microsoft YaHei", + "letterSpace": 0, + "fontShadow": false, + "nameValueSpacing": 0, + "namePosition": "bottom" + }, + "map": { + "id": "", + "level": "world" + } + }, + "customAttrMobile": null, + "customStyle": { + "text": { + "show": true, + "fontSize": 16, + "hPosition": "left", + "vPosition": "top", + "isItalic": false, + "isBolder": true, + "remarkShow": false, + "remark": "", + "fontFamily": "PingFang", + "letterSpace": "0", + "fontShadow": false, + "color": "#000000", + "remarkBackgroundColor": "#ffffff" + }, + "legend": { + "show": true, + "hPosition": "center", + "vPosition": "bottom", + "orient": "horizontal", + "icon": "circle", + "color": "#000000", + "fontSize": 12, + "size": 4, + "showRange": true, + "sort": "none", + "customSort": [] + }, + "xAxis": { + "show": true, + "position": "bottom", + "nameShow": false, + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}", + "lengthLimit": 10 + }, + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "yAxis": { + "show": true, + "position": "left", + "nameShow": false, + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}", + "lengthLimit": 10 + }, + "axisLine": { + "show": false, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "yAxisExt": { + "show": true, + "position": "right", + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}" + }, + "axisLine": { + "show": false, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "misc": { + "showName": false, + "color": "#000000", + "fontSize": 12, + "axisColor": "#999", + "splitNumber": 5, + "axisLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "type": "solid" + } + }, + "axisTick": { + "show": false, + "length": 5, + "lineStyle": { + "color": "#000000", + "width": 1, + "type": "solid" + } + }, + "axisLabel": { + "show": false, + "rotate": 0, + "margin": 8, + "color": "#000000", + "fontSize": "12", + "formatter": "{value}" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "type": "solid" + } + }, + "splitArea": { + "show": true + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + } + } + }, + "customStyleMobile": null, + "customFilter": { + "logic": null, + "items": null + }, + "drillFields": [], + "senior": { + "functionCfg": { + "sliderShow": false, + "sliderRange": [ + 0, + 10 + ], + "sliderBg": "#FFFFFF", + "sliderFillBg": "#BCD6F1", + "sliderTextColor": "#999999", + "emptyDataStrategy": "ignoreData", + "emptyDataCustomValue": "", + "emptyDataFieldCtrl": [] + }, + "assistLineCfg": { + "enable": false, + "assistLine": [] + }, + "threshold": { + "enable": false, + "gaugeThreshold": "", + "liquidThreshold": "", + "labelThreshold": [], + "tableThreshold": [], + "textLabelThreshold": [], + "lineLabelThreshold": [] + }, + "scrollCfg": { + "open": false, + "row": 1, + "interval": 2000, + "step": 50 + }, + "areaMapping": {}, + "bubbleCfg": { + "enable": false, + "speed": 1, + "rings": 1, + "type": "wave" + } + }, + "createBy": null, + "createTime": null, + "updateTime": null, + "snapshot": null, + "stylePriority": "panel", + "chartType": "private", + "isPlugin": false, + "dataFrom": "calc", + "viewFields": [], + "refreshViewEnable": false, + "refreshUnit": "minute", + "refreshTime": 5, + "linkageActive": false, + "jumpActive": false, + "aggregate": null, + "flowMapStartName": [], + "flowMapEndName": [], + "calParams": [], + "extColor": null, + "sortPriority": [], + "data": null, + "privileges": null, + "isLeaf": null, + "pid": null, + "sql": null, + "drill": false, + "drillFilters": null, + "position": null, + "totalPage": 0, + "totalItems": 0, + "datasetMode": 0, + "datasourceType": null, + "chartExtRequest": null, + "isExcelExport": false, + "exportDatasetOriginData": false, + "cache": false, + "sourceTableId": null, + "downloadType": null, + "xAxis": [ + { + "id": "{{ XAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_platform", + "name": "访问平台", + "dbFieldName": null, + "description": "访问平台", + "dataeaseName": "{{ XAXIS_DE_NAME }}", + "groupType": "d", + "type": "VARCHAR", + "precision": null, + "scale": null, + "deType": 0, + "deExtractType": 0, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ XAXIS_DE_NAME }}", + "groupList": [], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [], + "summary": "count", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false + } + ], + "xAxisExt": [], + "yAxis": [ + { + "id": "{{ YAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_count", + "name": "访问次数", + "dbFieldName": null, + "description": "访问次数", + "dataeaseName": "{{ YAXIS_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ YAXIS_DE_NAME }}", + "groupList": [], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false + } + ], + "yAxisExt": [] + } + }, + "appData": null, + "id": "{{ SCENE_ID }}", + "name": "单柱状图", + "pid": "0", + "status": 1, + "selfWatermarkStatus": true, + "type": "dashboard", + "creatorName": "系统管理员", + "updateName": "系统管理员", + "createTime": 1775811411420, + "updateTime": 1775811415375, + "watermarkInfo": null, + "weight": 9, + "ext": 0, + "contentId": "{{ CONTENT_ID }}", + "mobileLayout": false, + "checkVersion": "2.10.20" +} \ No newline at end of file diff --git a/templates/chart_line/params.json b/templates/chart_line/params.json new file mode 100644 index 0000000..04029d4 --- /dev/null +++ b/templates/chart_line/params.json @@ -0,0 +1,28 @@ +{ + "_comment": "chart_line 模板参数(来源:DataEase 抓包 + 参数化)", + "view": { + "VIEW_ID": "7447105738385657856", + "CONTENT_ID": "7447106138476122112", + "SCENE_ID": "0" + }, + "dataset": { + "DATASOURCE_ID": "1166370337189924864", + "DATASET_TABLE_ID": "7387069976563159040", + "DATASET_GROUP_ID": "1178801986976485376" + }, + "fields": { + "xAxis": { + "XAXIS_FIELD_ID": "1761214724097", + "XAXIS_DE_NAME": "f_3319f6ba3e484e93" + }, + "yAxis": { + "YAXIS_FIELD_ID": "1761214724096", + "YAXIS_DE_NAME": "f_97666027e11d7dc5", + "YAXIS_SERIES_ID": "1761214724096-yAxis" + }, + "yAxis2": { + "YAXIS2_FIELD_ID": "1761214724100", + "YAXIS2_DE_NAME": "f_c4c3c6a1a3d6688b" + } + } +} diff --git a/templates/chart_line/template.j2 b/templates/chart_line/template.j2 new file mode 100644 index 0000000..2880f45 --- /dev/null +++ b/templates/chart_line/template.j2 @@ -0,0 +1,1254 @@ +{ + "canvasStyleData": "{\"width\":1920,\"height\":1080,\"refreshBrowserEnable\":false,\"refreshBrowserUnit\":\"minute\",\"refreshBrowserTime\":5,\"refreshViewEnable\":false,\"refreshViewLoading\":true,\"refreshUnit\":\"minute\",\"refreshTime\":5,\"popupAvailable\":true,\"popupButtonAvailable\":true,\"suspensionButtonAvailable\":false,\"screenAdaptor\":\"widthFirst\",\"dashboardAdaptor\":\"keepHeightAndWidth\",\"scale\":15,\"scaleWidth\":15,\"scaleHeight\":15,\"backgroundColorSelect\":true,\"backgroundImageEnable\":false,\"backgroundType\":\"backgroundColor\",\"background\":\"\",\"openCommonStyle\":true,\"opacity\":1,\"fontSize\":14,\"fontFamily\":\"PingFang\",\"themeId\":\"10001\",\"color\":\"#000000\",\"backgroundColor\":\"#f5f6f7\",\"dashboard\":{\"gap\":\"yes\",\"gapSize\":5,\"gapMode\":\"middle\",\"showGrid\":false,\"matrixBase\":4,\"resultMode\":\"all\",\"resultCount\":1000,\"themeColor\":\"light\",\"mobileSetting\":{\"customSetting\":false,\"imageUrl\":null,\"backgroundType\":\"image\",\"color\":\"#000\"}},\"component\":{\"chartTitle\":{\"show\":true,\"fontSize\":16,\"hPosition\":\"left\",\"vPosition\":\"top\",\"isItalic\":false,\"isBolder\":true,\"remarkShow\":false,\"remark\":\"\",\"fontFamily\":\"\",\"letterSpace\":\"0\",\"fontShadow\":false,\"color\":\"#000000\",\"remarkBackgroundColor\":\"#ffffff\"},\"chartColor\":{\"basicStyle\":{\"colorScheme\":\"default\",\"colors\":[\"#1E90FF\",\"#90EE90\",\"#00CED1\",\"#E2BD84\",\"#7A90E0\",\"#3BA272\",\"#2BE7FF\",\"#0A8ADA\",\"#FFD700\"],\"alpha\":100,\"gradient\":false,\"mapStyle\":\"normal\",\"areaBaseColor\":\"#FFFFFF\",\"areaBorderColor\":\"#303133\",\"gaugeStyle\":\"default\",\"tableBorderColor\":\"#E6E7E4\",\"tableScrollBarColor\":\"rgba(0, 0, 0, 0.15)\",\"zoomButtonColor\":\"#aaa\",\"zoomBackground\":\"#fff\"},\"misc\":{\"flowMapConfig\":{\"lineConfig\":{\"mapLineAnimate\":true,\"mapLineGradient\":false,\"mapLineSourceColor\":\"#146C94\",\"mapLineTargetColor\":\"#576CBC\"}},\"nameFontColor\":\"#000000\",\"valueFontColor\":\"#5470c6\"},\"tableHeader\":{\"tableHeaderBgColor\":\"#1E90FF\",\"tableHeaderCornerBgColor\":\"#1E90FF\",\"tableHeaderColBgColor\":\"#1E90FF\",\"tableHeaderFontColor\":\"#000000\",\"tableHeaderCornerFontColor\":\"#000000\",\"tableHeaderColFontColor\":\"#000000\"},\"tableCell\":{\"tableItemBgColor\":\"#FFFFFF\",\"tableFontColor\":\"#000000\",\"tableItemSubBgColor\":\"#1E90FF\"}},\"chartCommonStyle\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"filterStyle\":{\"layout\":\"horizontal\",\"titleLayout\":\"left\",\"labelColor\":\"#1f2329\",\"titleColor\":\"#1f2329\",\"color\":\"#1f2329\",\"borderColor\":\"#bbbfc4\",\"text\":\"#1f2329\",\"bgColor\":\"#FFFFFF\"},\"tabStyle\":{\"headPosition\":\"left\",\"headFontColor\":\"#000000\",\"headFontActiveColor\":\"#000000\",\"headBorderColor\":\"#ffffff\",\"headBorderActiveColor\":\"#ffffff\"},\"seniorStyleSetting\":{\"linkageIconColor\":\"#a6a6a6\",\"drillLayerColor\":\"#a6a6a6\",\"pagerColor\":\"#a6a6a6\"},\"formatterItem\":{\"type\":\"auto\",\"unitLanguage\":\"ch\",\"unit\":1,\"suffix\":\"\",\"decimalCount\":2,\"thousandSeparator\":true}},\"dialogBackgroundColor\":\"rgba(255, 255, 255, 1)\",\"dialogButton\":\"#020408\"}", + "componentData": "[{\"animations\":[],\"canvasId\":\"canvas-main\",\"events\":{\"checked\":false,\"showTips\":false,\"type\":\"jump\",\"typeList\":[{\"key\":\"jump\",\"label\":\"jump\"},{\"key\":\"download\",\"label\":\"download\"},{\"key\":\"share\",\"label\":\"share\"},{\"key\":\"fullScreen\",\"label\":\"fullScreen\"},{\"key\":\"showHidden\",\"label\":\"showHidden\"},{\"key\":\"refreshDataV\",\"label\":\"refreshDataV\"},{\"key\":\"refreshView\",\"label\":\"refreshView\"}],\"jump\":{\"value\":\"https://\",\"type\":\"_blank\"},\"download\":{\"value\":true},\"share\":{\"value\":true},\"showHidden\":{\"value\":true},\"refreshDataV\":{\"value\":true},\"refreshView\":{\"value\":true,\"target\":\"all\"}},\"carousel\":{\"enable\":false,\"time\":10},\"multiDimensional\":{\"enable\":false,\"x\":0,\"y\":0,\"z\":0},\"groupStyle\":{},\"isLock\":false,\"maintainRadio\":false,\"aspectRatio\":1,\"isShow\":true,\"dashboardHidden\":false,\"category\":\"base\",\"dragging\":false,\"resizing\":false,\"collapseName\":[\"position\",\"background\",\"style\",\"picture\",\"frameLinks\",\"videoLinks\",\"streamLinks\",\"carouselInfo\",\"events\",\"decoration_style\"],\"linkage\":{\"duration\":0,\"data\":[{\"id\":\"\",\"label\":\"\",\"event\":\"\",\"style\":[{\"key\":\"\",\"value\":\"\"}]}]},\"component\":\"UserView\",\"name\":\"基础折线图\",\"label\":\"基础折线图\",\"propValue\":{\"textValue\":\"\",\"urlList\":[]},\"icon\":\"bar\",\"innerType\":\"line\",\"editing\":false,\"canvasActive\":false,\"actionSelection\":{\"linkageActive\":\"custom\"},\"x\":1,\"y\":1,\"sizeX\":36,\"sizeY\":14,\"style\":{\"rotate\":0,\"opacity\":1,\"borderActive\":false,\"borderWidth\":1,\"borderRadius\":5,\"borderStyle\":\"solid\",\"borderColor\":\"rgba(204, 204, 204, 1)\",\"adaptation\":\"adaptation\",\"width\":552,\"height\":66.88888888888889,\"left\":0,\"top\":0},\"matrixStyle\":{},\"commonBackground\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"state\":\"ready\",\"render\":\"antv\",\"isPlugin\":false,\"id\":\"{{ VIEW_ID }}\",\"_dragId\":0,\"linkageFilters\":[],\"expand\":false,\"resizeInnerKeep\":false}]", + "canvasViewInfo": { + "{{ VIEW_ID }}": { + "id": "{{ VIEW_ID }}", + "title": "基础折线图", + "sceneId": "{{ SCENE_ID }}", + "tableId": "{{ DATASET_GROUP_ID }}", + "type": "line", + "render": "antv", + "resultCount": 1000, + "resultMode": "custom", + "extStack": [ + + ], + "extBubble": [ + + ], + "extLabel": [ + + ], + "extTooltip": [ + + ], + "customAttr": { + "basicStyle": { + "alpha": 100, + "tableBorderColor": "#E6E7E4", + "tableScrollBarColor": "rgba(0, 0, 0, 0.15)", + "tableColumnMode": "adapt", + "tableColumnWidth": 100, + "tableFieldWidth": [ + + ], + "tablePageMode": "page", + "tablePageStyle": "simple", + "tablePageSize": 20, + "gaugeStyle": "default", + "colorScheme": "default", + "colors": [ + "#1E90FF", + "#90EE90", + "#00CED1", + "#E2BD84", + "#7A90E0", + "#3BA272", + "#2BE7FF", + "#0A8ADA", + "#FFD700" + ], + "mapVendor": "amap", + "gradient": false, + "lineWidth": 2, + "lineSymbol": "circle", + "lineSymbolSize": 4, + "lineSmooth": true, + "barDefault": true, + "radiusColumnBar": "rightAngle", + "columnBarRightAngleRadius": 20, + "columnWidthRatio": 60, + "barWidth": 40, + "barGap": 0.4, + "lineType": "solid", + "scatterSymbol": "circle", + "scatterSymbolSize": 8, + "radarShape": "polygon", + "mapStyle": "normal", + "heatMapType": "heatmap", + "heatMapIntensity": 2, + "heatMapRadius": 20, + "areaBorderColor": "#303133", + "areaBaseColor": "#FFFFFF", + "mapSymbolOpacity": 0.7, + "mapSymbolStrokeWidth": 2, + "mapSymbol": "circle", + "mapSymbolSize": 6, + "radius": 80, + "innerRadius": 60, + "showZoom": true, + "zoomButtonColor": "#aaa", + "zoomBackground": "#fff", + "tableLayoutMode": "grid", + "defaultExpandLevel": 1, + "calcTopN": false, + "topN": 5, + "topNLabel": "其他", + "gaugeAxisLine": true, + "gaugePercentLabel": true, + "showSummary": false, + "summaryLabel": "总计", + "seriesColor": [ + + ], + "layout": "horizontal", + "mapSymbolSizeMin": 4, + "mapSymbolSizeMax": 30, + "showLabel": true, + "mapStyleUrl": "", + "autoFit": true, + "mapCenter": { + "longitude": 117.232, + "latitude": 39.354 + }, + "zoomLevel": 7, + "customIcon": "", + "showHoverStyle": true, + "autoWrap": false, + "maxLines": 3, + "radarShowPoint": true, + "radarPointSize": 4, + "radarAreaColor": true, + "circleBorderColor": "#fff", + "circleBorderWidth": 0, + "circlePadding": 0, + "quotaPosition": "col", + "quotaColLabel": "数值", + "tableRowHeaderMode": "adapt", + "tableRowHeaderWidth": 120, + "tableRowHeaderWidthPercent": 20 + }, + "misc": { + "pieInnerRadius": 0, + "pieOuterRadius": 80, + "radarShape": "polygon", + "radarSize": 80, + "gaugeMinType": "fix", + "gaugeMinField": { + "id": "", + "summary": "" + }, + "gaugeMin": 0, + "gaugeMaxType": "dynamic", + "gaugeMaxField": { + "id": "", + "summary": "" + }, + "gaugeStartAngle": 225, + "gaugeEndAngle": -45, + "nameFontSize": 18, + "valueFontSize": 18, + "nameValueSpace": 10, + "valueFontColor": "#5470c6", + "valueFontFamily": "Microsoft YaHei", + "valueFontIsBolder": false, + "valueFontIsItalic": false, + "valueLetterSpace": 0, + "valueFontShadow": false, + "showName": true, + "nameFontColor": "#000000", + "nameFontFamily": "Microsoft YaHei", + "nameFontIsBolder": false, + "nameFontIsItalic": false, + "nameLetterSpace": "0", + "nameFontShadow": false, + "treemapWidth": 80, + "treemapHeight": 80, + "liquidMaxType": "dynamic", + "liquidMaxField": { + "id": "", + "summary": "" + }, + "liquidSize": 80, + "liquidShape": "circle", + "hPosition": "center", + "vPosition": "center", + "mapPitch": 0, + "wordSizeRange": [ + 8, + 32 + ], + "wordSpacing": 6, + "mapAutoLegend": true, + "mapLegendMax": 0, + "mapLegendMin": 0, + "mapLegendNumber": 9, + "mapLegendRangeType": "quantize", + "mapLegendCustomRange": [ + + ], + "flowMapConfig": { + "lineConfig": { + "mapLineAnimate": true, + "mapLineType": "arc", + "mapLineWidth": 1, + "mapLineAnimateDuration": 3, + "mapLineGradient": false, + "mapLineSourceColor": "#146C94", + "mapLineTargetColor": "#576CBC", + "alpha": 100 + }, + "pointConfig": { + "text": { + "color": "#146C94", + "fontSize": 10 + }, + "point": { + "color": "#146C94", + "size": 4, + "animate": false, + "speed": 0.01 + } + } + }, + "wordCloudAxisValueRange": { + "auto": true, + "min": 0, + "max": 0 + }, + "bullet": { + "bar": { + "ranges": { + "fill": [ + "rgba(0,128,255,0.3)" + ], + "size": 20, + "showType": "dynamic", + "fixedRangeNumber": 3, + "symbol": "circle", + "symbolSize": 4 + }, + "measures": { + "fill": [ + "rgba(0,128,255,1)" + ], + "size": 15, + "symbol": "circle", + "symbolSize": 4 + }, + "target": { + "fill": "#000000", + "size": 20, + "showType": "dynamic", + "value": 0, + "symbol": "line", + "symbolSize": 4 + } + } + }, + "liquidShowBorder": false, + "liquidBorderWidth": 4, + "liquidBorderDistance": 8 + }, + "label": { + "show": false, + "childrenShow": true, + "position": "top", + "color": "#000000", + "fontSize": 12, + "formatter": "", + "labelLine": { + "show": true + }, + "labelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "reserveDecimalCount": 2, + "labelShadow": false, + "labelBgColor": "", + "labelShadowColor": "", + "quotaLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "showDimension": true, + "showQuota": false, + "showProportion": true, + "seriesLabelFormatter": [ + { + "id": "{{ YAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_count", + "name": "访问次数", + "dbFieldName": null, + "description": "访问次数", + "dataeaseName": "{{ YAXIS_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ YAXIS_DE_NAME }}", + "groupList": [ + + ], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [ + + ], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [ + + ], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "axisType": "yAxis", + "seriesId": "{{ YAXIS_FIELD_ID }}-yAxis", + "optionLabel": "访问次数(求和)", + "optionShowName": "访问次数(求和)", + "show": true, + "color": "#000000", + "fontSize": 12, + "showExtremum": false, + "position": "top" + } + ], + "conversionTag": { + "show": false, + "precision": 2, + "text": "转化率" + }, + "showTotal": false, + "totalFontSize": 12, + "totalColor": "#FFF", + "totalFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "showStackQuota": false, + "fullDisplay": false, + "proportionSeriesFormatter": { + "show": false, + "color": "#000000", + "fontSize": 12, + "formatterCfg": { + "decimalCount": 2 + } + } + }, + "tooltip": { + "show": true, + "trigger": "item", + "confine": true, + "fontSize": 12, + "color": "#000000", + "tooltipFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "backgroundColor": "#FFFFFF", + "seriesTooltipFormatter": [ + { + "id": "{{ YAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_count", + "name": "访问次数", + "dbFieldName": null, + "description": "访问次数", + "dataeaseName": "{{ YAXIS_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ YAXIS_DE_NAME }}", + "groupList": [ + + ], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [ + + ], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [ + + ], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "seriesId": "{{ YAXIS_FIELD_ID }}-yAxis", + "show": true, + "axisType": "yAxis" + }, + { + "id": "{{ YAXIS2_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "page_views", + "name": "浏览量", + "dbFieldName": null, + "description": "浏览量", + "dataeaseName": "{{ YAXIS2_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ YAXIS2_DE_NAME }}", + "groupList": [ + + ], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [ + + ], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [ + + ], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "seriesId": "{{ YAXIS2_FIELD_ID }}", + "show": false + }, + { + "id": "-1", + "datasourceId": null, + "datasetTableId": null, + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "*", + "name": "记录数*", + "dbFieldName": null, + "description": null, + "dataeaseName": "*", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": null, + "extField": 1, + "checked": true, + "columnIndex": 999, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": null, + "groupList": null, + "otherGroup": null, + "desensitized": null, + "orderChecked": null, + "params": null, + "summary": "count", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [ + + ], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false, + "seriesId": "-1", + "show": false + } + ], + "carousel": { + "enable": false, + "stayTime": 3, + "intervalTime": 1 + } + }, + "tableTotal": { + "row": { + "showGrandTotals": true, + "showSubTotals": true, + "reverseLayout": false, + "reverseSubLayout": false, + "label": "总计", + "subLabel": "小计", + "subTotalsDimensions": [ + + ], + "subTotalsDimensionsNew": true, + "calcTotals": { + "aggregation": "SUM", + "cfg": [ + + ] + }, + "calcSubTotals": { + "aggregation": "SUM", + "cfg": [ + + ] + }, + "totalSort": "none", + "totalSortField": "" + }, + "col": { + "showGrandTotals": true, + "showSubTotals": true, + "reverseLayout": false, + "reverseSubLayout": false, + "label": "总计", + "subLabel": "小计", + "subTotalsDimensions": [ + + ], + "calcTotals": { + "aggregation": "SUM", + "cfg": [ + + ] + }, + "calcSubTotals": { + "aggregation": "SUM", + "cfg": [ + + ] + }, + "totalSort": "none", + "totalSortField": "" + } + }, + "tableHeader": { + "indexLabel": "序号", + "showIndex": false, + "tableHeaderAlign": "left", + "tableHeaderCornerAlign": "left", + "tableHeaderColAlign": "left", + "tableHeaderBgColor": "#1E90FF", + "tableHeaderCornerBgColor": "#1E90FF", + "tableHeaderColBgColor": "#1E90FF", + "tableHeaderFontColor": "#000000", + "tableHeaderCornerFontColor": "#000000", + "tableHeaderColFontColor": "#000000", + "tableTitleFontSize": 12, + "tableTitleCornerFontSize": 12, + "tableTitleColFontSize": 12, + "tableTitleHeight": 36, + "tableHeaderSort": false, + "showColTooltip": false, + "showRowTooltip": false, + "showTableHeader": true, + "showHorizonBorder": true, + "showVerticalBorder": true, + "isItalic": false, + "isCornerItalic": false, + "isColItalic": false, + "isBolder": true, + "isCornerBolder": true, + "isColBolder": true, + "headerGroup": false, + "headerGroupConfig": { + "columns": [ + + ], + "meta": [ + + ] + }, + "rowHeaderFreeze": true, + "alignConfig": [ + + ] + }, + "tableCell": { + "tableFontColor": "#000000", + "tableItemAlign": "right", + "tableItemBgColor": "#FFFFFF", + "tableItemFontSize": 12, + "tableItemHeight": 36, + "enableTableCrossBG": false, + "tableItemSubBgColor": "#1E90FF", + "showTooltip": false, + "showHorizonBorder": true, + "showVerticalBorder": true, + "isItalic": false, + "isBolder": false, + "tableFreeze": false, + "tableColumnFreezeHead": 0, + "tableRowFreezeHead": 0, + "mergeCells": true, + "alignConfig": [ + + ] + }, + "indicator": { + "show": true, + "fontSize": 20, + "color": "#5470C6ff", + "hPosition": "center", + "vPosition": "center", + "isItalic": false, + "isBolder": true, + "fontFamily": "Microsoft YaHei", + "letterSpace": 0, + "fontShadow": false, + "backgroundColor": "", + "suffixEnable": true, + "suffix": "", + "suffixFontSize": 14, + "suffixColor": "#5470C6ff", + "suffixIsItalic": false, + "suffixIsBolder": true, + "suffixFontFamily": "Microsoft YaHei", + "suffixLetterSpace": 0, + "suffixFontShadow": false + }, + "indicatorName": { + "show": true, + "fontSize": 18, + "color": "#ffffffff", + "isItalic": false, + "isBolder": true, + "fontFamily": "Microsoft YaHei", + "letterSpace": 0, + "fontShadow": false, + "nameValueSpacing": 0, + "namePosition": "bottom" + }, + "map": { + "id": "", + "level": "world" + } + }, + "customAttrMobile": null, + "customStyle": { + "text": { + "show": true, + "fontSize": 16, + "hPosition": "left", + "vPosition": "top", + "isItalic": false, + "isBolder": true, + "remarkShow": false, + "remark": "", + "fontFamily": "PingFang", + "letterSpace": "0", + "fontShadow": false, + "color": "#000000", + "remarkBackgroundColor": "#ffffff" + }, + "legend": { + "show": true, + "hPosition": "center", + "vPosition": "bottom", + "orient": "horizontal", + "icon": "circle", + "color": "#000000", + "fontSize": 12, + "size": 4, + "showRange": true, + "sort": "none", + "customSort": [ + + ] + }, + "xAxis": { + "show": true, + "position": "bottom", + "nameShow": false, + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}", + "lengthLimit": 10 + }, + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "yAxis": { + "show": true, + "position": "left", + "nameShow": false, + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}", + "lengthLimit": 10 + }, + "axisLine": { + "show": false, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "yAxisExt": { + "show": true, + "position": "right", + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}" + }, + "axisLine": { + "show": false, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "misc": { + "showName": false, + "color": "#000000", + "fontSize": 12, + "axisColor": "#999", + "splitNumber": 5, + "axisLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "type": "solid" + } + }, + "axisTick": { + "show": false, + "length": 5, + "lineStyle": { + "color": "#000000", + "width": 1, + "type": "solid" + } + }, + "axisLabel": { + "show": false, + "rotate": 0, + "margin": 8, + "color": "#000000", + "fontSize": "12", + "formatter": "{value}" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "type": "solid" + } + }, + "splitArea": { + "show": true + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + } + } + }, + "customStyleMobile": null, + "customFilter": { + "logic": null, + "items": null + }, + "drillFields": [ + + ], + "senior": { + "functionCfg": { + "sliderShow": false, + "sliderRange": [ + 0, + 10 + ], + "sliderBg": "#FFFFFF", + "sliderFillBg": "#BCD6F1", + "sliderTextColor": "#999999", + "emptyDataStrategy": "breakLine", + "emptyDataCustomValue": "", + "emptyDataFieldCtrl": [ + + ] + }, + "assistLineCfg": { + "enable": false, + "assistLine": [ + + ] + }, + "threshold": { + "enable": false, + "gaugeThreshold": "", + "liquidThreshold": "", + "labelThreshold": [ + + ], + "tableThreshold": [ + + ], + "textLabelThreshold": [ + + ], + "lineLabelThreshold": [ + + ] + }, + "scrollCfg": { + "open": false, + "row": 1, + "interval": 2000, + "step": 50 + }, + "areaMapping": { + + }, + "bubbleCfg": { + "enable": false, + "speed": 1, + "rings": 1, + "type": "wave" + } + }, + "createBy": null, + "createTime": null, + "updateTime": null, + "snapshot": null, + "stylePriority": "panel", + "chartType": "private", + "isPlugin": false, + "dataFrom": "calc", + "viewFields": [ + + ], + "refreshViewEnable": false, + "refreshUnit": "minute", + "refreshTime": 5, + "linkageActive": false, + "jumpActive": false, + "aggregate": null, + "flowMapStartName": [ + + ], + "flowMapEndName": [ + + ], + "calParams": [ + + ], + "extColor": null, + "sortPriority": [ + + ], + "data": null, + "privileges": null, + "isLeaf": null, + "pid": null, + "sql": null, + "drill": false, + "drillFilters": null, + "position": null, + "totalPage": 0, + "totalItems": 0, + "datasetMode": 0, + "datasourceType": null, + "chartExtRequest": null, + "isExcelExport": false, + "exportDatasetOriginData": false, + "cache": false, + "sourceTableId": null, + "downloadType": null, + "xAxis": [ + { + "id": "{{ XAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_platform", + "name": "访问平台", + "dbFieldName": null, + "description": "访问平台", + "dataeaseName": "{{ XAXIS_DE_NAME }}", + "groupType": "d", + "type": "VARCHAR", + "precision": null, + "scale": null, + "deType": 0, + "deExtractType": 0, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ XAXIS_DE_NAME }}", + "groupList": [ + + ], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [ + + ], + "summary": "count", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [ + + ], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false + } + ], + "xAxisExt": [ + + ], + "yAxis": [ + { + "id": "{{ YAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_count", + "name": "访问次数", + "dbFieldName": null, + "description": "访问次数", + "dataeaseName": "{{ YAXIS_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ YAXIS_DE_NAME }}", + "groupList": [ + + ], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [ + + ], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [ + + ], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false + } + ], + "yAxisExt": [ + + ] + } + }, + "appData": null, + "id": "{{ SCENE_ID }}", + "name": "新建仪表板line2", + "pid": "0", + "status": 1, + "selfWatermarkStatus": true, + "type": "dashboard", + "creatorName": "系统管理员", + "updateName": "系统管理员", + "createTime": 1775528391188, + "updateTime": 1775528399968, + "watermarkInfo": null, + "weight": 9, + "ext": 0, + "contentId": "{{ CONTENT_ID }}", + "mobileLayout": false, + "checkVersion": "2.10.20" +} \ No newline at end of file diff --git a/templates/chart_pie/params.json b/templates/chart_pie/params.json new file mode 100644 index 0000000..c8d986c --- /dev/null +++ b/templates/chart_pie/params.json @@ -0,0 +1,28 @@ +{ + "_comment": "chart_pie 模板参数(来源:DataEase 抓包 + 参数化)", + "view": { + "VIEW_ID": "7446557568647827456", + "CONTENT_ID": "7446557957866655744", + "SCENE_ID": "0" + }, + "dataset": { + "DATASOURCE_ID": "1166370337189924864", + "DATASET_TABLE_ID": "7387069976563159040", + "DATASET_GROUP_ID": "1178801986976485376" + }, + "fields": { + "xAxis": { + "XAXIS_FIELD_ID": "1761214724097", + "XAXIS_DE_NAME": "f_3319f6ba3e484e93" + }, + "yAxis": { + "YAXIS_FIELD_ID": "1761214724096", + "YAXIS_DE_NAME": "f_97666027e11d7dc5", + "YAXIS_SERIES_ID": "1761214724096-yAxis" + }, + "yAxis2": { + "YAXIS2_FIELD_ID": "1761214724100", + "YAXIS2_DE_NAME": "f_c4c3c6a1a3d6688b" + } + } +} diff --git a/templates/chart_pie/template.j2 b/templates/chart_pie/template.j2 new file mode 100644 index 0000000..6cde198 --- /dev/null +++ b/templates/chart_pie/template.j2 @@ -0,0 +1 @@ +{"canvasStyleData": "{\"width\":1920,\"height\":1080,\"refreshBrowserEnable\":false,\"refreshBrowserUnit\":\"minute\",\"refreshBrowserTime\":5,\"refreshViewEnable\":false,\"refreshViewLoading\":true,\"refreshUnit\":\"minute\",\"refreshTime\":5,\"popupAvailable\":true,\"popupButtonAvailable\":true,\"suspensionButtonAvailable\":false,\"screenAdaptor\":\"widthFirst\",\"dashboardAdaptor\":\"keepHeightAndWidth\",\"scale\":36,\"scaleWidth\":60,\"scaleHeight\":60,\"backgroundColorSelect\":true,\"backgroundImageEnable\":false,\"backgroundType\":\"backgroundColor\",\"background\":\"\",\"openCommonStyle\":true,\"opacity\":1,\"fontSize\":14,\"fontFamily\":\"PingFang\",\"themeId\":\"10001\",\"color\":\"#000000\",\"backgroundColor\":\"#f5f6f7\",\"dashboard\":{\"gap\":\"yes\",\"gapSize\":5,\"gapMode\":\"middle\",\"showGrid\":false,\"matrixBase\":4,\"resultMode\":\"all\",\"resultCount\":1000,\"themeColor\":\"light\",\"mobileSetting\":{\"customSetting\":false,\"imageUrl\":null,\"backgroundType\":\"image\",\"color\":\"#000\"}},\"component\":{\"chartTitle\":{\"show\":true,\"fontSize\":16,\"hPosition\":\"left\",\"vPosition\":\"top\",\"isItalic\":false,\"isBolder\":true,\"remarkShow\":false,\"remark\":\"\",\"fontFamily\":\"\",\"letterSpace\":\"0\",\"fontShadow\":false,\"color\":\"#000000\",\"remarkBackgroundColor\":\"#ffffff\"},\"chartColor\":{\"basicStyle\":{\"colorScheme\":\"default\",\"colors\":[\"#1E90FF\",\"#90EE90\",\"#00CED1\",\"#E2BD84\",\"#7A90E0\",\"#3BA272\",\"#2BE7FF\",\"#0A8ADA\",\"#FFD700\"],\"alpha\":100,\"gradient\":false,\"mapStyle\":\"normal\",\"areaBaseColor\":\"#FFFFFF\",\"areaBorderColor\":\"#303133\",\"gaugeStyle\":\"default\",\"tableBorderColor\":\"#E6E7E4\",\"tableScrollBarColor\":\"rgba(0, 0, 0, 0.15)\",\"zoomButtonColor\":\"#aaa\",\"zoomBackground\":\"#fff\"},\"misc\":{\"flowMapConfig\":{\"lineConfig\":{\"mapLineAnimate\":true,\"mapLineGradient\":false,\"mapLineSourceColor\":\"#146C94\",\"mapLineTargetColor\":\"#576CBC\"}},\"nameFontColor\":\"#000000\",\"valueFontColor\":\"#5470c6\"},\"tableHeader\":{\"tableHeaderBgColor\":\"#1E90FF\",\"tableHeaderCornerBgColor\":\"#1E90FF\",\"tableHeaderColBgColor\":\"#1E90FF\",\"tableHeaderFontColor\":\"#000000\",\"tableHeaderCornerFontColor\":\"#000000\",\"tableHeaderColFontColor\":\"#000000\"},\"tableCell\":{\"tableItemBgColor\":\"#FFFFFF\",\"tableFontColor\":\"#000000\",\"tableItemSubBgColor\":\"#1E90FF\"}},\"chartCommonStyle\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"filterStyle\":{\"layout\":\"horizontal\",\"titleLayout\":\"left\",\"labelColor\":\"#1f2329\",\"titleColor\":\"#1f2329\",\"color\":\"#1f2329\",\"borderColor\":\"#bbbfc4\",\"text\":\"#1f2329\",\"bgColor\":\"#FFFFFF\"},\"tabStyle\":{\"headPosition\":\"left\",\"headFontColor\":\"#000000\",\"headFontActiveColor\":\"#000000\",\"headBorderColor\":\"#ffffff\",\"headBorderActiveColor\":\"#ffffff\"},\"seniorStyleSetting\":{\"linkageIconColor\":\"#a6a6a6\",\"drillLayerColor\":\"#a6a6a6\",\"pagerColor\":\"#a6a6a6\"},\"formatterItem\":{\"type\":\"auto\",\"unitLanguage\":\"ch\",\"unit\":1,\"suffix\":\"\",\"decimalCount\":2,\"thousandSeparator\":true}},\"dialogBackgroundColor\":\"rgba(255, 255, 255, 1)\",\"dialogButton\":\"#020408\"}", "componentData": "[{\"animations\":[],\"canvasId\":\"canvas-main\",\"events\":{\"checked\":false,\"showTips\":false,\"type\":\"jump\",\"typeList\":[{\"key\":\"jump\",\"label\":\"jump\"},{\"key\":\"download\",\"label\":\"download\"},{\"key\":\"share\",\"label\":\"share\"},{\"key\":\"fullScreen\",\"label\":\"fullScreen\"},{\"key\":\"showHidden\",\"label\":\"showHidden\"},{\"key\":\"refreshDataV\",\"label\":\"refreshDataV\"},{\"key\":\"refreshView\",\"label\":\"refreshView\"}],\"jump\":{\"value\":\"https://\",\"type\":\"_blank\"},\"download\":{\"value\":true},\"share\":{\"value\":true},\"showHidden\":{\"value\":true},\"refreshDataV\":{\"value\":true},\"refreshView\":{\"value\":true,\"target\":\"all\"}},\"carousel\":{\"enable\":false,\"time\":10},\"multiDimensional\":{\"enable\":false,\"x\":0,\"y\":0,\"z\":0},\"groupStyle\":{},\"isLock\":false,\"maintainRadio\":false,\"aspectRatio\":1,\"isShow\":true,\"dashboardHidden\":false,\"category\":\"base\",\"dragging\":false,\"resizing\":false,\"collapseName\":[\"position\",\"background\",\"style\",\"picture\",\"frameLinks\",\"videoLinks\",\"streamLinks\",\"carouselInfo\",\"events\",\"decoration_style\"],\"linkage\":{\"duration\":0,\"data\":[{\"id\":\"\",\"label\":\"\",\"event\":\"\",\"style\":[{\"key\":\"\",\"value\":\"\"}]}]},\"component\":\"UserView\",\"name\":\"饼图\",\"label\":\"饼图\",\"propValue\":{\"textValue\":\"\",\"urlList\":[]},\"icon\":\"bar\",\"innerType\":\"pie\",\"editing\":false,\"canvasActive\":false,\"actionSelection\":{\"linkageActive\":\"custom\"},\"x\":1,\"y\":1,\"sizeX\":36,\"sizeY\":14,\"style\":{\"rotate\":0,\"opacity\":1,\"borderActive\":false,\"borderWidth\":1,\"borderRadius\":5,\"borderStyle\":\"solid\",\"borderColor\":\"rgba(204, 204, 204, 1)\",\"adaptation\":\"adaptation\",\"width\":519,\"height\":152.44444444444446,\"left\":0,\"top\":0},\"matrixStyle\":{},\"commonBackground\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"state\":\"ready\",\"render\":\"antv\",\"isPlugin\":false,\"id\":\"{{ VIEW_ID }}\",\"_dragId\":0,\"show\":true,\"linkageFilters\":[]}]", "canvasViewInfo": {"{{ VIEW_ID }}": {"id": "{{ VIEW_ID }}", "title": "饼图", "sceneId": 0, "tableId": "{{ DATASET_GROUP_ID }}", "type": "pie", "render": "antv", "resultCount": 1000, "resultMode": "custom", "refreshViewEnable": false, "refreshTime": 5, "refreshUnit": "minute", "xAxis": [{"id": "{{ XAXIS_FIELD_ID }}", "datasourceId": "{{ DATASOURCE_ID }}", "datasetTableId": "{{ DATASET_TABLE_ID }}", "datasetGroupId": "{{ DATASET_GROUP_ID }}", "chartId": null, "originName": "access_platform", "name": "访问平台", "dbFieldName": null, "description": "访问平台", "dataeaseName": "{{ XAXIS_DE_NAME }}", "groupType": "d", "type": "VARCHAR", "precision": null, "scale": null, "deType": 0, "deExtractType": 0, "extField": 0, "checked": true, "columnIndex": null, "lastSyncTime": null, "dateFormat": null, "dateFormatType": null, "fieldShortName": "{{ XAXIS_DE_NAME }}", "groupList": [], "otherGroup": null, "desensitized": false, "orderChecked": false, "params": [], "summary": "count", "sort": "none", "dateStyle": "y_M_d", "datePattern": "date_sub", "dateShowFormat": "y_M_d", "chartType": "bar", "compareCalc": {"type": "none", "resultData": "percent", "field": null, "custom": null}, "logic": null, "filterType": null, "index": null, "formatterCfg": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "chartShowName": null, "filter": [], "customSort": null, "busiType": null, "hide": false, "field": null, "agg": false}], "xAxisExt": [], "yAxis": [{"id": "{{ YAXIS_FIELD_ID }}", "datasourceId": "{{ DATASOURCE_ID }}", "datasetTableId": "{{ DATASET_TABLE_ID }}", "datasetGroupId": "{{ DATASET_GROUP_ID }}", "chartId": null, "originName": "access_count", "name": "访问次数", "dbFieldName": null, "description": "访问次数", "dataeaseName": "{{ YAXIS_DE_NAME }}", "groupType": "q", "type": "INT", "precision": null, "scale": null, "deType": 2, "deExtractType": 2, "extField": 0, "checked": true, "columnIndex": null, "lastSyncTime": null, "dateFormat": null, "dateFormatType": null, "fieldShortName": "{{ YAXIS_DE_NAME }}", "groupList": [], "otherGroup": null, "desensitized": false, "orderChecked": false, "params": [], "summary": "sum", "sort": "none", "dateStyle": "y_M_d", "datePattern": "date_sub", "dateShowFormat": "y_M_d", "chartType": "bar", "compareCalc": {"type": "none", "resultData": "percent", "field": null, "custom": null}, "logic": null, "filterType": null, "index": null, "formatterCfg": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "chartShowName": null, "filter": [], "customSort": null, "busiType": null, "hide": false, "field": null, "agg": false, "axisType": "yAxis", "seriesId": "{{ YAXIS_FIELD_ID }}-yAxis"}], "yAxisExt": [], "extStack": [], "drillFields": [], "viewFields": [], "extBubble": [], "extLabel": [], "extTooltip": [], "customFilter": {}, "sortPriority": [], "customAttr": {"basicStyle": {"alpha": 100, "tableBorderColor": "#E6E7E4", "tableScrollBarColor": "rgba(0, 0, 0, 0.15)", "tableColumnMode": "adapt", "tableColumnWidth": 100, "tableFieldWidth": [], "tablePageMode": "page", "tablePageStyle": "simple", "tablePageSize": 20, "gaugeStyle": "default", "colorScheme": "default", "colors": ["#1E90FF", "#90EE90", "#00CED1", "#E2BD84", "#7A90E0", "#3BA272", "#2BE7FF", "#0A8ADA", "#FFD700"], "mapVendor": "amap", "gradient": false, "lineWidth": 2, "lineSymbol": "circle", "lineSymbolSize": 4, "lineSmooth": true, "barDefault": true, "radiusColumnBar": "rightAngle", "columnBarRightAngleRadius": 20, "columnWidthRatio": 60, "barWidth": 40, "barGap": 0.4, "lineType": "solid", "scatterSymbol": "circle", "scatterSymbolSize": 8, "radarShape": "polygon", "mapStyle": "normal", "heatMapType": "heatmap", "heatMapIntensity": 2, "heatMapRadius": 20, "areaBorderColor": "#303133", "areaBaseColor": "#FFFFFF", "mapSymbolOpacity": 0.7, "mapSymbolStrokeWidth": 2, "mapSymbol": "circle", "mapSymbolSize": 6, "radius": 80, "innerRadius": 60, "showZoom": true, "zoomButtonColor": "#aaa", "zoomBackground": "#fff", "tableLayoutMode": "grid", "defaultExpandLevel": 1, "calcTopN": false, "topN": 5, "topNLabel": "其他", "gaugeAxisLine": true, "gaugePercentLabel": true, "showSummary": false, "summaryLabel": "总计", "seriesColor": [], "layout": "horizontal", "mapSymbolSizeMin": 4, "mapSymbolSizeMax": 30, "showLabel": true, "mapStyleUrl": "", "autoFit": true, "mapCenter": {"longitude": 117.232, "latitude": 39.354}, "zoomLevel": 7, "customIcon": "", "showHoverStyle": true, "autoWrap": false, "maxLines": 3, "radarShowPoint": true, "radarPointSize": 4, "radarAreaColor": true, "circleBorderColor": "#fff", "circleBorderWidth": 0, "circlePadding": 0, "quotaPosition": "col", "quotaColLabel": "数值", "tableRowHeaderMode": "adapt", "tableRowHeaderWidth": 120, "tableRowHeaderWidthPercent": 20}, "misc": {"pieInnerRadius": 0, "pieOuterRadius": 80, "radarShape": "polygon", "radarSize": 80, "gaugeMinType": "fix", "gaugeMinField": {"id": "", "summary": ""}, "gaugeMin": 0, "gaugeMaxType": "dynamic", "gaugeMaxField": {"id": "", "summary": ""}, "gaugeStartAngle": 225, "gaugeEndAngle": -45, "nameFontSize": 18, "valueFontSize": 18, "nameValueSpace": 10, "valueFontColor": "#5470c6", "valueFontFamily": "Microsoft YaHei", "valueFontIsBolder": false, "valueFontIsItalic": false, "valueLetterSpace": 0, "valueFontShadow": false, "showName": true, "nameFontColor": "#000000", "nameFontFamily": "Microsoft YaHei", "nameFontIsBolder": false, "nameFontIsItalic": false, "nameLetterSpace": "0", "nameFontShadow": false, "treemapWidth": 80, "treemapHeight": 80, "liquidMaxType": "dynamic", "liquidMaxField": {"id": "", "summary": ""}, "liquidSize": 80, "liquidShape": "circle", "hPosition": "center", "vPosition": "center", "mapPitch": 0, "wordSizeRange": [8, 32], "wordSpacing": 6, "mapAutoLegend": true, "mapLegendMax": 0, "mapLegendMin": 0, "mapLegendNumber": 9, "mapLegendRangeType": "quantize", "mapLegendCustomRange": [], "flowMapConfig": {"lineConfig": {"mapLineAnimate": true, "mapLineType": "arc", "mapLineWidth": 1, "mapLineAnimateDuration": 3, "mapLineGradient": false, "mapLineSourceColor": "#146C94", "mapLineTargetColor": "#576CBC", "alpha": 100}, "pointConfig": {"text": {"color": "#146C94", "fontSize": 10}, "point": {"color": "#146C94", "size": 4, "animate": false, "speed": 0.01}}}, "wordCloudAxisValueRange": {"auto": true, "min": 0, "max": 0}, "bullet": {"bar": {"ranges": {"fill": ["rgba(0,128,255,0.3)"], "size": 20, "showType": "dynamic", "fixedRangeNumber": 3, "symbol": "circle", "symbolSize": 4}, "measures": {"fill": ["rgba(0,128,255,1)"], "size": 15, "symbol": "circle", "symbolSize": 4}, "target": {"fill": "#000000", "size": 20, "showType": "dynamic", "value": 0, "symbol": "line", "symbolSize": 4}}}, "liquidShowBorder": false, "liquidBorderWidth": 4, "liquidBorderDistance": 8}, "label": {"show": true, "childrenShow": true, "position": "outer", "color": "#000000", "fontSize": 12, "formatter": "", "labelLine": {"show": true}, "labelFormatter": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "reserveDecimalCount": 2, "labelShadow": false, "labelBgColor": "", "labelShadowColor": "", "quotaLabelFormatter": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "showDimension": true, "showQuota": false, "showProportion": true, "seriesLabelFormatter": [], "conversionTag": {"show": false, "precision": 2, "text": "转化率"}, "showTotal": false, "totalFontSize": 12, "totalColor": "#FFF", "totalFormatter": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "showStackQuota": false, "fullDisplay": false, "proportionSeriesFormatter": {"show": false, "color": "#000000", "fontSize": 12, "formatterCfg": {"decimalCount": 2}}}, "tooltip": {"show": true, "trigger": "item", "confine": true, "fontSize": 12, "color": "#000000", "tooltipFormatter": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "backgroundColor": "#FFFFFF", "seriesTooltipFormatter": [{"id": "{{ YAXIS_FIELD_ID }}", "datasourceId": "{{ DATASOURCE_ID }}", "datasetTableId": "{{ DATASET_TABLE_ID }}", "datasetGroupId": "{{ DATASET_GROUP_ID }}", "chartId": null, "originName": "access_count", "name": "访问次数", "dbFieldName": null, "description": "访问次数", "dataeaseName": "{{ YAXIS_DE_NAME }}", "groupType": "q", "type": "INT", "precision": null, "scale": null, "deType": 2, "deExtractType": 2, "extField": 0, "checked": true, "columnIndex": null, "lastSyncTime": null, "dateFormat": null, "dateFormatType": null, "fieldShortName": "{{ YAXIS_DE_NAME }}", "groupList": [], "otherGroup": null, "desensitized": false, "orderChecked": false, "params": [], "summary": "sum", "sort": "none", "dateStyle": "y_M_d", "datePattern": "date_sub", "dateShowFormat": "y_M_d", "chartType": "bar", "compareCalc": {"type": "none", "resultData": "percent", "field": null, "custom": null}, "logic": null, "filterType": null, "index": null, "formatterCfg": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "chartShowName": null, "filter": [], "customSort": null, "busiType": null, "hide": false, "field": null, "agg": false, "seriesId": "{{ YAXIS_FIELD_ID }}-yAxis", "show": true, "axisType": "yAxis"}, {"id": "{{ YAXIS2_FIELD_ID }}", "datasourceId": "{{ DATASOURCE_ID }}", "datasetTableId": "{{ DATASET_TABLE_ID }}", "datasetGroupId": "{{ DATASET_GROUP_ID }}", "chartId": null, "originName": "page_views", "name": "浏览量", "dbFieldName": null, "description": "浏览量", "dataeaseName": "{{ YAXIS2_DE_NAME }}", "groupType": "q", "type": "INT", "precision": null, "scale": null, "deType": 2, "deExtractType": 2, "extField": 0, "checked": true, "columnIndex": null, "lastSyncTime": null, "dateFormat": null, "dateFormatType": null, "fieldShortName": "{{ YAXIS2_DE_NAME }}", "groupList": [], "otherGroup": null, "desensitized": false, "orderChecked": false, "params": [], "summary": "sum", "sort": "none", "dateStyle": "y_M_d", "datePattern": "date_sub", "dateShowFormat": "y_M_d", "chartType": "bar", "compareCalc": {"type": "none", "resultData": "percent", "field": null, "custom": null}, "logic": null, "filterType": null, "index": null, "formatterCfg": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "chartShowName": null, "filter": [], "customSort": null, "busiType": null, "hide": false, "field": null, "agg": false, "seriesId": "{{ YAXIS2_FIELD_ID }}", "show": false}, {"id": "-1", "datasourceId": null, "datasetTableId": null, "datasetGroupId": "{{ DATASET_GROUP_ID }}", "chartId": null, "originName": "*", "name": "记录数*", "dbFieldName": null, "description": null, "dataeaseName": "*", "groupType": "q", "type": "INT", "precision": null, "scale": null, "deType": 2, "deExtractType": null, "extField": 1, "checked": true, "columnIndex": 999, "lastSyncTime": null, "dateFormat": null, "dateFormatType": null, "fieldShortName": null, "groupList": null, "otherGroup": null, "desensitized": null, "orderChecked": null, "params": null, "summary": "count", "sort": "none", "dateStyle": "y_M_d", "datePattern": "date_sub", "dateShowFormat": "y_M_d", "chartType": "bar", "compareCalc": {"type": "none", "resultData": "percent", "field": null, "custom": null}, "logic": null, "filterType": null, "index": null, "formatterCfg": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}, "chartShowName": null, "filter": [], "customSort": null, "busiType": null, "hide": false, "field": null, "agg": false, "seriesId": "-1", "show": false}], "carousel": {"enable": false, "stayTime": 3, "intervalTime": 1}}, "tableTotal": {"row": {"showGrandTotals": true, "showSubTotals": true, "reverseLayout": false, "reverseSubLayout": false, "label": "总计", "subLabel": "小计", "subTotalsDimensions": [], "subTotalsDimensionsNew": true, "calcTotals": {"aggregation": "SUM", "cfg": []}, "calcSubTotals": {"aggregation": "SUM", "cfg": []}, "totalSort": "none", "totalSortField": ""}, "col": {"showGrandTotals": true, "showSubTotals": true, "reverseLayout": false, "reverseSubLayout": false, "label": "总计", "subLabel": "小计", "subTotalsDimensions": [], "calcTotals": {"aggregation": "SUM", "cfg": []}, "calcSubTotals": {"aggregation": "SUM", "cfg": []}, "totalSort": "none", "totalSortField": ""}}, "tableHeader": {"indexLabel": "序号", "showIndex": false, "tableHeaderAlign": "left", "tableHeaderCornerAlign": "left", "tableHeaderColAlign": "left", "tableHeaderBgColor": "#1E90FF", "tableHeaderCornerBgColor": "#1E90FF", "tableHeaderColBgColor": "#1E90FF", "tableHeaderFontColor": "#000000", "tableHeaderCornerFontColor": "#000000", "tableHeaderColFontColor": "#000000", "tableTitleFontSize": 12, "tableTitleCornerFontSize": 12, "tableTitleColFontSize": 12, "tableTitleHeight": 36, "tableHeaderSort": false, "showColTooltip": false, "showRowTooltip": false, "showTableHeader": true, "showHorizonBorder": true, "showVerticalBorder": true, "isItalic": false, "isCornerItalic": false, "isColItalic": false, "isBolder": true, "isCornerBolder": true, "isColBolder": true, "headerGroup": false, "headerGroupConfig": {"columns": [], "meta": []}, "rowHeaderFreeze": true, "alignConfig": []}, "tableCell": {"tableFontColor": "#000000", "tableItemAlign": "right", "tableItemBgColor": "#FFFFFF", "tableItemFontSize": 12, "tableItemHeight": 36, "enableTableCrossBG": false, "tableItemSubBgColor": "#1E90FF", "showTooltip": false, "showHorizonBorder": true, "showVerticalBorder": true, "isItalic": false, "isBolder": false, "tableFreeze": false, "tableColumnFreezeHead": 0, "tableRowFreezeHead": 0, "mergeCells": true, "alignConfig": []}, "indicator": {"show": true, "fontSize": 20, "color": "#5470C6ff", "hPosition": "center", "vPosition": "center", "isItalic": false, "isBolder": true, "fontFamily": "Microsoft YaHei", "letterSpace": 0, "fontShadow": false, "backgroundColor": "", "suffixEnable": true, "suffix": "", "suffixFontSize": 14, "suffixColor": "#5470C6ff", "suffixIsItalic": false, "suffixIsBolder": true, "suffixFontFamily": "Microsoft YaHei", "suffixLetterSpace": 0, "suffixFontShadow": false}, "indicatorName": {"show": true, "fontSize": 18, "color": "#ffffffff", "isItalic": false, "isBolder": true, "fontFamily": "Microsoft YaHei", "letterSpace": 0, "fontShadow": false, "nameValueSpacing": 0, "namePosition": "bottom"}, "map": {"id": "", "level": "world"}}, "customStyle": {"text": {"show": true, "fontSize": 16, "hPosition": "left", "vPosition": "top", "isItalic": false, "isBolder": true, "remarkShow": false, "remark": "", "fontFamily": "PingFang", "letterSpace": "0", "fontShadow": false, "color": "#000000", "remarkBackgroundColor": "#ffffff"}, "legend": {"show": false, "hPosition": "center", "vPosition": "bottom", "orient": "horizontal", "icon": "circle", "color": "#000000", "fontSize": 12, "size": 4, "showRange": true, "sort": "none", "customSort": []}, "xAxis": {"show": true, "position": "bottom", "nameShow": false, "name": "", "color": "#000000", "fontSize": 12, "axisLabel": {"show": true, "color": "#000000", "fontSize": 12, "rotate": 0, "formatter": "{value}", "lengthLimit": 10}, "axisLine": {"show": true, "lineStyle": {"color": "#cccccc", "width": 1, "style": "solid"}}, "splitLine": {"show": false, "lineStyle": {"color": "#CCCCCC", "width": 1, "style": "solid"}}, "axisValue": {"auto": true, "min": 10, "max": 100, "split": 10, "splitCount": 10}, "axisLabelFormatter": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}}, "yAxis": {"show": true, "position": "left", "nameShow": false, "name": "", "color": "#000000", "fontSize": 12, "axisLabel": {"show": true, "color": "#000000", "fontSize": 12, "rotate": 0, "formatter": "{value}", "lengthLimit": 10}, "axisLine": {"show": false, "lineStyle": {"color": "#cccccc", "width": 1, "style": "solid"}}, "splitLine": {"show": true, "lineStyle": {"color": "#CCCCCC", "width": 1, "style": "solid"}}, "axisValue": {"auto": true, "min": 10, "max": 100, "split": 10, "splitCount": 10}, "axisLabelFormatter": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}}, "yAxisExt": {"show": true, "position": "right", "name": "", "color": "#000000", "fontSize": 12, "axisLabel": {"show": true, "color": "#000000", "fontSize": 12, "rotate": 0, "formatter": "{value}"}, "axisLine": {"show": false, "lineStyle": {"color": "#cccccc", "width": 1, "style": "solid"}}, "splitLine": {"show": false, "lineStyle": {"color": "#CCCCCC", "width": 1, "style": "solid"}}, "axisValue": {"auto": true, "min": 10, "max": 100, "split": 10, "splitCount": 10}, "axisLabelFormatter": {"type": "auto", "unitLanguage": "ch", "unit": 1, "suffix": "", "decimalCount": 2, "thousandSeparator": true}}, "misc": {"showName": false, "color": "#000000", "fontSize": 12, "axisColor": "#999", "splitNumber": 5, "axisLine": {"show": true, "lineStyle": {"color": "#CCCCCC", "width": 1, "type": "solid"}}, "axisTick": {"show": false, "length": 5, "lineStyle": {"color": "#000000", "width": 1, "type": "solid"}}, "axisLabel": {"show": false, "rotate": 0, "margin": 8, "color": "#000000", "fontSize": "12", "formatter": "{value}"}, "splitLine": {"show": true, "lineStyle": {"color": "#CCCCCC", "width": 1, "type": "solid"}}, "splitArea": {"show": true}, "axisValue": {"auto": true, "min": 10, "max": 100, "split": 10, "splitCount": 10}}}, "senior": {"functionCfg": {"sliderShow": false, "sliderRange": [0, 10], "sliderBg": "#FFFFFF", "sliderFillBg": "#BCD6F1", "sliderTextColor": "#999999", "emptyDataStrategy": "breakLine", "emptyDataCustomValue": "", "emptyDataFieldCtrl": []}, "assistLineCfg": {"enable": false, "assistLine": []}, "threshold": {"enable": false, "gaugeThreshold": "", "liquidThreshold": "", "labelThreshold": [], "tableThreshold": [], "textLabelThreshold": [], "lineLabelThreshold": []}, "scrollCfg": {"open": false, "row": 1, "interval": 2000, "step": 50}, "areaMapping": {}, "bubbleCfg": {"enable": false, "speed": 1, "rings": 1, "type": "wave"}}, "flowMapStartName": [], "flowMapEndName": [], "isPlugin": false, "plugin": {"isPlugin": false}, "calParams": [], "dataFrom": "calc", "chartExtRequest": {"user": "1", "filter": [], "drill": [], "resultCount": 1000, "resultMode": "all"}}}, "appData": null, "dataState": "ready", "optType": null, "id": "{{ SCENE_ID }}", "name": "新建仪表板102", "pid": "0", "type": "dashboard", "status": 0, "selfWatermarkStatus": true, "watermarkInfo": null, "mobileLayout": false, "contentId": "{{ VIEW_ID }}", "weight": 9, "checkVersion": "2.10.20"} \ No newline at end of file diff --git a/templates/chart_table_info/params.json b/templates/chart_table_info/params.json new file mode 100644 index 0000000..9b71428 --- /dev/null +++ b/templates/chart_table_info/params.json @@ -0,0 +1,22 @@ +{ + "view": { + "VIEW_ID": "7448293416766541824", + "CONTENT_ID": "7449036212012060672", + "SCENE_ID": "1240025405293989888" + }, + "dataset": { + "DATASET_GROUP_ID": "1178801986976485376", + "DATASOURCE_ID": "1166370337189924864", + "DATASET_TABLE_ID": "7387069976563159040" + }, + "fields": { + "xAxis": { + "XAXIS_FIELD_ID": "1761214724097", + "XAXIS_DE_NAME": "f_3319f6ba3e484e93" + }, + "xAxis2": { + "XAXIS2_FIELD_ID": "1761214724096", + "XAXIS2_DE_NAME": "f_97666027e11d7dc5" + } + } +} diff --git a/templates/chart_table_info/template.j2 b/templates/chart_table_info/template.j2 new file mode 100644 index 0000000..3c3f2d8 --- /dev/null +++ b/templates/chart_table_info/template.j2 @@ -0,0 +1,902 @@ +{ + "canvasStyleData": "{\"width\":1920,\"height\":1080,\"refreshBrowserEnable\":false,\"refreshBrowserUnit\":\"minute\",\"refreshBrowserTime\":5,\"refreshViewEnable\":false,\"refreshViewLoading\":true,\"refreshUnit\":\"minute\",\"refreshTime\":5,\"popupAvailable\":true,\"popupButtonAvailable\":true,\"suspensionButtonAvailable\":false,\"screenAdaptor\":\"widthFirst\",\"dashboardAdaptor\":\"keepHeightAndWidth\",\"scale\":33,\"scaleWidth\":24,\"scaleHeight\":24,\"backgroundColorSelect\":true,\"backgroundImageEnable\":false,\"backgroundType\":\"backgroundColor\",\"background\":\"\",\"openCommonStyle\":true,\"opacity\":1,\"fontSize\":14,\"fontFamily\":\"PingFang\",\"themeId\":\"10001\",\"color\":\"#000000\",\"backgroundColor\":\"#f5f6f7\",\"dashboard\":{\"gap\":\"yes\",\"gapSize\":5,\"gapMode\":\"middle\",\"showGrid\":false,\"matrixBase\":4,\"resultMode\":\"all\",\"resultCount\":1000,\"themeColor\":\"light\",\"mobileSetting\":{\"customSetting\":false,\"imageUrl\":null,\"backgroundType\":\"image\",\"color\":\"#000\"}},\"component\":{\"chartTitle\":{\"show\":true,\"fontSize\":16,\"hPosition\":\"left\",\"vPosition\":\"top\",\"isItalic\":false,\"isBolder\":true,\"remarkShow\":false,\"remark\":\"\",\"fontFamily\":\"\",\"letterSpace\":\"0\",\"fontShadow\":false,\"color\":\"#000000\",\"remarkBackgroundColor\":\"#ffffff\"},\"chartColor\":{\"basicStyle\":{\"colorScheme\":\"default\",\"colors\":[\"#1E90FF\",\"#90EE90\",\"#00CED1\",\"#E2BD84\",\"#7A90E0\",\"#3BA272\",\"#2BE7FF\",\"#0A8ADA\",\"#FFD700\"],\"alpha\":100,\"gradient\":false,\"mapStyle\":\"normal\",\"areaBaseColor\":\"#FFFFFF\",\"areaBorderColor\":\"#303133\",\"gaugeStyle\":\"default\",\"tableBorderColor\":\"#E6E7E4\",\"tableScrollBarColor\":\"rgba(0, 0, 0, 0.15)\",\"zoomButtonColor\":\"#aaa\",\"zoomBackground\":\"#fff\"},\"misc\":{\"flowMapConfig\":{\"lineConfig\":{\"mapLineAnimate\":true,\"mapLineGradient\":false,\"mapLineSourceColor\":\"#146C94\",\"mapLineTargetColor\":\"#576CBC\"}},\"nameFontColor\":\"#000000\",\"valueFontColor\":\"#5470c6\"},\"tableHeader\":{\"tableHeaderBgColor\":\"#1E90FF\",\"tableHeaderCornerBgColor\":\"#1E90FF\",\"tableHeaderColBgColor\":\"#1E90FF\",\"tableHeaderFontColor\":\"#000000\",\"tableHeaderCornerFontColor\":\"#000000\",\"tableHeaderColFontColor\":\"#000000\"},\"tableCell\":{\"tableItemBgColor\":\"#FFFFFF\",\"tableFontColor\":\"#000000\",\"tableItemSubBgColor\":\"#1E90FF\"}},\"chartCommonStyle\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"filterStyle\":{\"layout\":\"horizontal\",\"titleLayout\":\"left\",\"labelColor\":\"#1f2329\",\"titleColor\":\"#1f2329\",\"color\":\"#1f2329\",\"borderColor\":\"#bbbfc4\",\"text\":\"#1f2329\",\"bgColor\":\"#FFFFFF\"},\"tabStyle\":{\"headPosition\":\"left\",\"headFontColor\":\"#000000\",\"headFontActiveColor\":\"#000000\",\"headBorderColor\":\"#ffffff\",\"headBorderActiveColor\":\"#ffffff\"},\"seniorStyleSetting\":{\"linkageIconColor\":\"#a6a6a6\",\"drillLayerColor\":\"#a6a6a6\",\"pagerColor\":\"#a6a6a6\"},\"formatterItem\":{\"type\":\"auto\",\"unitLanguage\":\"ch\",\"unit\":1,\"suffix\":\"\",\"decimalCount\":2,\"thousandSeparator\":true}},\"dialogBackgroundColor\":\"rgba(255, 255, 255, 1)\",\"dialogButton\":\"#020408\"}", + "componentData": "[{\"animations\":[],\"canvasId\":\"canvas-main\",\"events\":{\"checked\":false,\"showTips\":false,\"type\":\"jump\",\"typeList\":[{\"key\":\"jump\",\"label\":\"jump\"},{\"key\":\"download\",\"label\":\"download\"},{\"key\":\"share\",\"label\":\"share\"},{\"key\":\"fullScreen\",\"label\":\"fullScreen\"},{\"key\":\"showHidden\",\"label\":\"showHidden\"},{\"key\":\"refreshDataV\",\"label\":\"refreshDataV\"},{\"key\":\"refreshView\",\"label\":\"refreshView\"}],\"jump\":{\"value\":\"https://\",\"type\":\"_blank\"},\"download\":{\"value\":true},\"share\":{\"value\":true,\"target\":\"all\"}},\"carousel\":{\"enable\":false,\"time\":10},\"multiDimensional\":{\"enable\":false,\"x\":0,\"y\":0,\"z\":0},\"groupStyle\":{},\"isLock\":false,\"maintainRadio\":false,\"aspectRatio\":1,\"isShow\":true,\"dashboardHidden\":false,\"category\":\"base\",\"dragging\":false,\"resizing\":false,\"collapseName\":[\"position\",\"background\",\"style\",\"picture\",\"frameLinks\",\"videoLinks\",\"streamLinks\",\"carouselInfo\",\"events\",\"decoration_style\"],\"linkage\":{\"duration\":0,\"data\":[{\"id\":\"\",\"label\":\"\",\"event\":\"\",\"style\":[{\"key\":\"\",\"value\":\"\"}]}]},\"component\":\"UserView\",\"name\":\"明细表\",\"label\":\"明细表\",\"propValue\":{\"textValue\":\"\",\"urlList\":[]},\"icon\":\"bar\",\"innerType\":\"table-info\",\"editing\":false,\"canvasActive\":false,\"actionSelection\":{\"linkageActive\":\"custom\"},\"x\":1,\"y\":1,\"sizeX\":36,\"sizeY\":14,\"style\":{\"rotate\":0,\"opacity\":1,\"borderActive\":false,\"borderWidth\":1,\"borderRadius\":5,\"borderStyle\":\"solid\",\"borderColor\":\"rgba(204, 204, 204, 1)\",\"adaptation\":\"adaptation\",\"width\":744,\"height\":139.22222222222223,\"left\":0,\"top\":0},\"matrixStyle\":{},\"commonBackground\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"state\":\"ready\",\"render\":\"antv\",\"isPlugin\":false,\"id\":\"{{ VIEW_ID }}\",\"_dragId\":0,\"linkageFilters\":[],\"expand\":false,\"resizeInnerKeep\":false}]", + "canvasViewInfo": { + "{{ VIEW_ID }}": { + "id": "{{ VIEW_ID }}", + "title": "明细表", + "sceneId": "{{ SCENE_ID }}", + "tableId": "{{ DATASET_GROUP_ID }}", + "type": "table-info", + "render": "antv", + "resultCount": 1000, + "resultMode": "custom", + "extStack": [], + "extBubble": [], + "extLabel": [], + "extTooltip": [], + "customAttr": { + "basicStyle": { + "alpha": 100, + "tableBorderColor": "#E6E7E4", + "tableScrollBarColor": "rgba(0, 0, 0, 0.15)", + "tableColumnMode": "adapt", + "tableColumnWidth": 100, + "tableFieldWidth": [], + "tablePageMode": "page", + "tablePageStyle": "simple", + "tablePageSize": 20, + "gaugeStyle": "default", + "colorScheme": "default", + "colors": [ + "#1E90FF", + "#90EE90", + "#00CED1", + "#E2BD84", + "#7A90E0", + "#3BA272", + "#2BE7FF", + "#0A8ADA", + "#FFD700" + ], + "mapVendor": "amap", + "gradient": false, + "lineWidth": 2, + "lineSymbol": "circle", + "lineSymbolSize": 4, + "lineSmooth": true, + "barDefault": true, + "radiusColumnBar": "rightAngle", + "columnBarRightAngleRadius": 20, + "columnWidthRatio": 60, + "barWidth": 40, + "barGap": 0.4, + "lineType": "solid", + "scatterSymbol": "circle", + "scatterSymbolSize": 8, + "radarShape": "polygon", + "mapStyle": "normal", + "heatMapType": "heatmap", + "heatMapIntensity": 2, + "heatMapRadius": 20, + "areaBorderColor": "#303133", + "areaBaseColor": "#FFFFFF", + "mapSymbolOpacity": 0.7, + "mapSymbolStrokeWidth": 2, + "mapSymbol": "circle", + "mapSymbolSize": 6, + "radius": 80, + "innerRadius": 60, + "showZoom": true, + "zoomButtonColor": "#aaa", + "zoomBackground": "#fff", + "tableLayoutMode": "grid", + "defaultExpandLevel": 1, + "calcTopN": false, + "topN": 5, + "topNLabel": "其他", + "gaugeAxisLine": true, + "gaugePercentLabel": true, + "showSummary": false, + "summaryLabel": "总计", + "seriesColor": [], + "layout": "horizontal", + "mapSymbolSizeMin": 4, + "mapSymbolSizeMax": 30, + "showLabel": true, + "mapStyleUrl": "", + "autoFit": true, + "mapCenter": { + "longitude": 117.232, + "latitude": 39.354 + }, + "zoomLevel": 7, + "customIcon": "", + "showHoverStyle": true, + "autoWrap": false, + "maxLines": 3, + "radarShowPoint": true, + "radarPointSize": 4, + "radarAreaColor": true, + "circleBorderColor": "#fff", + "circleBorderWidth": 0, + "circlePadding": 0, + "quotaPosition": "col", + "quotaColLabel": "数值", + "tableRowHeaderMode": "adapt", + "tableRowHeaderWidth": 120, + "tableRowHeaderWidthPercent": 20 + }, + "misc": { + "pieInnerRadius": 0, + "pieOuterRadius": 80, + "radarShape": "polygon", + "radarSize": 80, + "gaugeMinType": "fix", + "gaugeMinField": { + "id": "", + "summary": "" + }, + "gaugeMin": 0, + "gaugeMaxType": "dynamic", + "gaugeMaxField": { + "id": "", + "summary": "" + }, + "gaugeStartAngle": 225, + "gaugeEndAngle": -45, + "nameFontSize": 18, + "valueFontSize": 18, + "nameValueSpace": 10, + "valueFontColor": "#5470c6", + "valueFontFamily": "Microsoft YaHei", + "valueFontIsBolder": false, + "valueFontIsItalic": false, + "valueLetterSpace": 0, + "valueFontShadow": false, + "showName": true, + "nameFontColor": "#000000", + "nameFontFamily": "Microsoft YaHei", + "nameFontIsBolder": false, + "nameFontIsItalic": false, + "nameLetterSpace": "0", + "nameFontShadow": false, + "treemapWidth": 80, + "treemapHeight": 80, + "liquidMaxType": "dynamic", + "liquidMaxField": { + "id": "", + "summary": "" + }, + "liquidSize": 80, + "liquidShape": "circle", + "hPosition": "center", + "vPosition": "center", + "mapPitch": 0, + "wordSizeRange": [ + 8, + 32 + ], + "wordSpacing": 6, + "mapAutoLegend": true, + "mapLegendMax": 0, + "mapLegendMin": 0, + "mapLegendNumber": 9, + "mapLegendRangeType": "quantize", + "mapLegendCustomRange": [], + "flowMapConfig": { + "lineConfig": { + "mapLineAnimate": true, + "mapLineType": "arc", + "mapLineWidth": 1, + "mapLineAnimateDuration": 3, + "mapLineGradient": false, + "mapLineSourceColor": "#146C94", + "mapLineTargetColor": "#576CBC", + "alpha": 100 + }, + "pointConfig": { + "text": { + "color": "#146C94", + "fontSize": 10 + }, + "point": { + "color": "#146C94", + "size": 4, + "animate": false, + "speed": 0.01 + } + } + }, + "wordCloudAxisValueRange": { + "auto": true, + "min": 0, + "max": 0 + }, + "bullet": { + "bar": { + "ranges": { + "fill": [ + "rgba(0,128,255,0.3)" + ], + "size": 20, + "showType": "dynamic", + "fixedRangeNumber": 3, + "symbol": "circle", + "symbolSize": 4 + }, + "measures": { + "fill": [ + "rgba(0,128,255,1)" + ], + "size": 15, + "symbol": "circle", + "symbolSize": 4 + }, + "target": { + "fill": "#000000", + "size": 20, + "showType": "dynamic", + "value": 0, + "symbol": "line", + "symbolSize": 4 + } + } + }, + "liquidShowBorder": false, + "liquidBorderWidth": 4, + "liquidBorderDistance": 8 + }, + "label": { + "show": false, + "childrenShow": true, + "position": "top", + "color": "#000000", + "fontSize": 12, + "formatter": "", + "labelLine": { + "show": true + }, + "labelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "reserveDecimalCount": 2, + "labelShadow": false, + "labelBgColor": "", + "labelShadowColor": "", + "quotaLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "showDimension": true, + "showQuota": false, + "showProportion": true, + "seriesLabelFormatter": [], + "conversionTag": { + "show": false, + "precision": 2, + "text": "转化率" + }, + "showTotal": false, + "totalFontSize": 12, + "totalColor": "#FFF", + "totalFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "showStackQuota": false, + "fullDisplay": false, + "proportionSeriesFormatter": { + "show": false, + "color": "#000000", + "fontSize": 12, + "formatterCfg": { + "decimalCount": 2 + } + } + }, + "tooltip": { + "show": true, + "trigger": "item", + "confine": true, + "fontSize": 12, + "color": "#000000", + "tooltipFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "backgroundColor": "#FFFFFF", + "seriesTooltipFormatter": [], + "carousel": { + "enable": false, + "stayTime": 3, + "intervalTime": 1 + } + }, + "tableTotal": { + "row": { + "showGrandTotals": true, + "showSubTotals": true, + "reverseLayout": false, + "reverseSubLayout": false, + "label": "总计", + "subLabel": "小计", + "subTotalsDimensions": [], + "subTotalsDimensionsNew": true, + "calcTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "calcSubTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "totalSort": "none", + "totalSortField": "" + }, + "col": { + "showGrandTotals": true, + "showSubTotals": true, + "reverseLayout": false, + "reverseSubLayout": false, + "label": "总计", + "subLabel": "小计", + "subTotalsDimensions": [], + "calcTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "calcSubTotals": { + "aggregation": "SUM", + "cfg": [] + }, + "totalSort": "none", + "totalSortField": "" + } + }, + "tableHeader": { + "indexLabel": "序号", + "showIndex": false, + "tableHeaderAlign": "left", + "tableHeaderCornerAlign": "left", + "tableHeaderColAlign": "left", + "tableHeaderBgColor": "#1E90FF", + "tableHeaderCornerBgColor": "#1E90FF", + "tableHeaderColBgColor": "#1E90FF", + "tableHeaderFontColor": "#000000", + "tableHeaderCornerFontColor": "#000000", + "tableHeaderColFontColor": "#000000", + "tableTitleFontSize": 12, + "tableTitleCornerFontSize": 12, + "tableTitleColFontSize": 12, + "tableTitleHeight": 36, + "tableHeaderSort": false, + "showColTooltip": false, + "showRowTooltip": false, + "showTableHeader": true, + "showHorizonBorder": true, + "showVerticalBorder": true, + "isItalic": false, + "isCornerItalic": false, + "isColItalic": false, + "isBolder": true, + "isCornerBolder": true, + "isColBolder": true, + "headerGroup": false, + "headerGroupConfig": { + "columns": [], + "meta": [] + }, + "rowHeaderFreeze": true, + "alignConfig": [] + }, + "tableCell": { + "tableFontColor": "#000000", + "tableItemAlign": "right", + "tableItemBgColor": "#FFFFFF", + "tableItemFontSize": 12, + "tableItemHeight": 36, + "enableTableCrossBG": false, + "tableItemSubBgColor": "#1E90FF", + "showTooltip": false, + "showHorizonBorder": true, + "showVerticalBorder": true, + "isItalic": false, + "isBolder": false, + "tableFreeze": false, + "tableColumnFreezeHead": 0, + "tableRowFreezeHead": 0, + "mergeCells": true, + "alignConfig": [] + }, + "indicator": { + "show": true, + "fontSize": 20, + "color": "#5470C6ff", + "hPosition": "center", + "vPosition": "center", + "isItalic": false, + "isBolder": true, + "fontFamily": "Microsoft YaHei", + "letterSpace": 0, + "fontShadow": false, + "backgroundColor": "", + "suffixEnable": true, + "suffix": "", + "suffixFontSize": 14, + "suffixColor": "#5470C6ff", + "suffixIsItalic": false, + "suffixIsBolder": true, + "suffixFontFamily": "Microsoft YaHei", + "suffixLetterSpace": 0, + "suffixFontShadow": false + }, + "indicatorName": { + "show": true, + "fontSize": 18, + "color": "#ffffffff", + "isItalic": false, + "isBolder": true, + "fontFamily": "Microsoft YaHei", + "letterSpace": 0, + "fontShadow": false, + "nameValueSpacing": 0, + "namePosition": "bottom" + }, + "map": { + "id": "", + "level": "world" + } + }, + "customAttrMobile": null, + "customStyle": { + "text": { + "show": true, + "fontSize": 16, + "hPosition": "left", + "vPosition": "top", + "isItalic": false, + "isBolder": true, + "remarkShow": false, + "remark": "", + "fontFamily": "PingFang", + "letterSpace": "0", + "fontShadow": false, + "color": "#000000", + "remarkBackgroundColor": "#ffffff" + }, + "legend": { + "show": true, + "hPosition": "center", + "vPosition": "bottom", + "orient": "horizontal", + "icon": "circle", + "color": "#000000", + "fontSize": 12, + "size": 4, + "showRange": true, + "sort": "none", + "customSort": [] + }, + "xAxis": { + "show": true, + "position": "bottom", + "nameShow": false, + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}", + "lengthLimit": 10 + }, + "axisLine": { + "show": true, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "yAxis": { + "show": true, + "position": "left", + "nameShow": false, + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}", + "lengthLimit": 10 + }, + "axisLine": { + "show": false, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "yAxisExt": { + "show": true, + "position": "right", + "name": "", + "color": "#000000", + "fontSize": 12, + "axisLabel": { + "show": true, + "color": "#000000", + "fontSize": 12, + "rotate": 0, + "formatter": "{value}" + }, + "axisLine": { + "show": false, + "lineStyle": { + "color": "#cccccc", + "width": 1, + "style": "solid" + } + }, + "splitLine": { + "show": false, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "style": "solid" + } + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + }, + "axisLabelFormatter": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + } + }, + "misc": { + "showName": false, + "color": "#000000", + "fontSize": 12, + "axisColor": "#999", + "splitNumber": 5, + "axisLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "type": "solid" + } + }, + "axisTick": { + "show": false, + "length": 5, + "lineStyle": { + "color": "#000000", + "width": 1, + "type": "solid" + } + }, + "axisLabel": { + "show": false, + "rotate": 0, + "margin": 8, + "color": "#000000", + "fontSize": "12", + "formatter": "{value}" + }, + "splitLine": { + "show": true, + "lineStyle": { + "color": "#CCCCCC", + "width": 1, + "type": "solid" + } + }, + "splitArea": { + "show": true + }, + "axisValue": { + "auto": true, + "min": 10, + "max": 100, + "split": 10, + "splitCount": 10 + } + } + }, + "customStyleMobile": null, + "customFilter": { + "logic": null, + "items": null + }, + "drillFields": [], + "senior": { + "functionCfg": { + "sliderShow": false, + "sliderRange": [ + 0, + 10 + ], + "sliderBg": "#FFFFFF", + "sliderFillBg": "#BCD6F1", + "sliderTextColor": "#999999", + "emptyDataStrategy": "breakLine", + "emptyDataCustomValue": "", + "emptyDataFieldCtrl": [] + }, + "assistLineCfg": { + "enable": false, + "assistLine": [] + }, + "threshold": { + "enable": false, + "gaugeThreshold": "", + "liquidThreshold": "", + "labelThreshold": [], + "tableThreshold": [], + "textLabelThreshold": [], + "lineLabelThreshold": [] + }, + "scrollCfg": { + "open": false, + "row": 1, + "interval": 2000, + "step": 50 + }, + "areaMapping": {}, + "bubbleCfg": { + "enable": false, + "speed": 1, + "rings": 1, + "type": "wave" + } + }, + "createBy": null, + "createTime": null, + "updateTime": null, + "snapshot": null, + "stylePriority": "panel", + "chartType": "private", + "isPlugin": false, + "dataFrom": "calc", + "viewFields": [], + "refreshViewEnable": false, + "refreshUnit": "minute", + "refreshTime": 5, + "linkageActive": false, + "jumpActive": false, + "aggregate": null, + "flowMapStartName": [], + "flowMapEndName": [], + "calParams": [], + "extColor": null, + "sortPriority": [], + "data": null, + "privileges": null, + "isLeaf": null, + "pid": null, + "sql": null, + "drill": false, + "drillFilters": null, + "position": null, + "totalPage": 0, + "totalItems": 0, + "datasetMode": 0, + "datasourceType": null, + "chartExtRequest": null, + "isExcelExport": false, + "exportDatasetOriginData": false, + "cache": false, + "sourceTableId": null, + "downloadType": null, + "xAxis": [ + { + "id": "{{ XAXIS_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_platform", + "name": "访问平台", + "dbFieldName": null, + "description": "访问平台", + "dataeaseName": "{{ XAXIS_DE_NAME }}", + "groupType": "d", + "type": "VARCHAR", + "precision": null, + "scale": null, + "deType": 0, + "deExtractType": 0, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ XAXIS_DE_NAME }}", + "groupList": [], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [], + "summary": "count", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false + }, + { + "id": "{{ XAXIS2_FIELD_ID }}", + "datasourceId": "{{ DATASOURCE_ID }}", + "datasetTableId": "{{ DATASET_TABLE_ID }}", + "datasetGroupId": "{{ DATASET_GROUP_ID }}", + "chartId": null, + "originName": "access_count", + "name": "访问次数", + "dbFieldName": null, + "description": "访问次数", + "dataeaseName": "{{ XAXIS2_DE_NAME }}", + "groupType": "q", + "type": "INT", + "precision": null, + "scale": null, + "deType": 2, + "deExtractType": 2, + "extField": 0, + "checked": true, + "columnIndex": null, + "lastSyncTime": null, + "dateFormat": null, + "dateFormatType": null, + "fieldShortName": "{{ XAXIS2_DE_NAME }}", + "groupList": [], + "otherGroup": null, + "desensitized": false, + "orderChecked": false, + "params": [], + "summary": "sum", + "sort": "none", + "dateStyle": "y_M_d", + "datePattern": "date_sub", + "dateShowFormat": "y_M_d", + "chartType": "bar", + "compareCalc": { + "type": "none", + "resultData": "percent", + "field": null, + "custom": null + }, + "logic": null, + "filterType": null, + "index": null, + "formatterCfg": { + "type": "auto", + "unitLanguage": "ch", + "unit": 1, + "suffix": "", + "decimalCount": 2, + "thousandSeparator": true + }, + "chartShowName": null, + "filter": [], + "customSort": null, + "busiType": null, + "hide": false, + "field": null, + "agg": false + } + ], + "xAxisExt": [], + "yAxis": [], + "yAxisExt": [], + "pageInfo": { + "total": 1000, + "pageSize": 20, + "currentPage": 1 + } + } + }, + "appData": null, + "id": "{{ SCENE_ID }}", + "name": "单明细表", + "pid": "0", + "status": 1, + "selfWatermarkStatus": true, + "type": "dashboard", + "creatorName": "系统管理员", + "updateName": "系统管理员", + "createTime": 1775811560811, + "updateTime": 1775812302437, + "watermarkInfo": null, + "weight": 9, + "ext": 0, + "contentId": "{{ CONTENT_ID }}", + "mobileLayout": false, + "checkVersion": "2.10.20" +} diff --git a/templates/dashboard/base.json b/templates/dashboard/base.json new file mode 100644 index 0000000..bb20cf3 --- /dev/null +++ b/templates/dashboard/base.json @@ -0,0 +1,3 @@ +{ + "canvasStyleData": "{\"width\":1920,\"height\":1080,\"refreshBrowserEnable\":false,\"refreshBrowserUnit\":\"minute\",\"refreshBrowserTime\":5,\"refreshViewEnable\":false,\"refreshViewLoading\":true,\"refreshUnit\":\"minute\",\"refreshTime\":5,\"popupAvailable\":true,\"popupButtonAvailable\":true,\"suspensionButtonAvailable\":false,\"screenAdaptor\":\"widthFirst\",\"dashboardAdaptor\":\"keepHeightAndWidth\",\"scale\":17,\"scaleWidth\":60,\"scaleHeight\":60,\"backgroundColorSelect\":true,\"backgroundImageEnable\":false,\"backgroundType\":\"backgroundColor\",\"background\":\"\",\"openCommonStyle\":true,\"opacity\":1,\"fontSize\":14,\"fontFamily\":\"PingFang\",\"themeId\":\"10001\",\"color\":\"#000000\",\"backgroundColor\":\"rgba(245, 246, 247, 1)\",\"dashboard\":{\"gap\":\"yes\",\"gapSize\":5,\"gapMode\":\"middle\",\"showGrid\":false,\"matrixBase\":4,\"resultMode\":\"all\",\"resultCount\":1000,\"themeColor\":\"light\",\"mobileSetting\":{\"customSetting\":false,\"imageUrl\":null,\"backgroundType\":\"image\",\"color\":\"#000\"}},\"component\":{\"chartTitle\":{\"show\":true,\"fontSize\":16,\"hPosition\":\"left\",\"vPosition\":\"top\",\"isItalic\":false,\"isBolder\":true,\"remarkShow\":false,\"remark\":\"\",\"fontFamily\":\"\",\"letterSpace\":\"0\",\"fontShadow\":false,\"color\":\"#000000\",\"remarkBackgroundColor\":\"#ffffff\"},\"chartColor\":{\"basicStyle\":{\"colorScheme\":\"default\",\"colors\":[\"#1E90FF\",\"#90EE90\",\"#00CED1\",\"#E2BD84\",\"#7A90E0\",\"#3BA272\",\"#2BE7FF\",\"#0A8ADA\",\"#FFD700\"],\"alpha\":100,\"gradient\":false,\"mapStyle\":\"normal\",\"areaBaseColor\":\"#FFFFFF\",\"areaBorderColor\":\"#303133\",\"gaugeStyle\":\"default\",\"tableBorderColor\":\"rgba(230, 231, 228, 1)\",\"tableScrollBarColor\":\"rgba(0, 0, 0, 0.15)\",\"zoomButtonColor\":\"#aaa\",\"zoomBackground\":\"#fff\"},\"misc\":{\"flowMapConfig\":{\"lineConfig\":{\"mapLineAnimate\":true,\"mapLineGradient\":false,\"mapLineSourceColor\":\"#146C94\",\"mapLineTargetColor\":\"#576CBC\"}},\"nameFontColor\":\"#000000\",\"valueFontColor\":\"#5470c6\"},\"tableHeader\":{\"tableHeaderBgColor\":\"#1E90FF\",\"tableHeaderCornerBgColor\":\"#1E90FF\",\"tableHeaderColBgColor\":\"#1E90FF\",\"tableHeaderFontColor\":\"#000000\",\"tableHeaderCornerFontColor\":\"#000000\",\"tableHeaderColFontColor\":\"#000000\"},\"tableCell\":{\"tableItemBgColor\":\"rgba(255, 255, 255, 1)\",\"tableFontColor\":\"#000000\",\"tableItemSubBgColor\":\"#1E90FF\"}},\"chartCommonStyle\":{\"backgroundColorSelect\":true,\"backdropFilterEnable\":false,\"backgroundImageEnable\":false,\"backgroundType\":\"innerImage\",\"innerImage\":\"board/board_1.svg\",\"outerImage\":null,\"innerPadding\":{\"mode\":\"uniform\",\"top\":12},\"borderRadius\":{\"mode\":\"uniform\",\"topLeft\":0},\"backdropFilter\":4,\"backgroundColor\":\"rgba(255,255,255,1)\",\"innerImageColor\":\"rgba(16, 148, 229,1)\"},\"filterStyle\":{\"layout\":\"horizontal\",\"titleLayout\":\"left\",\"labelColor\":\"#1F2329\",\"titleColor\":\"#1F2329\",\"color\":\"#1f2329\",\"borderColor\":\"#BBBFC4\",\"text\":\"#1F2329\",\"bgColor\":\"#FFFFFF\"},\"tabStyle\":{\"headPosition\":\"left\",\"headFontColor\":\"#000000\",\"headFontActiveColor\":\"#000000\",\"headBorderColor\":\"#ffffff\",\"headBorderActiveColor\":\"#ffffff\"},\"seniorStyleSetting\":{\"linkageIconColor\":\"#A6A6A6\",\"drillLayerColor\":\"#A6A6A6\",\"pagerColor\":\"rgba(166, 166, 166, 1)\"},\"formatterItem\":{\"type\":\"auto\",\"unitLanguage\":\"ch\",\"unit\":1,\"suffix\":\"\",\"decimalCount\":2,\"thousandSeparator\":true}},\"dialogBackgroundColor\":\"rgba(255, 255, 255, 1)\",\"dialogButton\":\"#020408\"}" +} \ No newline at end of file