Files
dianxiaomi-crossborder-play…/skill/SKILL.md
T
admin9webs 428255abc9 skill: 把 KVM-2 上在用的 SKILL.md 提回仓库(补两节,仓库版落后 9 天)
KVM-2 的 ~/.dsh/skills/dianxiaomi-multiplatform-listing/SKILL.md 是 172 行,
仓库 skill/SKILL.md 停在 113 行(2026-09-21),少了当天新增的两节:
  * ## 2.1 建listing 前的 0 号自检:这个平台有没有可用店铺(2026-09-30 加)
  * ## 8. Ozon「保存并移入待发布」被拦的真实错误链(2026-09-30 实测,逐层挖出来的)
技能自身声明「以 Gitea 为准」,但仓库版反而更旧 —— 本次以现场版为准回填,
并把 KVM-2 的部署改成指向本文件的软链,避免再漂。
2026-10-04 11:36:13 +08:00

13 KiB
Raw Blame History

name, description
name description
dianxiaomi-multiplatform-listing 在店小秘(dianxiaomi)把同一批商品批量建到多个跨境平台的「待发布」,并诊断「点保存没反应」。覆盖 Ozon/TikTok/Shopee/Walmart/速卖通/SHEIN/Temu 的店铺选择、类目弹窗、字段 store 落点、价格币种、图片外链写法、SKU 表结构。触发词:店小秘、dianxiaomi、批量建品、待发布、铺货、多平台、保存没反应、静默拦截、SHEIN 建品、Temu 建品、Temu 全托管、Walmart 建品、速卖通建品、TikTok 建品、Shopee 建品。

店小秘多平台批量建品

完整配方与逐平台踩坑:Gitea admin9webs/dianxiaomi-crossborder-playbook(以 Gitea 为准; 本机副本 ~/gitea-push/dianxiaomi-crossborder-playbook/ 可能滞后,读之前先 git pull)。 本技能是可复用的操作约定 + 通用技术事实;具体字段表看仓库各平台目录。

选品建listing 的两个新配方(2026-09-30): 速卖通建品/RECIPE-2026-09-30.md(速卖通,实测 4/4 进待发布)、 shopee建品/README-授权状态-2026-09-30.md(Shopee 页面事实 + 授权异常证据链与解锁步骤); 同一份内容也在技能 dianxiaomi-multiplatform-push/references/,另有运维脚本仓库 admin9webs/product-pool-ops → listing-spec/(共用页面库 / 输入生成 / 店铺自检 / 落地回读)。

0. 第一原则:遇到「点保存没反应」先挂钩子,不要猜字段

症状:点「保存」→ 不发 POST、无界面提示、无 .ant-form-item-has-error。

根因:校验失败了,但报错被 MYJ.message.error 吞掉;而且报错层层递进 (修完 A 才显示 B)——不修完前一层,后一层根本不显示。

解法(在点保存之前注入):

window.__cap=[];
function wrap(o,n,l){const f=o[n];o[n]=function(){
  window.__cap.push(l+': '+[...arguments].map(a=>typeof a==='string'?a:JSON.stringify(a)).join(' '));
  return f.apply(o,arguments);};}
['error','warning','info','success'].forEach(k=>wrap(MYJ.message,k,'MYJ.'+k));
['error','warning','info','success'].forEach(k=>wrap(antd.message,k,'antd.'+k));

点保存 → 读 window.__cap。

为什么「必填扫描」不够:扫 .ant-form-item-required 且为空,会漏掉 条件必填(Temu 省份:产地=中国才出现)和类目相关的文件字段 (速卖通商品说明书:部分类目强制)。

已用此法定位:

平台 被吞掉的报错
速卖通 商品说明书 → 请上传文件!(类目相关)
SHEIN 有必选规格未选择,请完善后再提交
Temu 产地选择中国时,省份必填 → 产品变种不能为空! → 尺寸不能为0或空 → 净重必须小于重量 → 请上传预览图

1. 通用技术事实

  • 站点是 Vue3 + Pinia SPA,读 store:
    document.querySelector('#app').__vue_app__.config.globalProperties.$pinia._s.get('<store>').$state
    
  • Chrome CDP 端口 9223,profile /tmp/dxm7;helper /tmp/cdp3.mjs(ESM,不能用 require)
  • VNC:http://192.168.9.122:6097/vnc.html / :6100(免密)
  • 列表 API 一律 POST /api/<平台>Product/pageList.json,列表在 data.page.list
  • idStr ≠ id:大整数 JS 精度丢失,服务端操作(删除/详情)一律用 idStr

2. 交互约定(血泪换来的)

场景 正确做法
antd select 选项 DOM click .ant-select-item-option 通常可行
只吃真实鼠标的组件 Input.dispatchMouseEvent 发 mousePressed/mouseReleased
hover 触发的下拉 先 mouseMoved 到目标,再 click(Walmart「选择图片」)
类名含 ! 的选择器 input[class*="!w-200"](input.!w-200 是非法 CSS)
长下拉(几百项) 先 scrollTop=0(会残留上次位置),再滚动找
有 store 直读列表 优先读 store(Temu 类目 1038 个在 dataState.categories),比驱动 UI 可靠
文本输入 优先 Input.insertText(逐字符 dispatchKeyEvent 在 Shopee 产品标题写入 0 字符);退化时才用原生 setter Object.getOwnPropertyDescriptor(proto,'value').set.call(el,v) + input/change/blur 事件
数字输入 原生 setter + input/change;部分字段是整数语义(速卖通/Shopee 尺寸 27.5 → 27),逐字符键入还会把小数点直接吃掉(27.5 → 275)
改 store 后校验仍失败 UI 异步同步,再点一次保存可能就过了

2.1 建listing 前的 0 号自检:这个平台有没有可用店铺(2026-09-30 加)

没有店铺 → 点「选择分类」弹窗根本不出现(类目树随店铺/站点变)、保存必失败; 填完一整屏才发现 = 白跑一轮。脚本侧同样硬拦(listShopOptions() 空数组即跳过该条)。

node listing-spec/platform-shared/preflight-shops.mjs      # → /tmp/shop-preflight.json

更快的账号级盘点(一次接口,全平台店铺数):readAccountShops(c) 读 GET /api/userIn.json 的 data.shopMap。 实测同一登录 256 家店 → smt:61 / tiktok:98 / tiktokGlobal:24 / aliChoice:65 / ozon:5 / walmart:3,shopee 0 家。 ★ 页面下拉只是子集(速卖通页只列 10 家,账号里 61 家): 判「有没有店」用 readAccountShops,判「能不能在这个页面选到」才用 listShopOptions。

实测(2026-09-30,登录子账号 ejetZhangjiafen):TikTok 10 店、速卖通 10 店; Shopee 全球「商家账号」=「暂无数据」;Mercado/Shopify/Amazon 拿不到店铺下拉。 ⚠️ 注意 Shopee 此前是能建的(本 playbook 记录过店铺 Ljbgkj 47 条进待发布)—— 所以「下拉为空」应读作==当前账号的店铺授权异常/范围不足==,不是平台从未授权: 店小秘首页此时正挂着 …/authorization/platform?go=shopee 的「去处理>>」告警。

3. 各平台最独特的一点

平台 必须知道
Ozon 保存按钮是**「保存并移入待发布」;价格 CNY;改价必须同时抬高原价**,否则 售价需小于原价;回读走 API variantList[].price(原价)/salePrice(售价)
TikTok 价格 MYR;店铺锁站点(一个品牌 × 多站点);图片 store 直接吃外链数组
Walmart 保存体是 zip 的 multipart(zip 内 walmartSave.txt);Brand 必须点「无品牌」勾选框(打字无效);图片必须 hover → 网络图片 贴 URL
Shopee 用单件模式 type='single' 绕开变种(默认 mutiple);价格 CNY;列表字段是 itemName 不是 title;「上架后自动发布到店铺」是 checkbox,必须不勾(勾了=保存即发布);尺寸规格是整数 cm;图片模块 .shopee-global-product-image-module(头是 header.product-image-header)。详见 dianxiaomi-multiplatform-push/references/recipe-shopee-global.md
速卖通 check_box 属性值必须是数组(否则 P.forEach is not a function);图片列用外链 {id,url,name,width,height,imgId,checked}。⚠️ 保存必填:货值 / 物流属性(点 .single-logistics-attr-edit 铅笔 → 弹窗选「普货」,不是普通下拉,报「物流属性必填!」)/ 包装尺寸+包装后尺寸都要整数 cm / 报价是否含关税 radio value=1;重量必须精确命中「重量(kg)」(类目属性里另有「重量(Item Weight)」);描述是 CKEditor 4(ckeditor30=PC端 / ckeditor31=无线端)。详见 dianxiaomi-multiplatform-push/references/recipe-aliexpress-smt.md
SHEIN 店铺锁品类(箱包店选不了发夹);类目要两个规格(点 .theme-select-box 加 尺寸(Size));⚠️ 加规格会重建 SKU 行,顺序必须「规格 → SKU → 尺码表」
Temu 规格用 .theme-value-add 块(值输入 + 添加按钮),必须手动输值再点添加,改 store 无效;报错层层递进

4. 图片

全部用外链(alicdn 等),且必须无水印(平台会删带联系方式的图)。 字段形如 {url, imgUrl, name, width, height},多数平台存数组, Walmart 例外(必须走「网络图片」UI,落成 | 分隔字符串)。

尺寸门槛按平台不同(2026-09-30 实测):Ozon 单边 ≥200px;速卖通 / Shopee 单边 ≥400px ——喂 10 张给速卖通会静默只收 6 张(300×224 / 300×195 / 367×344 / 300×213 被丢,且不报错)。 → 建输入时就按真实尺寸过滤:build-add-input.py --min-side 400(会下载探尺寸; 文件名带中文如 TIM截图….jpg 必须先 percent-encode,否则 urlopen 报错把好图误杀)。 主图上限:速卖通 6 张、Shopee「附图最多 9 张」+ 主图。

5. 定价口径

Ozon ≤500g 档(用户给定的表,已解出公式):

运费成本 = 4.2 + 重量(g) × 0.045
合计成本 = 进货价 + 运费成本
售价     = 合计成本 ÷ (1 − 20%佣金 − 利润率)
价格带   = 0–1500₽ / 0–113¥(≈13.27₽/¥)

其它档:budget 500-2500g 重量×0.02345+15.96;small 1-2000g 重量×0.02534+14.07。 跨币种:MYR ≈ CNY×0.60;USD = CNY÷6.68525(店小秘 siteAndUsCurrencyRate)。

6. EAN-13 台账(湖北易千亿彩 · 69803093 段)

  • 结构:69803093 + 4 位产品码 + 1 位校验
  • 校验:(10 - sum(d*(1 if i%2==0 else 3) for i,d in enumerate(d12)) % 10) % 10 (左起权重 1,3,1,3…;UPC-A 权重相反)
  • 台账唯一来源 S03 /root/ean_*.txt(KVM-2 够不到)→ 用号后必须去 S03 落盘,否则撞号
  • 说明书 §1 例子 pc 1000 → 6980309310004 是错的,正确 6980309310007

7. 批量作业约定

  • 动辄 1 小时 → nohup bash run.sh > out.log 2>&1 & 后台跑,每条一个日志文件
  • 脚本写成「一个 oid 一条命令」+ shell 循环,单条失败不拖垮整批
  • 跑之前先检查是否已存在,否则重试会产生重复(本会话踩过:某商品被建了 3 份)
  • 删重复:速卖通 POST /api/smtProduct/batchDelete.json body ids=<idStr>

8. Ozon「保存并移入待发布」被拦的真实错误链(2026-09-30 实测,逐层挖出来的)

症状链:草稿 categoryId="0" → GET /api/ozonProduct/moveToNoPublish.json?id=<草稿id>&shopId=-1 返回 code=0 却不动作(待发布总数不变)→ 改成在编辑页点「保存并移入待发布」→ 前端校验拦截、没有任何保存请求发出、被吞的提示只有 MYJ.error: 产品信息中有错误,请检查、 且 .ant-form-item-has-error 数量为 0(光扫 DOM 定位不到)。

挖法:注入 MYJ.message / antd.message 钩子(见第 0 节)后点保存,再 dump .ant-form-item-explain-error / [class*=explain] 文本 —— 真正的报错是分层出现的:

层 报错原文 怎么解
1 (无提示,纯静默) 类目未落定:categoryId=0/fullCid 形如 304634-
2 产品标题只能输入英文或者俄文,请修改! 草稿带的是 1688 中文标题 → 必须换成英/俄文案
3 产品描述只能输入英文或者俄文,请修改! 同上,描述也要英/俄
4 图片高最小尺寸不能小于200像素! 有主图高度 < 200px → 挑图/换图

结论:Ozon 这条链的正确顺序是 ① 类目落定 → ② 写入英/俄文案(池里 copy_variants 的 Ozon 版就是俄文)→ ③ 图片尺寸达标 → ④ 保存并移入待发布 → ⑤ 回读 pageList 的 waitPublish 口径验收。 只做类目或只看 code 都会得到「假成功/假失败」。

类目弹窗的操作要点(listing-spec/ozon-daily/ozon-daily-category.mjs):

  • 搜索框 input[name="searchCategory"],必须点「搜索」按钮才出结果;
  • 结果行是 .search-result-item,文本形如 Галантерея и аксессуары(小百货和配饰)/Аксессуары для волос(发饰)/Повязка для волос(发带), 分隔符是 / 不是 >;点某一行即落定(弹窗自动关闭,没有「选择」按钮那一步);
  • 选优打分:末段括号里精确等于关键词 +3、末段含关键词 +2、路径含「小百货/Галантерея」+1。

只读排查工具(同目录):probe-ozon-edit.mjs(编辑页 Network + 钩子 + 快照)、 diag-move-state.mjs(按草稿 id / offer id 在待发布·草稿·offline* 三口径定位)。