11 KiB
速卖通(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. 逐字段硬知识(每条都踩过)
-
不选店铺 → 「选择分类」根本打不开(弹窗不出现,表现为
no-modal)。脚本里店铺选择必须在类目前。 -
类目搜索词必须是中文:
旗帜→横幅(Banners)、托特包→Tote Bags、护照夹→护照套/护照夹。 英文搜不到(Tote Bags→ 命中 0 → 保存报「请选择分类」)。 结果行文本形如Luggage & Bags(箱包)/Kids' Bags(童包)/Tote Bags(托特包)(分隔符/,中英双语), 点行即选中并关窗,没有可用的「选择」确认按钮。- 已知瑕疵:
托特包命中 3 条时会选到第一条「童包」,优先命中父级段(Women Bags)的逻辑已加,但速卖通树里未必存在该英文段 → 需要时人工在页面上点一次。
- 已知瑕疵:
-
图片门槛 单边 ≥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 张主图)。 -
保存必填项(缺一个就整单失败)——错误清单来自
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 条 -
重量下限: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。 -
描述是 CKEditor 4 两个实例:
ckeditor30=PC端描述、ckeditor31=无线端描述(实测确认)。 必须按实例所属 form-item 定位: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。
- editable 元素的
-
pageList不返回detail/packageWeight/packageLength(100 行全空)→ 不能用它判断描述/重量/尺寸是否存进去,只能看保存时有没有报错。别把空值当「没保存」。
⚠ 类目弹窗「点了行但没关窗」→ 后面所有字段都填不进去(2026-09-30 实测)
pickCategoryBySearch 点结果行后有时弹窗不关。不关的弹窗会挡住后续所有点击:
标题/价格/库存/来源URL 全部写不进去(但类目已选,看起来「像成功了」)——
上一版就是这样,保存时报的却是别的字段,或直接存成空字段。
现在两处兜底:① closeCategoryModal() 主动确认并 Esc/点「关闭」强关;
② 模型自检 smtModelGaps() 会在保存前报出 subject(标题)/skuPrice(价格)/ipmSkuStock(库存) 为空 → 跳过保存。
实测四个品修完后 gaps=[](title 115–127 / price 5.3–15.9 / stock 999 / desc 1990–3252)。
3. 脚本
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。
附二:Vue 3 + Pinia 模型层自检(2026-09-30 加,保存前必做)
店小秘所有平台添加页都是 Vue 3.5.4 + Pinia(#app.__vue_app__.config.globalProperties.$pinia)。
DOM 里显示的值 ≠ 表单模型里的值;填了 DOM 而模型为空时点保存会「静默失败」或存成空字段。
→ 保存前读模型自检:smtModelSnapshot(c) + smtModelGaps(model),gaps 非空则跳过保存。
速卖通的字段映射(实测,smtAddBasicStore.formState / smtAddProductStore.formState):
| 业务字段 | 模型路径 |
|---|---|
| 店铺 / 类目 / 标题 | smtAddBasicStore.formState.{shopId,categoryId,subject} |
| 价格 / 货值 / 库存 / 编码 | smtAddProductStore.formState.{skuPrice,cargoPrice,ipmSkuStock,skuCode} |
| 重量 / 尺寸(长宽高) | packageWeight / packageLength,packageWidth,packageHeight |
| 是否原箱 | originalBox("0"=否) |
| 物流属性 | specialProductTypeList(普货 = ["1983471271:2025103"]) |
| 图片 | imageList[] |
| 描述 | smtAddStore.describeInfoData.detail(PC端)/ mobileDetail(无线端) |
一次干净的 dry 实测(RV8I478D):
shopId=135223 categoryId=201717001 subjectLen=121
skuPrice=6.65 cargoPrice=5.5 ipmSkuStock=999 skuCode=ZSJLQ_1be
packageWeight=0.10 dims=28/16/1 originalBox=0
specialProductTypeList=["1983471271:2025103"] images=6 descLen=1935 → gaps=[]
结论:描述确实是写进模型的(1935 字符,在 describeInfoData.detail);物流属性的 UI 路径也确实会提交到模型。
之前「描述可能没保存」的疑虑到此消除;pageList 不返回这些字段只是列表投影问题。
其它平台 store 命名同构(<platform> = smt / shopeeGlobal / tiktokGlobal / ozonProduct):
<platform>AddStore(编排+描述)· BasicStore · ProductStore · AttrStore · OtherStore
· SkuDataStore/SkuAttrStore(变种)· PackageStore/DescStore(Shopee/Ozon)。
公共:activity global user userPlatforms batchImageTaskStore *Translate*。
用 vueStores(c) 列全部、readPinia(c, storeId, path) 读任意字段。