自然语言查询ERP数据并可视化

SKILL研究检索

原名 NW ERP SKILL · 标识 nw-erp-skill

研究检索 未标注权限 许可证不明确
安装这个技能

用自然语言查询ERP中的单据和报表数据,支持明细、汇总与聚合统计,结果可通过图表渲染为饼图、柱状图、折线图等可视化形式。

一句话选型结论

按资料字段整理

自然语言查询ERP数据并可视化是研究检索类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类研究检索 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_a7d36aaa/nw-erp-skill
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

研究检索
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_a7d36aaa仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量33.9 KBSKILL.md 原文大小,越大说明说明书写得越细

能做什么

0 项能力
上游给出的能力概览与适用场景
查询销售合同金额分析客户分布查看报表并出图

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
查资料并整理出处

让模型按主题查资料,并把引用来源整理成清单。

网页与文献要点提炼

把长文、论文、网页压成能直接看的要点。

竞品与行业汇总

按关键词汇总竞品动态与行业信息。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在研究检索场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
自然语言查询ERP数据并可视化当前 未标注权限 未标注 未标注 未标注 有
寻找技能 浏览器控制 GitHub 仓库 3120.0 万 Codex、Claude、Cursor 有
自我改进 Agent 接触敏感数据 ClawHub 市场 1646.4 万 OpenClaw 有
灵魂追踪 联网访问 GitHub 仓库 758.9 万 Codex、Claude、Cursor 有
自我完善 联网访问 ClawHub 市场 682.9 万 OpenClaw 有
微软代工厂 调用外部服务 GitHub 仓库 661.4 万 Codex、Claude、Cursor 有

SKILL.md 全文

33.9 KB
模型实际读到的说明书原文,来自技能仓库

ERP数据查询技能

你可以通过HTTP接口查询ERP系统中的业务单据数据。所有请求需要在请求头中携带 X-MCP-SECRET 参数进行鉴权,该密钥与个人ERP账号一一对应,代表个人角色,MCP会根据角色查询相应的数据。

环境变量

本技能依赖以下环境变量,每个用户必须配置自己的密钥:

  • ERP_BASE_URL:ERP系统访问地址(如 http://192.168.1.100:8080)
  • APP_SECRET:你的MCP访问密钥(从ERP个人资料页面"生成MCP密钥"按钮获取)

> ⚠️ 重要:首次使用前必须配置
>
> 如果 ERP_BASE_URL 或 APP_SECRET 未配置或为空,请停止执行,先引导用户完成配置:
>
> ### 配置步骤(引导用户操作)
>
> 1. 获取密钥:告诉用户登录 ERP 系统 → 个人资料页面 → 点击"生成MCP密钥"按钮 → 复制密钥
> 2. 配置方式(二选一):
>
> 方式一:在对话中直接告诉 AI(推荐手机用户)
>
> 用户说:"我的ERP地址是 xxx,密钥是 xxx",AI 自动将配置写入 ~/.codebuddy/settings.json 的 env 字段。
>
> 方式二:手动编辑配置文件
>
> 在 ~/.codebuddy/settings.json 中添加:
> json > { > "env": { > "ERP_BASE_URL": "https://你的ERP地址", > "APP_SECRET": "你的密钥" > } > } >
>
> 3. 验证配置:配置完成后,调用 list_bill_types 接口验证是否可用

所有 HTTP 请求中使用以下占位符(运行时自动从环境变量替换):
- 地址:${ERP_BASE_URL}
- 密钥:${APP_SECRET}

🔒 安全机制

ERP系统通过三层安全机制确保数据访问安全,用户只能访问自己权限范围内的数据。

第一层:身份认证(appsecret)

项目 说明
密钥生成 用户在ERP个人资料页面点击"生成MCP密钥"按钮生成,32位UUID
密钥绑定 appsecret与sys_org_person表中的个人账号一一对应
密钥唯一 每个人只有一个appsecret,重新生成后旧密钥立即失效
传输方式 通过HTTP请求头 X-MCP-SECRET 传递,不放在URL参数中
数据范围 appsecret → 个人账号 → 公司(pk_corp) + 部门(pk_dept) → LoginInfo

第二层:功能权限(菜单授权)

MCP不仅验证appsecret身份,还校验用户是否有该模块的菜单权限:

检查环节 实现 无权限时行为
列出单据类型 SysAuthService.hasAuth(pk_user, billtypecode) 该单据不出现在列表中
查询单据数据 SysAuthService.hasAuth(pk_user, billtypecode) 抛出异常"您没有[XX]模块的访问权限"
列出报表类型 SysAuthService.hasAuth(pk_user, nodecode) 该报表不出现在列表中
查询报表数据 SysAuthService.hasAuth(pk_user, nodecode) 抛出异常"您没有[XX]模块的访问权限"

示例:用户A有"销售合同"菜单权限但没有"采购订单"菜单权限 → list_bill_types只返回销售合同 → 直接调用采购订单查询会被拒绝。

> ⚠️ 模块级授权开关:即使功能权限校验通过,ERP管理员还需要在单据类型/报表模板中设置 if_authorize=Y(第三方授权),该模块才会对MCP开放。这是模块级开关,与用户级菜单权限是"且"关系——两者都满足才能访问。

第三层:数据权限(公司隔离)

即使通过了身份认证和功能权限,用户也只能看到自己公司的数据:

模块 数据权限实现 说明
单据 loadData() → QueryHelper.getDataPowerQueryString() 自动按用户pk_corp/pk_dept/pk_user过滤,支持数据权限模板配置
报表 SQL WHERE子句拼接 pk_corp='用户公司' 强制按当前用户公司过滤,防止跨公司查数据

示例:用户A属于公司X → 只能查到公司X的销售合同和报表数据 → 即使知道公司Y的单据ID也无法查询。

安全流程图

用户请求 (X-MCP-SECRET: appsecret)
    │
    ▼
┌─────────────────────────────────┐
│ 第一层:身份认证                  │
│ appsecret → sys_org_person       │
│ → 取 pk_user, pk_corp, pk_dept  │
│ → 设置 LoginInfo                 │
└───────────────┬─────────────────┘
                │ 认证失败 → 拒绝访问
                ▼
┌─────────────────────────────────┐
│ 第二层:功能权限                  │
│ SysAuthService.hasAuth(          │
│   pk_user, billtypecode/nodecode)│
│ + if_authorize=Y (模块授权开关)   │
└───────────────┬─────────────────┘
                │ 无权限 → 拒绝访问/不显示
                ▼
┌─────────────────────────────────┐
│ 第三层:数据权限                  │
│ 单据: QueryHelper 自动过滤       │
│ 报表: pk_corp WHERE 条件         │
└───────────────┬─────────────────┘
                │
                ▼
          返回数据(仅本公司的)

SQL注入防护

用户传入的查询条件(conditions参数)经过 SqlSecurityUtils.isSafeCondition() 安全校验,拦截:
- 注释符:--、/*、#
- 堆叠注入:;
- 子查询:SELECT、INSERT、UPDATE、DELETE
- DDL/DCL:DROP、ALTER、CREATE、TRUNCATE、EXEC
- 嵌套括号等复杂注入

日期过滤字段(dateField)和聚合字段(aggregateField)通过字段白名单校验,必须是模板中已定义的字段。

安全FAQ

Q: 别人拿到我的appsecret能查到我的数据吗?
A: 能。appsecret代表您的个人身份,请妥善保管。如果泄露,请立即在ERP个人资料页面重新生成密钥,旧密钥立即失效。

Q: 管理员能通过MCP查到所有公司的数据吗?
A: 管理员(isadmin=1)会跳过数据权限过滤,可以查到本公司所有数据,但仍受功能权限和模块授权开关的限制。

Q: 如果某个模块设置了if_authorize=Y,所有用户都能访问吗?
A: 不是。if_authorize=Y只是模块级开关,用户还需要有该模块的菜单权限才能通过MCP访问。

可用工具

1. list_bill_types — 查询可访问的单据类型

查询ERP中当前用户可访问的所有单据类型。返回 billtypecode(单据类型编码)和 billtypename(单据类型名称)。

当用户询问的业务模块不明确时,先调用此接口查看有哪些可用单据类型。例如:用户说"销售合同",此接口可能返回 billtypecode="xsht", billtypename="销售合同"。

  • 请求方式:GET
  • URL:${ERP_BASE_URL}/itf/common/mcp/listBillTypes.json
  • 请求头:X-MCP-SECRET: ${APP_SECRET}
  • 参数:无
  • 返回示例:
{
  "success": true,
  "data": [
    {"billtypecode": "xsht", "billtypename": "销售合同", "ifdoc": false, "vmodulename": "销售管理"},
    {"billtypecode": "cgdd", "billtypename": "采购订单", "ifdoc": false, "vmodulename": "采购管理"}
  ]
}

2. get_bill_fields — 查询单据字段元数据

查询指定单据类型的字段元数据。返回每个字段的 itemkey(字段编码)、showname(中文名)、datatype(数据类型)、listshow(是否列表显示)。

当需要知道"合同金额"对应哪个字段时,调用此接口查看。例如:用户说"金额",此接口可能返回 itemkey="ntotalamount", showname="合同总金额"。

  • 请求方式:GET
  • URL:${ERP_BASE_URL}/itf/common/mcp/getBillFields.json?billtype=${billtype}
  • 请求头:X-MCP-SECRET: ${APP_SECRET}
  • 参数:
  • billtype(必填):单据类型编码
  • 返回示例:
{
  "success": true,
  "data": [
    {"itemkey": "vbillcode", "showname": "单据编号", "datatype": 0, "listshow": true},
    {"itemkey": "ddate", "showname": "签订日期", "datatype": 3, "listshow": true},
    {"itemkey": "ntotalamount", "showname": "合同总金额", "datatype": 2, "listshow": true}
  ]
}

3. query_bill_list — 查询单据列表(含汇总行)

查询单据列表数据,返回记录列表和汇总行(summaryRow)。summaryRow 包含数值字段的合计值,如总金额。

支持 dateField + dateFrom + dateTo 进行日期范围过滤。支持 conditions 传入自定义查询条件(SQL WHERE片段,如 "billstatus=1")。

当用户问"XX金额是多少"时,调用此接口获取 summaryRow 中的合计值。返回数据中 fieldNames 是 itemkey → 中文名 的映射,帮助你理解字段含义。

  • 请求方式:POST
  • URL:${ERP_BASE_URL}/itf/common/mcp/queryBillList.json
  • 请求头:X-MCP-SECRET: ${APP_SECRET},Content-Type: application/json
  • 请求体:
{
  "billtype": "xsht",
  "pageNo": 1,
  "pageSize": 50,
  "dateField": "ddate",
  "dateFrom": "2025-07-22",
  "dateTo": "2026-07-22",
  "conditions": "billstatus=1",
  "orderBy": "ddate desc"
}
  • 参数说明:
  • billtype(必填):单据类型编码
  • pageNo:页码,默认1
  • pageSize:每页条数,默认50,最大200
  • dateField:日期过滤字段(itemkey,如 "ddate")
  • dateFrom:日期起(如 "2025-07-22")
  • dateTo:日期止(如 "2026-07-22")
  • conditions:自定义SQL条件(如 "billstatus=1 AND ntotalamount>10000")
  • orderBy:排序(如 "ddate desc")
  • 返回示例:
{
  "success": true,
  "data": {
    "summaryRow": {"ntotalamount": 12345678.90},
    "totalRecords": 56,
    "records": [
      {"vbillcode": "XSHT-2026-0001", "ddate": "2026-01-15", "ntotalamount": 50000.00, "billstatus": 1},
      {"vbillcode": "XSHT-2026-0002", "ddate": "2026-02-20", "ntotalamount": 120000.00, "billstatus": 1}
    ],
    "pageSize": 50,
    "pageNo": 1,
    "totalPage": 2,
    "fieldNames": {"vbillcode": "单据编号", "ddate": "签订日期", "ntotalamount": "合同总金额", "billstatus": "单据状态"}
  }
}

4. get_bill_detail — 查询单据明细

查询单据明细数据,返回完整的表头(HEADER)和表体(BODY)数据。当需要查看某张具体单据的详细信息时调用。

  • 请求方式:GET
  • URL:${ERP_BASE_URL}/itf/common/mcp/getBillDetail.json?billtype=${billtype}&billid=${billid}
  • 请求头:X-MCP-SECRET: ${APP_SECRET}
  • 参数:
  • billtype(必填):单据类型编码
  • billid(必填):单据ID
  • 返回示例:
{
  "success": true,
  "data": {
    "billtype": "xsht",
    "billid": "xxx-xxx-xxx",
    "data": {
      "HEADER": {"vbillcode": "XSHT-2026-0001", "ntotalamount": 50000.00, "ddate": "2026-01-15"},
      "BODY": {"sale_order_b": [{"vbillcode_b": "XSHT-2026-0001-01", "nnum": 100, "nprice": 500.00}]}
    },
    "fieldNames": {"vbillcode": "单据编号", "ntotalamount": "合同总金额", "ddate": "签订日期"}
  }
}

5. query_bill_summary — 聚合统计查询

对指定字段执行聚合统计(sum/count/avg/max/min),支持条件过滤。当用户问"总金额是多少""有多少张单据"时,可用此接口。也可以用 query_bill_list 的 summaryRow 获取合计,此接口更灵活。

  • 请求方式:POST
  • URL:${ERP_BASE_URL}/itf/common/mcp/queryBillSummary.json
  • 请求头:X-MCP-SECRET: ${APP_SECRET},Content-Type: application/json
  • 请求体:
{
  "billtype": "xsht",
  "aggregateField": "ntotalamount",
  "aggregateType": "sum",
  "dateField": "ddate",
  "dateFrom": "2025-07-22",
  "dateTo": "2026-07-22"
}
  • 参数说明:
  • billtype(必填):单据类型编码
  • aggregateField(必填):聚合字段(itemkey)
  • aggregateType:聚合类型,默认 sum。可选值:sum(求和)、count(计数)、avg(平均)、max(最大)、min(最小)
  • dateField:日期过滤字段
  • dateFrom:日期起
  • dateTo:日期止
  • conditions:自定义SQL条件
  • 返回示例:
{
  "success": true,
  "data": {
    "billtype": "xsht",
    "billtypename": "销售合同",
    "aggregateField": "ntotalamount",
    "aggregateType": "sum",
    "value": 12345678.90,
    "fieldNames": {"ntotalamount": "合同总金额", "ddate": "签订日期"}
  }
}

6. list_report_types — 查询可访问的报表类型

查询ERP中当前用户可访问的所有报表模板。返回 vtemplatecode(报表模板编码)和 vtemplatename(报表模板名称)。

当用户提到"报表""汇总""按XX统计""按XX分组"时,调用此接口查看有哪些可用报表。报表是预定义的查询SQL,适合聚合统计场景。

  • 请求方式:GET
  • URL:${ERP_BASE_URL}/itf/common/mcp/listReportTypes.json
  • 请求头:X-MCP-SECRET: ${APP_SECRET}
  • 参数:无
  • 返回示例:
{
  "success": true,
  "data": [
    {"vtemplatecode": "xsht_kh_hz", "vtemplatename": "销售合同客户汇总", "nodecode": "XSHT_KH_HZ"},
    {"vtemplatecode": "xsht_bm_hz", "vtemplatename": "销售合同部门汇总", "nodecode": "XSHT_BM_HZ"}
  ]
}

7. get_report_fields — 查询报表字段元数据

查询指定报表的字段元数据。返回每个字段的 itemkey(字段编码)、defaultshowname(中文名)、data_type(数据类型)、total_flag(是否参与合计)、show_flag(是否显示)。

当需要知道报表中"金额"对应哪个字段时,调用此接口查看。

  • 请求方式:GET
  • URL:${ERP_BASE_URL}/itf/common/mcp/getReportFields.json?vtemplatecode=${vtemplatecode}
  • 请求头:X-MCP-SECRET: ${APP_SECRET}
  • 参数:
  • vtemplatecode(必填):报表模板编码
  • 返回示例:
{
  "success": true,
  "data": [
    {"itemkey": "customer_name", "defaultshowname": "客户名称", "data_type": 0, "total_flag": false, "show_flag": true},
    {"itemkey": "ntotalamount", "defaultshowname": "合同金额", "data_type": 2, "total_flag": true, "show_flag": true},
    {"itemkey": "ddate", "defaultshowname": "签订日期", "data_type": 3, "total_flag": false, "show_flag": true}
  ]
}

8. query_report_data — 查询报表数据(含公式执行 + 汇总行)

执行报表查询,返回记录列表、汇总行和字段映射。

关键特性:报表数据经过公式执行(FormulaHelper),PK字段已自动转换为名称。例如 pk_customer 会显示为"客户A",不需要再做关联查询。

  • 请求方式:POST
  • URL:${ERP_BASE_URL}/itf/common/mcp/queryReportData.json
  • 请求头:X-MCP-SECRET: ${APP_SECRET},Content-Type: application/json
  • 请求体:
{
  "vtemplatecode": "xsht_kh_hz",
  "pageNo": 1,
  "pageSize": 50,
  "dateField": "ddate",
  "dateFrom": "2026-07-01",
  "dateTo": "2026-07-31",
  "conditions": "ntotalamount>10000",
  "orderBy": "ntotalamount desc"
}
  • 参数说明:
  • vtemplatecode(必填):报表模板编码
  • pageNo:页码,默认1
  • pageSize:每页条数,默认50,最大200
  • dateField:日期过滤字段(itemkey,通过 get_report_fields 获取)
  • dateFrom:日期起(如 "2026-07-01")
  • dateTo:日期止(如 "2026-07-31")
  • conditions:自定义SQL条件(如 "ntotalamount>10000"),经安全校验
  • orderBy:排序(如 "ntotalamount desc")
  • 返回示例:
{
  "success": true,
  "data": {
    "summaryRow": {"ntotalamount": 800000.00},
    "totalRecords": 3,
    "records": [
      {"customer_name": "客户A", "ntotalamount": 500000.00, "ddate": "2026-07-15"},
      {"customer_name": "客户B", "ntotalamount": 300000.00, "ddate": "2026-07-20"}
    ],
    "pageSize": 50,
    "pageNo": 1,
    "totalPage": 1,
    "fieldNames": {"customer_name": "客户名称", "ntotalamount": "合同金额", "ddate": "签订日期"}
  }
}

使用流程

第一步:判断查询类型并确定编码

单据查询:当用户提到"销售合同""采购订单"等具体单据时,调用 list_bill_types,将业务名词匹配到 billtypecode。

例如:用户说"销售合同",在返回列表中找到 billtypename 包含"销售合同"的记录,获取其 billtypecode(如 "xsht")。

报表查询:当用户提到"报表""汇总""按XX统计""按XX分组"时,先调用 list_report_types 查看有哪些可用报表,将业务名词匹配到 vtemplatecode。

例如:用户说"销售合同按客户汇总",在返回列表中找到 vtemplatename 包含"销售合同客户汇总"的记录,获取其 vtemplatecode。

第二步:确定字段映射

单据:调用 get_bill_fields(billtype) 查看字段列表,将中文名映射到 itemkey。

例如:用户说"金额",在返回字段列表中找到 showname 包含"金额"的字段,获取其 itemkey(如 "ntotalamount")。

报表:调用 get_report_fields(vtemplatecode) 查看报表字段列表,将中文名映射到 itemkey。报表字段额外返回 total_flag(是否参与合计),帮助你判断哪些字段有汇总值。

第三步:查询数据

  • 用户问"XX金额是多少" → 调用 query_bill_list(设置日期范围),从返回的 summaryRow 中取合计值;或调用 query_bill_summary(aggregateType=sum)直接获取聚合值。
  • 用户问"列出最近XX的单据" → 调用 query_bill_list(设置日期范围和分页)。
  • 用户问"某张单据的详情" → 先通过 query_bill_list 找到 billid,再调用 get_bill_detail。
  • 用户问"有多少张/数量" → 调用 query_bill_summary(aggregateType=count)。
  • 用户问"按客户/部门汇总" → 如果有对应报表,调用 query_report_data(报表更适合此类聚合查询);否则用 query_bill_list 获取明细数据后自行聚合。
  • 用户问"查询XX报表" → 调用 query_report_data(报表数据已执行公式,PK字段已转换为名称)。

第四步:分析数据

当用户要求"分析"时(如"分析销售合同的风险情况"),先调用 query_bill_list 获取足够的数据,然后你(LLM)根据返回的数据进行推理分析,输出分析报告。ERP不提供分析能力,分析由你完成。

第五步:可视化数据

查询结果默认应该用图表呈现,而不是仅输出表格。当返回的 records 含有可聚合的数值字段(金额、数量等)或可分组的维度字段(客户、状态、日期、物料等)时,主动调用 show_widget 工具渲染 Chart.js 图表,让用户像用 BI 工具一样直观看到数据。

详细规则和代码模板见下方「数据可视化」章节。

数据可视化

何时可视化(触发规则)

场景 是否可视化 说明
用户问"总金额是多少""有多少张" ❌ 不画图 单一数值,文字回答即可
用户问"列出最近 N 条"且 N ≤ 5 ❌ 不画图 数据太少,表格更清晰
返回记录 ≥ 3 条且含金额/数量字段 ✅ 画图 至少画一张主图
用户说"分析""趋势""分布""占比""排名""对比" ✅ 画图 按意图选图
用户说"画图""可视化""chart" ✅ 画图 明确要求
用户问"某张单据的详情" ❌ 不画图 单据详情用文字/表格

图表类型选择

根据用户意图和数据形状选择:

用户意图 数据形状 推荐图表 示例问题
看占比/分布 1 个维度 + 1 个数值 饼图/环形图 "各客户合同金额占比"
看排名/对比 1 个维度 + 1 个数值,维度 ≤ 15 水平柱状图 "合同金额 TOP 10 客户"
看趋势 时间维度 + 1 个数值 折线图 "近一年销售合同月度趋势"
看多维度对比 2 个维度 + 1 个数值 分组柱状图 "各客户各月合同金额"
看状态分布 1 个状态维度 + 计数 环形图 "销售合同状态分布"
看累计进度 时间 + 累计数值 折线图(填充) "累计合同金额增长"

> 默认策略:如果用户没有明确指定图表类型,且数据适合可视化,优先画一张水平柱状图(金额按维度排名),因为排名图信息密度最高、最实用。

数据转换规则

ERP 返回的 records 是字段 Map 数组,需转换为 Chart.js 数据集:

1. 按维度聚合(分组求和)

# Python 伪代码:按客户聚合合同总额
from collections import defaultdict
agg = defaultdict(float)
for r in records:
    key = r.get('customer_name', '未知')   # 维度字段
    val = float(r.get('total_contract_amount', 0) or 0)  # 数值字段
    agg[key] += val
# 排序取 TOP N
sorted_items = sorted(agg.items(), key=lambda x: -x[1])[:10]
labels = [x[0] for x in sorted_items]
values = [x[1] for x in sorted_items]

2. 按时间分组(月度趋势)

from collections import defaultdict
monthly = defaultdict(float)
for r in records:
    d = r.get('signingdate', '')[:7]  # 'YYYY-MM'
    if d:
        monthly[d] += float(r.get('total_contract_amount', 0) or 0)
# 按月份排序
labels = sorted(monthly.keys())
values = [monthly[k] for k in labels]

3. 数值字段清洗
- ERP 数值字段可能返回字符串带千分位(如 "10,400,002.00"),需 float(str(val).replace(',', ''))
- None / "" / "null" 统一当 0 处理

调用 show_widget 的规范

调用 show_widget 工具时:
- 先调用 read_me 加载 chart 模块(首次画图前必须调用一次)
- widget_code 传纯 HTML 片段(含 <canvas> + <script>),不要包含 <!DOCTYPE>、<html>、<head>、<body>
- title 参数用简短中文描述(如"销售合同客户金额分布")
- loading_messages 传 1-4 条中文加载提示

颜色规范

Canvas 无法读取 CSS 变量,必须用硬编码十六进制色值。从以下色板中选取(light 主题:50 填充 + 600 描边/文字):

色系 填充(50) 主色(600) 深色(800)
蓝 c-blue #E6F1FB #185FA5 #0C447C
紫 c-purple #EEEDFE #534AB7 #3C3489
青 c-teal #E1F5EE #0F6E56 #085041
珊瑚 c-coral #FAECE7 #993C1D #712B13
粉 c-pink #FBEAF0 #993556 #72243E
绿 c-green #EAF3DE #3B6D11 #27500A
琥珀 c-amber #FAEEDA #854F0B #633806
红 c-red #FCEBEB #A32D2D #791F1F
灰 c-gray #F1EFE8 #5F5E5A #444441

多色系列(饼图/环形图按顺序循环使用):

const palette = ['#185FA5', '#534AB7', '#0F6E56', '#993C1D', '#993556', '#3B6D11', '#854F0B', '#A32D2D', '#5F5E5A'];

图表模板

模板 1:水平柱状图(金额排名 TOP N)

适用:合同金额按客户/物料/业务员排名。values 已按降序排好。

<div style="position: relative; width: 100%; height: 420px;">
  <canvas id="erpChart" role="img" aria-label="合同金额按客户排名水平柱状图">各客户合同金额排名柱状图。</canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
<script>
const labels = /* ['客户A','客户B','客户C', ...] */;
const values = /* [10400002, 105787, 93000, ...] */;
const palette = ['#185FA5','#534AB7','#0F6E56','#993C1D','#993556','#3B6D11','#854F0B','#A32D2D','#5F5E5A'];
new Chart(document.getElementById('erpChart'), {
  type: 'bar',
  data: {
    labels: labels,
    datasets: [{
      label: '合同金额',
      data: values,
      backgroundColor: values.map((_, i) => palette[i % palette.length] + 'CC'),
      borderColor: values.map((_, i) => palette[i % palette.length]),
      borderWidth: 1
    }]
  },
  options: {
    indexAxis: 'y',
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      legend: { display: false },
      tooltip: {
        callbacks: {
          label: function(ctx) {
            return ' ' + ctx.parsed.x.toLocaleString('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2});
          }
        }
      }
    },
    scales: {
      x: {
        ticks: { callback: function(v) { return (v/10000).toFixed(0) + '万'; } },
        grid: { color: 'rgba(0,0,0,0.06)' }
      },
      y: { grid: { display: false } }
    }
  }
});
</script>

模板 2:环形图(占比分布)

适用:客户占比、状态分布、币种分布。

<div style="position: relative; width: 100%; height: 320px;">
  <canvas id="erpChart" role="img" aria-label="合同金额按客户占比环形图">各客户合同金额占比环形图。</canvas>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size: 12px; color: #5F5E5A;">
  <!-- 图例由 AI 根据数据生成,示例: -->
  <span style="display: flex; align-items: center; gap: 4px;">
    <span style="width: 10px; height: 10px; border-radius: 2px; background: #185FA5;"></span>客户A
  </span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
<script>
const labels = /* ['客户A','客户B','客户C'] */;
const values = /* [10400002, 105787, 93000] */;
const palette = ['#185FA5','#534AB7','#0F6E56','#993C1D','#993556','#3B6D11','#854F0B','#A32D2D','#5F5E5A'];
new Chart(document.getElementById('erpChart'), {
  type: 'doughnut',
  data: {
    labels: labels,
    datasets: [{
      data: values,
      backgroundColor: values.map((_, i) => palette[i % palette.length]),
      borderColor: '#fff',
      borderWidth: 2
    }]
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    cutout: '60%',
    plugins: {
      legend: { display: false },
      tooltip: {
        callbacks: {
          label: function(ctx) {
            const total = ctx.dataset.data.reduce((a,b) => a+b, 0);
            const pct = (ctx.parsed / total * 100).toFixed(1);
            return ' ' + ctx.label + ': ' + ctx.parsed.toLocaleString('zh-CN') + ' (' + pct + '%)';
          }
        }
      }
    }
  }
});
</script>

模板 3:折线图(时间趋势)

适用:月度/季度合同金额趋势。

<div style="position: relative; width: 100%; height: 320px;">
  <canvas id="erpChart" role="img" aria-label="合同金额月度趋势折线图">合同金额月度趋势折线图。</canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
<script>
const labels = /* ['2025-01','2025-02','2025-03', ...] */;
const values = /* [50000, 120000, 80000, ...] */;
new Chart(document.getElementById('erpChart'), {
  type: 'line',
  data: {
    labels: labels,
    datasets: [{
      label: '合同金额',
      data: values,
      borderColor: '#185FA5',
      backgroundColor: 'rgba(24,95,165,0.1)',
      borderWidth: 2,
      fill: true,
      tension: 0.3,
      pointBackgroundColor: '#185FA5',
      pointRadius: 4
    }]
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      legend: { display: false },
      tooltip: {
        callbacks: {
          label: function(ctx) {
            return ' ' + ctx.parsed.y.toLocaleString('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2});
          }
        }
      }
    },
    scales: {
      y: {
        ticks: { callback: function(v) { return (v/10000).toFixed(0) + '万'; } },
        grid: { color: 'rgba(0,0,0,0.06)' }
      },
      x: { grid: { display: false } }
    }
  }
});
</script>

模板 4:分组柱状图(双维度对比)

适用:各客户在各月份的合同金额对比。datasets 为每个维度一条数据集。

<div style="position: relative; width: 100%; height: 360px;">
  <canvas id="erpChart" role="img" aria-label="各客户各月合同金额分组柱状图">各客户各月合同金额分组柱状图。</canvas>
</div>
<div style="display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; font-size: 12px; color: #5F5E5A;">
  <!-- 图例由 AI 生成 -->
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"></script>
<script>
const months = /* ['2025-05','2025-06','2025-07'] */;
const palette = ['#185FA5','#534AB7','#0F6E56','#993C1D','#993556'];
const datasets = /* [
  { label: '客户A', data: [50000, 80000, 10400002] },
  { label: '客户B', data: [0, 105787, 93000] }
] */;
new Chart(document.getElementById('erpChart'), {
  type: 'bar',
  data: {
    labels: months,
    datasets: datasets.map((ds, i) => ({
      label: ds.label,
      data: ds.data,
      backgroundColor: palette[i % palette.length] + 'CC',
      borderColor: palette[i % palette.length],
      borderWidth: 1
    }))
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: { legend: { display: false } },
    scales: {
      y: {
        ticks: { callback: function(v) { return (v/10000).toFixed(0) + '万'; } },
        grid: { color: 'rgba(0,0,0,0.06)' }
      },
      x: { grid: { display: false } }
    }
  }
});
</script>

多图组合

复杂分析场景可连续调用多次 show_widget,每次画一张图,图与图之间用文字过渡。例如分析销售合同时:
1. 先画环形图(客户分布占比)
2. 文字过渡:"下面是各客户的合同金额排名详情..."
3. 再画水平柱状图(金额 TOP 排名)
4. 文字过渡:"从时间趋势来看..."
5. 最后画折线图(月度趋势)

不要把多张图塞进同一个 widget,每张图独立调用。

金额格式化

图表中的金额轴和 tooltip 统一用中文习惯:
- 万级:(v/10000).toFixed(0) + '万'
- 亿级:(v/100000000).toFixed(2) + '亿'
- tooltip 原始值:v.toLocaleString('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2})

完整工作流示例

用户问"分析最近一年销售合同的客户分布和金额趋势":

  1. list_bill_types → 找到 salesorder
  2. get_bill_fields → 确认 signingdate(合同日期)、customer_name(客户)、total_contract_amount(合同总额)等字段
  3. query_bill_list(pageSize=200,日期范围=最近一年)→ 获取 records
  4. Python 脚本聚合:按客户分组求和、按月分组求和
  5. read_me(modules=["chart"]) → 加载图表模块
  6. show_widget 画环形图(客户占比)
  7. 文字过渡
  8. show_widget 画折线图(月度趋势)
  9. 文字总结分析结论

日期处理规则

  • "最近一年" → dateFrom = 今天减一年(YYYY-MM-DD),dateTo = 今天
  • "本月" → dateFrom = 本月1号,dateTo = 今天
  • "本季度" → dateFrom = 本季度第一天,dateTo = 今天
  • "今年" → dateFrom = 今年1月1日,dateTo = 今天
  • 日期格式始终为 YYYY-MM-DD
  • dateField 通常是签订日期(如 "ddate"),单据通过 get_bill_fields 确认,报表通过 get_report_fields 确认

返回数据理解

  • records:单据/报表记录数组,每条记录是一个字段Map(key是itemkey)
  • summaryRow:汇总行,包含数值字段的合计(如 ntotalamount: 12345678.90)
  • totalRecords:符合条件的总记录数
  • fieldNames:itemkey → 中文名 的映射,帮助你理解字段含义
  • value(query_bill_summary返回):聚合计算结果
  • 报表的 records 已经过公式执行,PK字段已转换为名称,不需要再做关联查询
  • 报表的 summaryRow 只包含 total_flag=true 的字段的合计值

注意事项

  1. 你只能查询到当前用户角色权限范围内的数据(由 APP_SECRET 决定)
  2. 如果查询结果为空,告知用户可能没有权限或没有符合条件的单据
  3. 分析类问题需要你基于数据自行推理,ERP不提供分析能力
  4. conditions 参数只支持简单的 SQL WHERE 条件(如 "billstatus=1 AND ntotalamount>10000"),不支持子查询、注释、分号等
  5. 金额类字段返回的是数值,需要你格式化为人类可读的形式(如 12345678.90 → 1234.57万元)
  6. 如果用户没有指定单据类型,先调用 list_bill_types 查看有哪些可用模块
  7. 如果用户没有指定日期范围,默认查询全部数据(不传 dateField/dateFrom/dateTo)
  8. 所有请求必须携带请求头 X-MCP-SECRET,值为你的 APP_SECRET
  9. 可视化优先:查询返回 ≥3 条记录且含数值字段时,应主动用 show_widget 画图,而非仅输出表格
  10. 画图前必须先调用 read_me(modules=["chart"]) 加载图表设计规范,否则可能违反样式规则
  11. 数值清洗:ERP 数值字段可能返回带千分位的字符串(如 "10,400,002.00"),画图前必须 float(str(v).replace(',','')) 转为数字
  12. Canvas 硬编码颜色:Chart.js 的 canvas 无法读取 CSS 变量,所有颜色必须用十六进制硬编码
  13. 多图分多次调用:不要把多张图塞进一个 widget,每张图独立调用 show_widget,图之间用文字过渡
  14. 报表数据已执行公式:query_report_data 返回的数据已通过 FormulaHelper 执行公式,PK字段已转换为名称,不需要再做关联查询
  15. 报表 vs 单据选择:用户需要"按XX汇总""按XX统计"时优先查 list_report_types 是否有对应报表;用户需要"列出单据""查单据明细"时用单据工具

常见问题

5 问
答案由本站依据条目字段整理
自然语言查询ERP数据并可视化怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_a7d36aaa/nw-erp-skill。安装方式为未标注。装完刷新技能列表或重开客户端。

自然语言查询ERP数据并可视化支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

自然语言查询ERP数据并可视化权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

自然语言查询ERP数据并可视化收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

自然语言查询ERP数据并可视化用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。