Files
dianxiaomi-crossborder-play…/速卖通建品/RECIPE-2026-09-30.md
T

127 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 速卖通(AliExpress POP)「选品建listing」完整配方
> 2026-09-30 实测跑通:**4/4 进「待发布」,采集箱 0 残留**(RV8I478D / ZQI01T5J / XSSIERBV / XAGOKS9C)。
> 目标态 = 待发布(`dxmState=offline & dxmOfflineState=waitPublish`),**绝不点「发布」**。
## 0. 页面与接口事实
| 项 | 值 |
|---|---|
| 创建页 | `https://www.dianxiaomi.com/web/smt/add`(标题「店小秘--添加速卖通产品」) |
| 编辑页 | `/web/smt/edit?id=<id>` —— **实测打不开**:`GET /api/smtProduct/edit.json?id=` 无论 id 传什么都返回同一份 12,592 字节的**添加页引导包**(modelList/unitList),不含商品数据 → 不要指望它做回读 |
| 保存 | `POST /api/smtProduct/add.json`(点「保存」触发;**不点「发布」**) |
| 列表 | `POST /api/smtProduct/pageList.json`,body: `pageNo&pageSize&total&searchType=0&searchValue&shopId=-1&dxmState&dxmOfflineState` |
| 列表页 | 待发布 `/web/smt/smtProductList/offline`、采集箱 `…/draft`、在线 `…/online` |
| 类目 | 点「选择分类」→ 弹窗标题**「选择类目」**、搜索框 `input[placeholder*=搜索分类]`、结果行 `.search-result-item` |
| 单位 | **kg / cm**(Ozon 是 g / mm,别混) |
| 店铺 | 10 家:BlessLuckA、Laikeet、Sunbeam、AliMingPro1、Key、Deanoyal… |
## 1. 执行顺序(顺序错了就被挡)
```
① 店铺(必须最先!类目树随店铺/站点变)
② 类目(中文品类词搜索 → 点结果行,弹窗自动关)
③ 标题(insertText)
④ 来源URL(无 label,用 placeholder 定位)
⑤ 零售价格 / 库存数量 / 货值 / 商品编码
⑥ 重量(kg)、包装尺寸(cm) ×3、包装后尺寸(cm) ×3
⑦ 图片(选择图片 → 网络图片 → 粘 URL → 添加)
⑧ 物流属性(铅笔图标弹窗 → 普货 → 确定)
⑨ 是否原箱(下拉,必填)、报价是否含关税(radio value=1 不含关税报价)
⑩ PC端描述(CKEditor 4)
⑪ 保存 → 回读 pageList
```
## 2. 逐字段硬知识(每条都踩过)
1. **不选店铺 → 「选择分类」根本打不开**(弹窗不出现,表现为 `no-modal`)。脚本里店铺选择必须在类目前。
2. **类目搜索词必须是中文**:`旗帜→横幅(Banners)`、`托特包→Tote Bags`、`护照夹→护照套/护照夹`。
**英文搜不到**(`Tote Bags` → 命中 0 → 保存报「请选择分类」)。
结果行文本形如 `Luggage & Bags(箱包)/Kids' Bags(童包)/Tote Bags(托特包)`(分隔符 `/`,中英双语),
**点行即选中并关窗**,没有可用的「选择」确认按钮。
* 已知瑕疵:`托特包` 命中 3 条时会选到第一条「童包」,优先命中父级段(`Women Bags`)的逻辑已加,但速卖通树里未必存在该英文段 → 需要时人工在页面上点一次。
3. **图片门槛 单边 ≥400px**:喂 10 张图,速卖通**静默只收 6 张**(300×224 / 300×195 / 367×344 / 300×213 被丢)。
→ 建输入前先按真实尺寸过滤:`build-add-input.py --min-side 400`(会下载探尺寸)。
模块:`.productMainImgModule`,头按钮文案「选择图片」;弹层 tab:本地图片 / **网络图片** / 图片银行 / 引用采集图片;
网络图片 tab 的 textarea placeholder「请填写图片URL地址,多个地址用回车换行」→ 点「添加」。
回读口令:`主图最多选用6张,已经选用了6张`(上限 **6 张主图**)。
4. **保存必填项(缺一个就整单失败)**——错误清单来自 `MYJ.message.error` 钩子 + `.ant-form-item-explain-error`:
| 报错原文 | 要填什么 | 正确写法 |
|---|---|---|
| 请输入货值 | 货值(CNY) | 用成本价兜底(`cost_cny`) |
| 物流属性必填! | 物流属性 | **不是普通下拉**:点 `.single-logistics-attr-edit` 铅笔 → `.batch-edit-modal` → 选「普货」→ 确定 |
| 包装后尺寸请填写整数! | 包装后尺寸 | **整数 cm**;包装尺寸与包装后尺寸都要填,且**尺寸建议整体向上取整** |
| 请选择报价是否含关税! | radio | `input[type=radio][value=1]` = 不含关税报价 |
| 请输入重量 | 重量(kg) | **必须精确命中「重量(kg)」**:类目属性里还有「重量(Item Weight)」,用 `/重量/` 会填错字段(护照夹实测被拒) |
| 请选择分类 | 类目 | 见第 2 条 |
5. **重量下限**:46g → 0.046 → 字段只留 2 位小数变 `0.04` → 被拒「请输入重量」。
安全写法:`wkg = max(0.05, ceil(g/10)/100)` → 0.046→0.05、0.075→0.08、0.097→0.10。
6. **描述是 CKEditor 4 两个实例**:`ckeditor30`=PC端描述、`ckeditor31`=无线端描述(实测确认)。
**必须按实例所属 form-item 定位**:
```js
const hit = Object.keys(CKEDITOR.instances).find(k => {
const el = CKEDITOR.instances[k].element?.$; return el && formItem.contains(el);
});
CKEDITOR.instances[hit].setData(html); CKEDITOR.instances[hit].updateElement();
```
* editable 元素的 `id` **不等于**实例名 → 用 `CKEDITOR.instances[ed.id]` 会落空;
* 退化成「改 iframe innerHTML / contenteditable」时 model 不同步 = 白填。
* 实测成功回读:`ckeditor:ckeditor30:len=2290`。
7. **`pageList` 不返回 `detail` / `packageWeight` / `packageLength`**(100 行全空)→
**不能**用它判断描述/重量/尺寸是否存进去,只能看保存时有没有报错。别把空值当「没保存」。
## 3. 脚本
```bash
D=/home/admin9webs/dsh-dingtalk-workspace/listing-spec
# ① 生成输入(含 400px 图过滤 + 中文类目词)
python3 $D/platform-shared/build-add-input.py pending-package-*.json \
--platform 速卖通 --out /tmp/smt_input.json --min-side 400
# ② 干跑(只填不保存,验证选择器)
node $D/platform-smt/smtbatch.mjs --input /tmp/smt_input.json --only <oid> --dry
# ③ 正式建listing(只保存 → 待发布)
node $D/platform-smt/smtbatch.mjs --input /tmp/smt_input.json --only <oid>
# ④ 回读验收(待发布有没有 + 采集箱有没有本轮新草稿)
node $D/platform-smt/verify-landing.mjs --oids <oid1>,<oid2> --keywords "kw1,kw2"
```
* `platform-shared/dxm-add-lib.mjs`:公共交互库(CDP 真实点击/滚动、setByLabel(insertText)、
`setNumberByLabel`(原生 setter,避免小数被吃)、`pickCategoryBySearch`、`addImagesByUrl`、
`setRichTextByLabel`(CKEditor)、`setLogisticsAttr`、`listShopOptions`、`readErrors`)。
* `platform-shared/preflight-shops.mjs`:**每轮批量前先跑**——查每个平台有没有授权店铺。
* `platform-shared/build-add-input.py`:把待上架 package 转成建listing输入;图按真实尺寸过滤;
英文类目叶 → 中文搜索词映射表 `CAT_KW`(命中不了就补这张表,别在页面上手点)。
## 4. 验收口径(实测结果)
```
RV8I478D ✅ 待发布 1 | 草稿 0 | 横幅 ¥6.65 图6
ZQI01T5J ✅ 待发布 1 | 草稿 0 | 托特包 ¥5.30 图6
XSSIERBV ✅ 待发布 1 | 草稿 0 | 护照套/护照夹 ¥6.90 图6
XAGOKS9C ✅ 待发布 1 | 草稿 0 | 托特包 ¥15.90 图6
```
* 采集箱里**有**大量历史草稿(有些是 2022/2023 年的),按关键词搜会误报残留 →
回读脚本只把**最近 N 分钟**建的草稿算成本轮残留(`--since-min`,默认 180)。
* SKU 前缀不能单独当判据:RV8I478D 的 SKU 是 1688 供应商码 `ZSJLQ_1be`,不含 oid → 回读用「SKU 前缀 **或** 标题关键词」双条件。
## 5. 仍未解决 / 待补
* 类目分支优选(托特包落「童包」而非「女包」)——需要人工确认或补父级英文段。
* `包装后尺寸` 与 `包装尺寸` 目前都填同一组向上取整值;如需精算需接速卖通计抛规则。
* 变种(多变种)模式未做:本配方用单品/单 SKU 路径;带变种的类目需要补变种行填写。
* 运费模板/服务模板未设置(`freightTemplateId=null`),发布前需人工在页面选或做「运费推荐生成」。
## 附:账号级店铺盘点(一次接口,最快)
`platform-shared/dxm-add-lib.mjs` 的 `readAccountShops(c)`:`GET /api/userIn.json` → `data.shopMap`
(key=shopId,value 含 `name`/`platform`)。实测 2026-09-30(子账号):
```
256 家店 → smt:61 tiktok:98 tiktokGlobal:24 aliChoice:65 ozon:5 walmart:3
```
★ **页面上的店铺下拉是子集**:速卖通页只列 10 家,`shopMap` 里是 61 家。
判断「这个登录有没有某平台店铺」用 `readAccountShops`;判断「这个店能不能在这个页面选到」才用 `listShopOptions`。