name: erp-retail-report description: "欧普V8移动报表 — curl 直接抓取(零依赖/0.5秒),生成销售分析+商品明细网页,飞书私发" version: 2.1.0 related_skills: [erp-curl-workflow]
纯 curl 方案——登录+查询+HTML解析,零依赖、0.5秒完成。
| 项目 | 值 |
|---|---|
| URL | http://182.61.44.242:16888 |
| 企业编号 | st |
| 账号 | 888 |
| 密码 | 123456 |
| 用户 | 郑雷 (888),普通用户 |
# 当天数据 + 覆盖更新 instant_sales.html + 飞书私发
python3 ~/erp_daily_report.py
# 指定日期
python3 ~/erp_daily_report.py --date 2026-05-25
# 不发飞书(调试用)
python3 ~/erp_daily_report.py --no-feishu
# 发飞书群
python3 ~/erp_daily_report.py --to-group
# 只抓数据不生成页面
python3 ~/erp_fetch.py --date 2026-05-26
| 脚本 | 职责 | 耗时 |
|---|---|---|
~/erp_fetch.py |
curl 登录 → 查询 → 解析 HTML → 输出 JSON → 退出 | 0.5s |
~/erp_daily_report.py |
调用 fetch → 生成单页面 → 飞书通知 | 0.7s |
输出文件:/var/www/sieta/sietadata/erp/instant_sales.html(唯一文件,每次覆盖,旧文件名 sales_*.html、pics_*.html、report_*.html 已删除)
访问 URL:https://www.sieta.vip/sietadata/erp/instant_sales.html
相关文档(sietadata/specs/):
- 即时销售表.md — 页面设计规范(CSS、字号、布局)
- 即时销售表Workflow.md — 完整工作流(含 IP/账号/密码)
三合一单页面(参考 22:30 SietaData pipeline 格式):
Part 1 — KPI 卡片(顺序固定) - 销售额 → 毛利(金额−选择价格金额) → 双数 → 店铺均价(102+104+105+106) → 商场均价(108)
Part 2 — 图表 - 各店销售额:双轴水平柱状图(销售额红 #FF6B6B / 双数紫 #C4B5FD) - 品牌排名:三层嵌套环形图(外环双数+透明间隙+内饼销售额,cutout 2%)
Part 3 — 商品卡片(完全克隆 sales_pics.html CSS)
- 按店铺分组,表头显示金额/毛利/双数
- 每件商品两排:货号(大字)+ 金额(红)& 毛利(绿)
- 右侧 90×90 图片(http://a.sieta.vip/img/{货号}.jpg),lightbox 大图
- 文字与图片垂直居中,无清版标签
cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.jsJS 嵌套对象如 {font:{size:10}} 在 Python f-string 中需写成 {{font:{{size:10}}}}。
推荐:数据放 <script type="application/json"> 标签,JS 用 JSON.parse() 读取,彻底避开括号地狱。
图表卡片和商品卡片勿共用 .cd。图表用 .cd,商品用 .ci,否则图表 display:flex 被覆盖变窄。
datalabels.align: function(ctx) { var v=ctx.dataset.data[ctx.dataIndex]; return v===max ? 'start':'end'; }
溢出时文字变白色在柱内显示。
这个 ERP 的数据表格是服务端渲染的(不是 JS 动态生成),curl 拿到的 HTML 里直接包含 <tr><td>STA108</td>...</tr>。
| curl | Playwright | |
|---|---|---|
| 耗时 | 0.5s | 7-15s |
| 依赖 | 零(bash+python stdlib) | Chrome + Playwright + venv |
| 适用 | 服务端渲染页面 | JS 动态渲染 SPA |
判断标准: 先用 curl -s URL | grep '<table' 检查 HTML 中有没有数据表格。有就用 curl,没有才上 Playwright。
表单是 GET /retail/summary?start_date=...&end_date=...&mode=10&is_pos=1。
关键参数:
- mode=10 → 店铺+商品+颜色汇总(⚠️ 值是数字,不是 label 文字)
- is_pos=1 → 包含实时 POS 数据
- price_type=CKJJ → 参考进价
邮件导出维度不一致: Web 页面查询显示店铺维度(STA102/STA108),但「发送邮件」的 Excel 附件却是营业员维度。 → 永远从 Web 页面 HTML 抓数据,不依赖邮件附件。
Session 退出(cew 标准):
每次 curl 查询后必须调用 /logout 退出,释放服务器 session。浏览器里查完数据点「退出」,curl 也不例外。
erp_fetch.py 已实现 try...finally 保证异常时也退出。验证:退出后查询返回 307(重定向到登录页)。
→ 详见 erp-curl-workflow skill(简称 cew)。
erp_daily_report.py 生成单个静态 HTML 页面 /sietadata/erp/report_YYYYMMDD.html,三部分:
.kr + .kc)| 指标 | 公式 | 顺序 |
|---|---|---|
| 销售额 | sum(金额) | 第1 |
| 毛利 | sum(金额) − sum(选择价格金额) | 第2 |
| 双数 | sum(数量) | 第3 ⚠️ 叫"双数"不叫"件数" |
| 店铺均价 | (102+104+105+106)金额 ÷ 四店总双数 | 第4 |
| 商场均价 | 108金额 ÷ 108双数 | 第5 |
.g2 + .cd + .cw)cdnjs.cloudflare.com,不是 jsdelivr(中国被墙导致图表空白).cd 类名被图表和商品卡片共用时互相覆盖 → 图表用 .cd,商品卡片用 .ci⚠️ JS 代码生成模式(Python f-string 坑):
绝对不要把所有 JS 代码嵌在 Python f-string 的 {{}} 里。{{→{ 的转义在深层嵌套时极易出错,且错误静默(new Chart() 不抛异常,只是不渲染)。
正确做法:数据与代码分离
# 数据:放在 <script type="application/json"> 中
html += '<script id="storeData" type="application/json">' + json.dumps(store_data) + '</script>'
# 代码:独立的 <script> 块,用 JS 的 JSON.parse() 读取
html += '''<script>
(function() {
var stores = JSON.parse(document.getElementById('storeData').textContent);
// ... chart code with single { } — no {{ }} escaping needed
})();
</script>'''
诊断方法:浏览器控制台执行 Chart.getChart('c1') — 返回 null 说明 new Chart() 静默失败。
双轴柱状图标注:
- 销售额:柱右侧显示 ¥金额(anchor:'end', align:'end'),最大柱溢出时自动转到柱内白色字(function-based align + color)
- 双数:贴近 Y 轴左侧显示 N双(anchor:'start', align:'end',贴在柱子左边缘)
// 销售额 dataset — 右侧标注,溢出白色
datalabels: { anchor: 'end',
align: function(ctx) {
var v = ctx.dataset.data[ctx.dataIndex];
return v === Math.max.apply(null, ctx.dataset.data) ? 'start' : 'end';
},
color: function(ctx) {
return ctx.dataset.data[ctx.dataIndex] === Math.max.apply(null, ctx.dataset.data) ? '#fff' : '#000';
},
font: { weight: 'bold', size: 13 },
formatter: function(v) { return '¥' + v.toLocaleString(); }
}
// 双数 dataset — 贴近 Y 轴
datalabels: { anchor: 'start', align: 'end', offset: 2,
font: { weight: 'bold', size: 12 },
formatter: function(v) { return v + '双'; }
}
环形图标注: 前4名品牌标注(display: function(ctx) { return ctx.dataIndex < 4; }),外层显示双数,内层显示金额。
datalabels CDN: cdnjs.cloudflare.com 同时提供 Chart.js 和 datalabels:
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js"></script>
⚠️ 必须 Chart.register(ChartDataLabels) 否则标注不显示无报错。
⚠️ 必须完全克隆 sales_pics.html 的 CSS 和 HTML 结构(class 名、布局、字号),唯一不同:不标注清版标签。
店头格式(v4):
<div class="st-hd">
<h2>二店</h2>
<div class="sum">
<span class="s-val">金额:¥889</span>
<span class="s-val">毛利:¥505</span>
<span class="s-val">3双</span>
</div>
</div>
font-size:1.3em),指标右对齐一字排开.s-val 字号 24px,margin-left:20px,display:inline-blockh2 字号 1.1em商品卡片结构(完全克隆 sales_pics.html):
<div class="cd">
<div class="cd-info">
<div class="cd-bot">
<div class="code">{货号}</div>
<div class="meta">
<span class="amt">¥{金额}</span>
<span class="cost">单价¥{单价}</span>
<span class="profit pos">毛利¥{毛利}</span>
</div>
</div>
</div>
<div class="tw">
<a href="#lb-{id}"><img src="http://a.sieta.vip/img/{货号}.jpg"></a>
</div>
</div>
.cd(图表用)原 CSS:background:#fff;border:3px solid #000;box-shadow:4px 4px 0 #000;padding:12px.ci(商品卡片用):display:flex;padding:8px 14px 10px 14px;gap:10px;border-bottom:2px solid #eee;min-height:100px.cd-bot 用 margin-top:auto 推到底部.tw 是 90×90(不是 300×300),margin-top:auto.code 字号 20px,金额 .amt 字号 20px,单价/毛利字号 22pxhttp://a.sieta.vip/img/{货号}.jpg.lb + :target)<h1 style="display:flex;align-items:baseline;gap:8px">
<span>即时销售表</span>
<span style="font-size:0.9em;font-weight:400;color:#666">2026-05-26 20:30</span>
</h1>
align-items:baseline),字号为标题的 0.9em权威来源:
erp-curl-workflow(cew) skill — 含完整 mode 探索脚本、HTML 解析模板、退出机制。本表仅列该 skill 使用的 mode=10。
| 条件 | value | 维度 |
|---|---|---|
| 商品汇总 | 0 | 商品 |
| 商品+颜色汇总 | 1 | 商品+颜色 |
| 营业员汇总 | 3 | 营业员 |
| 店铺汇总 | 7 | 店铺 |
| 店铺+商品汇总 | 8 | 店铺+商品 |
| 店铺+商品+颜色汇总 | 10 | 店铺+商品+颜色 |
| 店铺+商品+颜色汇总(显示尺码) | 11 | 店铺+商品+颜色+尺码 |
| 日期汇总 | 12 | 日期 |
| 月份汇总 | 14 | 月份 |
| 折扣汇总 | 16 | 折扣 |
| (全部 24 种) | 0-23 | 见完整对照表 |
完整对照表见:references/erp-automation-guide.md
私聊/群发统一用简单文本(不用 interactive card):
即时销售表 2026-05-26 20:35
https://www.sieta.vip/sietadata/erp/instant_sales.html
销售:¥6,622 毛利:¥3,451 23双
脚本:~/.hermes/scripts/feishu_sales_trigger.py
触发条件: 群里任何人发 1 或 @机器人 + 销售。
cron 配置: * * * * *(每分钟),no_agent=true。
⚠️ 读取群消息必须用 container_id_type=chat(不是 receive_id_type=chat_id):
GET /im/v1/messages?container_id_type=chat&container_id={chat_id}
receive_id_type=chat_id 可以发消息但不能读消息。
⚠️ 写入含 token 的脚本时,"Bearer " + token 拼接会被系统 mask 成 "Bearer *** 导致语法错误。绕过方式:先用 prefix = "Bearer " 变量再 auth = prefix + token。
群 ID: oc_9eeddda2511e3e8c62ed671017134814(每日销售播报群)
/retail — 零售统计/inventory — 库存统计/subscription — 订阅管理