diff --git a/skill/SKILL.md b/skill/SKILL.md index 252c9d3..985bd0b 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -5,8 +5,15 @@ description: "在店小秘(dianxiaomi)把同一批商品批量建到多个 # 店小秘多平台批量建品 -> 完整配方与逐平台踩坑:Gitea `admin9webs/dianxiaomi-crossborder-playbook`(本机 `~/gitea-push/dianxiaomi-crossborder-playbook/`) +> 完整配方与逐平台踩坑: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. 第一原则:遇到「点保存没反应」先挂钩子,不要猜字段 @@ -58,18 +65,39 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){ | 类名含 `!` 的选择器 | `input[class*="!w-200"]`(`input.!w-200` 是非法 CSS) | | 长下拉(几百项) | 先 `scrollTop=0`(会残留上次位置),再滚动找 | | 有 store 直读列表 | **优先读 store**(Temu 类目 1038 个在 `dataState.categories`),比驱动 UI 可靠 | -| 文本输入 | `Object.getOwnPropertyDescriptor(proto,'value').set.call(el,v)` + `input`/`change`/`blur` 事件 | +| 文本输入 | **优先 `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()` 空数组即跳过该条)。 + +```bash +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 直接吃**外链数组** | -| **Shopee** | 用**单件模式** `type='single'` 绕开变种;价格 **CNY**;列表字段是 `itemName` **不是** title | | **Walmart** | 保存体是 **zip 的 multipart**(zip 内 `walmartSave.txt`);Brand 必须**点「无品牌」勾选框**(打字无效);图片必须 **hover → 网络图片** 贴 URL | -| **速卖通** | **`check_box` 属性值必须是数组**(否则 `P.forEach is not a function`);图片列用外链 `{id,url,name,width,height,imgId,checked}` | +| **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 无效;报错层层递进 | @@ -79,6 +107,12 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){ 字段形如 `{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 档(用户给定的表,已解出公式): @@ -96,13 +130,7 @@ Ozon ≤500g 档(用户给定的表,已解出公式): - 结构:`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 权重相反**) -- 台账唯一来源 **SG3 `43.134.190.229` 的 `/root/ean_*.txt`**(KVM-2 够不到,经 A3 落账)→ - 用号后**必须同步过去**,否则撞号。⚠️ **「S03」是客户/团队代号,不是主机名**(舰队里没有这台机器); - 登录 `ssh -i ~/9webs_sites.pem root@43.134.190.229`(key 在 A3 .102),**别名 `ssh SG3` 解析不了**。 - 格式惯例:一个文件 = 一个完整连续区间 `ean_69803093_-.txt`,**空洞也要写进去**。 - 同步用 `~/ean-sync/sync_to_sg3.sh`(幂等,哈希一致即跳过,重叠则中止) -- 2026-09-21:pc **3400~3551** 已落 SG3(`/root/ean_69803093_3400-3551.txt`,152 行), - 下一号 pc **3552**。发号只看台账,**不看 `tm_done.json`**(断点文件、换号后不更新) +- 台账唯一来源 **S03 `/root/ean_*.txt`**(KVM-2 够不到)→ 用号后**必须去 S03 落盘**,否则撞号 - 说明书 §1 例子 `pc 1000 → 6980309310004` 是**错的**,正确 `6980309310007` ## 7. 批量作业约定 @@ -111,3 +139,34 @@ Ozon ≤500g 档(用户给定的表,已解出公式): - 脚本写成「一个 oid 一条命令」+ shell 循环,**单条失败不拖垮整批** - **跑之前先检查是否已存在**,否则重试会产生重复(本会话踩过:某商品被建了 3 份) - 删重复:速卖通 `POST /api/smtProduct/batchDelete.json` body `ids=` + +## 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* 三口径定位)。