Joom 47 条进待发布:jmbatch 跑通(店铺复选框组/可搜索类目/smtChoice* store),新增 3 条坑(无星号必填、虚拟滚动下拉、哨兵串定位 store)与 4 平台侦察表
This commit is contained in:
1 parent
c69952bd59
commit
c275571317
17 files changed
+1156
-4
No files matched your search
+13
-3
@@ -59,6 +59,9 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a
|
|||||||
- **SHEIN 40/40**(`shein建品/shbatch.mjs`,按 `shein_route.json` 分 4 家店;1 条因
|
- **SHEIN 40/40**(`shein建品/shbatch.mjs`,按 `shein_route.json` 分 4 家店;1 条因
|
||||||
①Shein-武汉九个网-箱包 报「商家无可用的发品额度」改挂 ②Shein-易世通达-箱包)
|
①Shein-武汉九个网-箱包 报「商家无可用的发品额度」改挂 ②Shein-易世通达-箱包)
|
||||||
- **Temu 全托管 47**(`temu建品/tmbatch.mjs` 批量 46/46 + 手工首条),逐条结果在 `tm_done.json`
|
- **Temu 全托管 47**(`temu建品/tmbatch.mjs` 批量 46/46 + 手工首条),逐条结果在 `tm_done.json`
|
||||||
|
- **Joom 47**(`joom建品/jmbatch.mjs` 批量 46/47 + 手工首条;唯一没建的是全平台统一跳过的
|
||||||
|
无水印图商品 `601964534137`),逐条结果 `jm_done.json`、实际类目 `joom_route.json`;
|
||||||
|
关键发现是 **Joom 的商品数据落在 `smtChoiceAddBasicStore`**(与 aliChoice 共用 `smtChoice*` store)
|
||||||
|
|
||||||
## 5. 这轮要做的(按优先级)
|
## 5. 这轮要做的(按优先级)
|
||||||
|
|
||||||
@@ -67,9 +70,16 @@ function wrap(o,n,l){const f=o[n];o[n]=function(){window.__cap.push(l+': '+[...a
|
|||||||
(pc 3447~3551,游标 `ean_cursor.json` → **3552**)。KVM-2 够不到 S03,
|
(pc 3447~3551,游标 `ean_cursor.json` → **3552**)。KVM-2 够不到 S03,
|
||||||
**需要在 S03 上把这份台账并进 `/root/ean_*.txt`**,之后从 3552 续号。
|
**需要在 S03 上把这份台账并进 `/root/ean_*.txt`**,之后从 3552 续号。
|
||||||
|
|
||||||
1. **继续铺剩余店小秘平台**:Joom(84 家)、Lazada(24 家)、aliChoice(64 家)、
|
1. **继续铺剩余店小秘平台**(已侦察,直接用):
|
||||||
pddkj(15 家)、fruugo(3 家)。店铺用**字母序/圈号自然序第一家**
|
|
||||||
(除 SHEIN 那种锁品类的)。这 5 个平台的字段落点和第一家店规则都还没勘,属于新探路。
|
| 平台 | add 路由 | 店铺控件 | 类目控件 | store 家族 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Joom | `/web/joomProduct/add` | **复选框组** 85 个(全部+84 店) | 可搜索 `.ant-select` 平铺列表 | `smtChoice*` + `joom*`(已跑通) |
|
||||||
|
| aliChoice | `/web/smtChoice/add` | 下拉 | 弹窗 + 自动识别分类 | `smtChoice*`(和 Joom 同一套,**可望直接复用**) |
|
||||||
|
| Lazada | `/web/lazada/add` | 下拉 | 弹窗 | 选完店铺才注册 `lazada*` store(懒加载) |
|
||||||
|
| fruugo | `/web/fruugoProduct/add` | 下拉 | 弹窗 | `fruugoAdd*`(Common/Basic/Dxm/Product/Variation/Image/Describe/Form) |
|
||||||
|
|
||||||
|
(pddkj = Temu 全托管,已完成。)店铺一律取**自然序第一家**,SHEIN 那种锁品类的除外。
|
||||||
|
|
||||||
2. **妙手各平台**(店小秘全部完成后)
|
2. **妙手各平台**(店小秘全部完成后)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
| Ozon | Yejet | **47** | 已建;改价 40/47 |
|
| Ozon | Yejet | **47** | 已建;改价 40/47 |
|
||||||
| SHEIN | 4 家分品类 | **40** | 完成(`shbatch.mjs` 自动批量;1 条因 ①箱包店发品额度用尽改挂 ②店) |
|
| SHEIN | 4 家分品类 | **40** | 完成(`shbatch.mjs` 自动批量;1 条因 ①箱包店发品额度用尽改挂 ②店) |
|
||||||
| Temu 全托管 | ①Alimingpro | **47** | 完成(`tmbatch.mjs` 批量 46 + 手工首条;预览图已解) |
|
| Temu 全托管 | ①Alimingpro | **47** | 完成(`tmbatch.mjs` 批量 46 + 手工首条;预览图已解) |
|
||||||
|
| Joom | Dummer-Pets(84 家里的第一家) | **47** | 完成(`jmbatch.mjs` 批量 46 + 手工首条;与 aliChoice 共用 `smtChoice*` store) |
|
||||||
|
|
||||||
> `601964534137`(遥控器保护套)18 张图全带水印、无干净图,所有平台统一跳过。
|
> `601964534137`(遥控器保护套)18 张图全带水印、无干净图,所有平台统一跳过。
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ function wrap(o, n, l) {
|
|||||||
| 速卖通 | `速卖通建品/` | `check_box` 属性值**必须是数组**;`idStr ≠ id` |
|
| 速卖通 | `速卖通建品/` | `check_box` 属性值**必须是数组**;`idStr ≠ id` |
|
||||||
| SHEIN | `shein建品/` | 店铺**锁品类**;**一个产品最多 3 个规格**(加满就停);**加规格会重建 SKU 行**;类目动态必填属性(`.attr-label.required`)不填只报「产品信息中有错误」 |
|
| SHEIN | `shein建品/` | 店铺**锁品类**;**一个产品最多 3 个规格**(加满就停);**加规格会重建 SKU 行**;类目动态必填属性(`.attr-label.required`)不填只报「产品信息中有错误」 |
|
||||||
| Temu | `temu建品/` | 类目 1038 个从 store 直读;规格是 `.theme-value-add`(**必须手动输值再点「添加」**,与 SHEIN 不同构);主图**必须满 5 张**;**预览图逐行点单元格** → 引用产品轮播图 → 全部 → 选择 |
|
| Temu | `temu建品/` | 类目 1038 个从 store 直读;规格是 `.theme-value-add`(**必须手动输值再点「添加」**,与 SHEIN 不同构);主图**必须满 5 张**;**预览图逐行点单元格** → 引用产品轮播图 → 全部 → 选择 |
|
||||||
|
| Joom | `joom建品/` | 店铺是**复选框组**(85 个 = 全部+84 店,别碰「全部」);类目是**可搜索下拉平铺列表**;商品数据落在 **`smtChoiceAddBasicStore`**(与 aliChoice 同构,不在 `joom*` 里) |
|
||||||
|
|
||||||
## 通用技术事实
|
## 通用技术事实
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ function wrap(o, n, l) {
|
|||||||
批量脚本请用 `shein建品/sh_lib.mjs` 的 `newPage(url)`:按 target 直连,不会抢错
|
批量脚本请用 `shein建品/sh_lib.mjs` 的 `newPage(url)`:按 target 直连,不会抢错
|
||||||
- VNC:`http://192.168.9.122:6097/vnc.html` 或 `:6100`(免密)
|
- VNC:`http://192.168.9.122:6097/vnc.html` 或 `:6100`(免密)
|
||||||
|
|
||||||
## 十五条会重复踩的坑
|
## 十八条会重复踩的坑
|
||||||
|
|
||||||
1. **保存静默拦截** → 挂钩子,别猜
|
1. **保存静默拦截** → 挂钩子,别猜
|
||||||
2. **`idStr ≠ id`** → 大整数精度丢失,服务端操作一律用 `idStr`
|
2. **`idStr ≠ id`** → 大整数精度丢失,服务端操作一律用 `idStr`
|
||||||
@@ -94,6 +96,13 @@ function wrap(o, n, l) {
|
|||||||
14. **类目动态必填属性**(`.attr-label.required`,如下拉、勾选组)不填不给具体提示,
|
14. **类目动态必填属性**(`.attr-label.required`,如下拉、勾选组)不填不给具体提示,
|
||||||
只报「产品信息中有错误,请检查」→ 兜底:空的下拉选第一项、勾选组勾第一个
|
只报「产品信息中有错误,请检查」→ 兜底:空的下拉选第一项、勾选组勾第一个
|
||||||
15. **EAN 游标必须落盘** → 计数器只存内存,换进程重跑就会从起点重发,必撞号
|
15. **EAN 游标必须落盘** → 计数器只存内存,换进程重跑就会从起点重发,必撞号
|
||||||
|
|
||||||
|
16. **没标 `*` 的字段也可能是必填** → Joom 的「运费」和「详情图语言」都没星号,
|
||||||
|
不填只给 `MYJ.error: 运费不能为空` / `请选择详情图语言且至少添加一张详情图`
|
||||||
|
17. **虚拟滚动的下拉,前几项里可能没有你要的值** → 先 `Input.insertText('英')` 过滤再点,
|
||||||
|
否则只能拿到列表第一项(Joom 详情图语言会落到「阿拉伯语」)
|
||||||
|
18. **找不到字段落点?打一个哨兵串** → 在页面手输 `PROBE-TITLE-XYZ`,再遍历所有 pinia store
|
||||||
|
的 `$state` 找这个串,立刻知道数据落在哪个 store(Joom 就是这么发现数据在 `smtChoice*` 的)
|
||||||
(Temu 第一轮就撞了一次,靠编辑页改号救回来)
|
(Temu 第一轮就撞了一次,靠编辑页改号救回来)
|
||||||
|
|
||||||
## EAN-13 台账(湖北易千亿彩 · 69803093 段)
|
## EAN-13 台账(湖北易千亿彩 · 69803093 段)
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Joom 建品配方(已跑通,批量进行中)
|
||||||
|
|
||||||
|
路由:`https://www.dianxiaomi.com/web/joomProduct/add` 列表:`/web/joomProduct/{draft=采集箱, offline=待发布}`
|
||||||
|
|
||||||
|
脚本:`jmbatch.mjs`(`node jmbatch.mjs` 全量 · `node jmbatch.mjs <pid>` 单条 · `--dry` 只构建)
|
||||||
|
断点:`jm_done.json` 逐条日志:`jm_batch.log` 实际落到的类目:`joom_route.json`
|
||||||
|
|
||||||
|
## ⭐ 关键发现:Joom 的表单数据不在 `joom*` store 里
|
||||||
|
商品主体数据落在 **`smtChoiceAddBasicStore`**(速卖通全托管的 store),
|
||||||
|
Joom 与 **aliChoice 共用同一套 `smtChoice*` store**。定位方法:在页面上手输一个
|
||||||
|
哨兵串(`PROBE-TITLE-XYZ`),再遍历所有 pinia store 的 `$state` 找它落在哪。
|
||||||
|
|
||||||
|
| 字段 | 落点 |
|
||||||
|
|---|---|
|
||||||
|
| 店铺 | `smtChoiceAddBasicStore.formState.shopIds[]` / `shopId`(**店铺是复选框组,不是下拉**) |
|
||||||
|
| 类目 | `smtChoiceAddBasicStore.formState.categoryId`(复合串 `1473…-16-2-118-2600…`) |
|
||||||
|
| 标题 / ParentSKU / 描述 / 标签 | `smtChoiceAddBasicStore.formState.{name, parentSku, description, tags[]}` |
|
||||||
|
| 价格 / 库存 / 运费 / MSRP / 报关金额 | `joomAddPriceStore.formState.{price, inventory, shipping, msrp, declaredValue}` |
|
||||||
|
| 主图 / 详情图 | `joomAddImageStore.formState.imageList[]` / `detailImageList[0].images[]`(吃 `{url,imgUrl,name,width,height}` 数组) |
|
||||||
|
| SKU / 颜色 / 尺码 | `joomSkuDataStore.formState.skuDataList[]` / `joomSkuAttrStore.formState.{colorValues,sizeValues}` |
|
||||||
|
| 自定义属性 | `smtChoiceAddAttrStore.formState.customAttrs[]` |
|
||||||
|
| 类目属性(`attrsInfo.attrsList` 10 条) | `joomAddStore.attrsInfo`(`joomAddStore` **没有 formState**) |
|
||||||
|
|
||||||
|
## 单条顺序(不能换)
|
||||||
|
1. **勾店铺**:`店铺名称` 是 `.d-checkbox-group-wrapper`,共 **85 个复选框** = `全部` + 84 家店。
|
||||||
|
勾「第一个非全部」= `Dummer-Pets`。⚠️ **别碰 `全部`**(那是铺满 84 家店)。
|
||||||
|
2. **选类目**:`产品分类` 是可搜索 `.ant-select`(不是 SHEIN/Temu 的弹窗)。点开 →
|
||||||
|
`Input.insertText(cat_kw)` → 在 `.ant-select-item-option` 里选**去掉括注后完全相等**的,
|
||||||
|
没有就退到第一个(Joom 类目比 1688 粗,实测「发圈」只能落到「发夹(Hairpins)」)。
|
||||||
|
3. 填 store(标题/ParentSKU/描述/标签 + 价格/库存/运费/报关金额 + 主图/详情图)
|
||||||
|
4. `fillRequiredSelects()`(复用 SHEIN 的兜底)→ **敏感货类型 = 普通货**
|
||||||
|
5. **详情图语言** → 目标项**标签就叫「详情图」**(不含"语言"二字):
|
||||||
|
下拉是虚拟滚动的,前 10 项没有「英语」→ 先 `Input.insertText('英')` 过滤,
|
||||||
|
再点含「英」的项(否则会落到「阿拉伯语」)
|
||||||
|
6. 保存 → `您的产品已保存到「待发布」`
|
||||||
|
|
||||||
|
## 钩子新抓出的两条「没标必填但必须填」
|
||||||
|
| 被吞的报错 | 真相 |
|
||||||
|
|---|---|
|
||||||
|
| `MYJ.error: 运费不能为空` | 运费**没有** `*`,但必须填(给 `0` 即可) |
|
||||||
|
| `MYJ.error: 请选择详情图语言且至少添加一张详情图` | 语言必选;图片 store 已赋值也要选语言 |
|
||||||
|
|
||||||
|
## 与其它平台的差异
|
||||||
|
- 店铺:Joom 是**复选框组**(多选=铺多店),SHEIN/Temu/Lazada 是下拉
|
||||||
|
- 类目:Joom 是**可搜索下拉平铺列表**,SHEIN/Temu 是「选择类目」弹窗(搜索→点结果→关弹窗)
|
||||||
|
- 数据落点:Joom 用 `smtChoice*`(与 aliChoice 同构),SHEIN/Temu 各有自己的 `shein*` / `temu*`
|
||||||
|
- **不消耗 EAN**(GTIN 非必填、`showGtin=false`)
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
{
|
||||||
|
"548459586741": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"661190598139": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"764423265986": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"685456757807": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"735147938123": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"627879474686": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"610164857063": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"708420302844": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"866537106657": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"547183997437": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"657617635303": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"641673262387": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"953869908708": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"703822717997": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"863524134410": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"694637537727": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"623011262210": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"836512561982": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"714492799386": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"591994902720": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"853414558425": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"997450831819": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"639409307567": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"902440558099": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"823838532886": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"698782391248": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"707826905566": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"1027332215847": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"682195499823": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"823560564174": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"739147684759": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"621300323670": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"601964534137": {
|
||||||
|
"ok": false,
|
||||||
|
"skipped": true,
|
||||||
|
"msg": "全平台统一跳过:18 张图全带水印、无干净图(imgs=0)"
|
||||||
|
},
|
||||||
|
"896469995553": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"944470567338": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"787261097583": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"833753630925": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"719391062058": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"857781294719": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"863384503032": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"774494945453": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"750271307455": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"726442972676": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"775057912118": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"893592264148": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"607330049419": {
|
||||||
|
"ok": true
|
||||||
|
},
|
||||||
|
"854003999648": {
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
// 1) 店铺选择控件长什么样
|
||||||
|
const shop = await c.evalJS(`(()=>{
|
||||||
|
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
if(!it) return 'no-item';
|
||||||
|
const ctrls=[...it.querySelectorAll('.ant-select,.ant-checkbox-group,.ant-radio-group')].map(x=>x.tagName+'.'+String(x.className).slice(0,40));
|
||||||
|
const txt=(it.innerText||'').replace(/\\s+/g,' ').trim().slice(0,120);
|
||||||
|
return JSON.stringify({ctrls, txt, html:it.innerHTML.replace(/\\s+/g,' ').slice(0,500)});})()`);
|
||||||
|
console.log('店铺:', S(String(shop)).slice(0,900));
|
||||||
|
// 2) 点开它
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]'; s.scrollIntoView({block:'center'});
|
||||||
|
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
console.log('坐标:', JSON.stringify(pos));
|
||||||
|
if (pos.length){
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(300);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(2500);
|
||||||
|
}
|
||||||
|
const opts = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd';
|
||||||
|
const items=[...dd.querySelectorAll('.ant-select-item-option')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim());
|
||||||
|
const multi=!!dd.querySelector('.ant-select-item-option-selected, .ant-checkbox');
|
||||||
|
return JSON.stringify({n:items.length, items:items.slice(0,12), multi});})()`);
|
||||||
|
console.log('店铺选项:', S(String(opts)).slice(0,800));
|
||||||
|
try{ await c.shot('/tmp/joom_shop.jpg'); console.log('shot ok'); }catch(e){}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const pid = process.argv[2] || '676236596821';
|
||||||
|
const it = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json','utf8'))[pid];
|
||||||
|
const IMGS = (it.imgs||[]).slice(0,6);
|
||||||
|
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await installHook(c);
|
||||||
|
// 店
|
||||||
|
console.log('店:', String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return (w.innerText||'').trim();})()`)));
|
||||||
|
await wait(6000);
|
||||||
|
// 类目
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=f.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText',{text: it.cat_kw || '发夹'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
console.log('类目:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||||
|
const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return (op.innerText||'').trim().slice(0,30);})()`)));
|
||||||
|
await wait(7000);
|
||||||
|
// store 填
|
||||||
|
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify(IMGS)};
|
||||||
|
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||||
|
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||||
|
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(pid)}; b.description=(${JSON.stringify((it.bullets||[]).join('. '))}||b.name); b.tags=(${JSON.stringify(it.keywords||'')}.split(', ').slice(0,10));
|
||||||
|
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||||
|
pr.price=String(${JSON.stringify(String(it.price_cny||'19.90'))}); pr.inventory='999'; pr.msrp=''; pr.shipping='0'; pr.declaredValue=String(${JSON.stringify(String(it.cost||'10'))});
|
||||||
|
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||||
|
im.imageList=imgs.map(mk);
|
||||||
|
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||||
|
return JSON.stringify({name:b.name.slice(0,20), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||||
|
console.log('填充:', S(String(fill)).slice(0,400));
|
||||||
|
await wait(4000);
|
||||||
|
// 列出所有带 select 的表单项,找「详情图语言」
|
||||||
|
const pairs = await c.evalJS(`(()=>{ try {
|
||||||
|
const out = [];
|
||||||
|
for (const e of document.querySelectorAll('.ant-form-item')) {
|
||||||
|
if (e.offsetParent === null) continue;
|
||||||
|
const sel = e.querySelector('.ant-select');
|
||||||
|
if (!sel) continue;
|
||||||
|
const l = e.querySelector('label');
|
||||||
|
out.push(((l ? l.innerText : '') || '?').replace(/\\s+/g, ' ').trim().slice(0, 26) + ' => ' + (sel.innerText || '').replace(/\\s+/g, ' ').trim().slice(0, 24));
|
||||||
|
}
|
||||||
|
return JSON.stringify(out.slice(0, 20));
|
||||||
|
} catch (e) { return 'ERR ' + e.message; } })()`);
|
||||||
|
console.log('带下拉的表项:', S(String(pairs)).slice(0, 1200));
|
||||||
|
|
||||||
|
// 点「详情图语言」那个 select,选第一项
|
||||||
|
const lang = JSON.parse(String(await c.evalJS(`(()=>{ try {
|
||||||
|
let target = null;
|
||||||
|
for (const e of document.querySelectorAll('.ant-form-item')) {
|
||||||
|
if (e.offsetParent === null) continue;
|
||||||
|
const l = e.querySelector('label'); const t = l ? (l.innerText || '') : '';
|
||||||
|
if (t.indexOf('详情图') >= 0 && e.querySelector('.ant-select')) { target = e; break; }
|
||||||
|
}
|
||||||
|
if (!target) return '[]';
|
||||||
|
const s = target.querySelector('.ant-select-selector'); s.scrollIntoView({ block: 'center' });
|
||||||
|
const r = s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x + r.width / 2), Math.round(r.y + r.height / 2)]);
|
||||||
|
} catch (e) { return '[]'; } })()`)));
|
||||||
|
console.log('语言控件坐标:', JSON.stringify(lang));
|
||||||
|
if (lang.length) {
|
||||||
|
await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: lang[0], y: lang[1], buttons: 0 }); await wait(300);
|
||||||
|
for (const t of ['mousePressed', 'mouseReleased']) await c.send('Input.dispatchMouseEvent', { type: t, x: lang[0], y: lang[1], button: 'left', clickCount: 1, buttons: 1 });
|
||||||
|
await wait(2000);
|
||||||
|
console.log('选了:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||||
|
const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return (op.innerText||'').trim().slice(0,20);})()`)));
|
||||||
|
await wait(3000);
|
||||||
|
}
|
||||||
|
// 保存
|
||||||
|
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
|
||||||
|
await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
|
||||||
|
const b=xs.find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()))||xs.find(e=>/保存/.test(e.innerText||''));
|
||||||
|
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
|
||||||
|
await wait(13000);
|
||||||
|
console.log('hook:', String(await readHook(c)).slice(-260));
|
||||||
|
const res = await c.evalJS(`JSON.stringify({mods:[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,120))})`);
|
||||||
|
console.log('结果:', S(String(res)).slice(0,400));
|
||||||
|
try { await c.shot('/tmp/joom_lang.jpg'); console.log('shot ok'); } catch (e) {}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
// 全部店铺标签
|
||||||
|
const labels = await c.evalJS(`(()=>{
|
||||||
|
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const ls=[...it.querySelectorAll('.ant-checkbox-wrapper')].map(w=>(w.innerText||'').replace(/\\s+/g,' ').trim());
|
||||||
|
return JSON.stringify({n:ls.length, first:ls.slice(0,10), last:ls.slice(-3)});})()`);
|
||||||
|
console.log('店铺:', S(String(labels)).slice(0,600));
|
||||||
|
// 勾第一个真实店铺(跳过「全部」)
|
||||||
|
const tick = await c.evalJS(`(()=>{
|
||||||
|
const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const ws=[...it.querySelectorAll('.ant-checkbox-wrapper')];
|
||||||
|
const target=ws.find(w=>{const t=(w.innerText||'').trim(); return t && !/^全部/.test(t);});
|
||||||
|
if(!target) return 'nf';
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) target.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const inp=target.querySelector('input[type=checkbox]'); if(inp&&!inp.checked) inp.click();
|
||||||
|
return 'ticked:'+(target.innerText||'').trim();})()`);
|
||||||
|
console.log('勾选:', String(tick));
|
||||||
|
await wait(6000);
|
||||||
|
// 选类目
|
||||||
|
const cat = await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类|分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
if(!it) return 'no-item';
|
||||||
|
const b=[...it.querySelectorAll('*')].find(e=>(e.innerText||'').trim()==='选择分类'&&e.children.length===0);
|
||||||
|
if(!b) return 'no-btn';
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 'opened';})()`);
|
||||||
|
console.log('类目按钮:', String(cat));
|
||||||
|
await wait(3500);
|
||||||
|
const modal = await c.evalJS(`(()=>{
|
||||||
|
const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!m) return 'no-modal';
|
||||||
|
const inp=[...m.querySelectorAll('input')].map(e=>e.placeholder||'').slice(0,4);
|
||||||
|
const txt=(m.innerText||'').replace(/\\s+/g,' ').trim().slice(0,220);
|
||||||
|
return JSON.stringify({inp, txt});})()`);
|
||||||
|
console.log('类目弹窗:', S(String(modal)).slice(0,700));
|
||||||
|
// 搜一个关键词
|
||||||
|
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
const inp=m.querySelector('input[placeholder="搜索分类名称"]')||m.querySelector('input'); if(!inp) return 'no-input';
|
||||||
|
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
|
||||||
|
setter.call(inp,'发圈'); inp.dispatchEvent(new Event('input',{bubbles:true})); return 1;})()`);
|
||||||
|
await wait(600);
|
||||||
|
await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
const b=[...m.querySelectorAll('button')].find(x=>/^\s*搜索\s*$/.test((x.innerText||'').trim()));
|
||||||
|
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
|
||||||
|
await wait(4000);
|
||||||
|
const res = await c.evalJS(`(()=>{const m=[...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!m) return 'closed';
|
||||||
|
const items=[...m.querySelectorAll('.search-result-item')].map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,70));
|
||||||
|
return JSON.stringify({n:items.length, items:items.slice(0,5)});})()`);
|
||||||
|
console.log('类目结果:', S(String(res)).slice(0,600));
|
||||||
|
try{ await c.shot('/tmp/joom_cat.jpg'); console.log('shot ok'); }catch(e){}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||||
|
await wait(6000);
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]'; s.scrollIntoView({block:'center'});
|
||||||
|
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
console.log('分类select坐标:', JSON.stringify(pos));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(300);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(3000);
|
||||||
|
const d = await c.evalJS(`(()=>{
|
||||||
|
const dd=[...document.querySelectorAll('.ant-select-dropdown,.ant-cascader-menus,.ant-cascader-dropdown')].filter(e=>e.offsetParent!==null);
|
||||||
|
const info=dd.map(e=>({cls:String(e.className).slice(0,60), txt:(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,300)}));
|
||||||
|
const menus=[...document.querySelectorAll('.ant-cascader-menu')].length;
|
||||||
|
const inputs=[...document.querySelectorAll('.ant-select-dropdown input,.ant-cascader-menus input')].filter(e=>e.offsetParent!==null).map(e=>e.placeholder||'');
|
||||||
|
return JSON.stringify({n:info.length, info, menus, inputs});})()`);
|
||||||
|
console.log('下拉:', S(String(d)).slice(0,1200));
|
||||||
|
try{ await c.shot('/tmp/joom_cat3.jpg'); console.log('shot ok'); }catch(e){}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
// 勾店
|
||||||
|
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||||
|
await wait(6000);
|
||||||
|
// 点分类 select 并输入关键词
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]'; s.scrollIntoView({block:'center'});
|
||||||
|
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText',{text:'发圈'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
const pick = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd';
|
||||||
|
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||||
|
if(!ops.length) return 'no-op';
|
||||||
|
const t=ops[0]; const r=t.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) t.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return 'picked:'+(t.innerText||'').trim().slice(0,30)+' (共'+ops.length+'项)';})()`);
|
||||||
|
console.log('类目:', String(pick));
|
||||||
|
await wait(6000);
|
||||||
|
// dump store 状态
|
||||||
|
const st = await c.evalJS(`(()=>{const p=${P};
|
||||||
|
const ids=(p._s instanceof Map?[...p._s.keys()]:Object.keys(p._s)).filter(x=>/joom/i.test(x));
|
||||||
|
const out={};
|
||||||
|
for(const id of ids){ try{ const s=(p._s.get?p._s.get(id):p._s[id]).$state; const f=s.formState||{};
|
||||||
|
const keep={}; for(const k of Object.keys(f)){ const v=f[k]; if(v===null||v===undefined||v===''||(Array.isArray(v)&&!v.length)) continue;
|
||||||
|
keep[k]=Array.isArray(v)?('['+v.length+'] '+(v[0]?JSON.stringify(v[0]).slice(0,80):'')):(typeof v==='object'?JSON.stringify(v).slice(0,80):String(v).slice(0,50));
|
||||||
|
if(Object.keys(keep).length>12) break; }
|
||||||
|
const extra=Object.keys(s).filter(k=>k!=='formState').slice(0,4);
|
||||||
|
out[id]={...keep, _keys:extra}; }catch(e){ out[id]='ERR'; } }
|
||||||
|
return JSON.stringify(out);})()`);
|
||||||
|
console.log('stores:', S(String(st)).slice(0,1800));
|
||||||
|
// 可见表单项(带必填标记)
|
||||||
|
const items = await c.evalJS(`JSON.stringify([...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).slice(0,22).map(e=>{
|
||||||
|
const l=e.querySelector('label'); const req=l?/required/.test(String(l.className)):'';
|
||||||
|
return (req?'*':'')+((e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,46));}))`);
|
||||||
|
console.log('表单项:', S(String(items)).slice(0,1400));
|
||||||
|
try{ await c.shot('/tmp/joom_form.jpg'); console.log('shot ok'); }catch(e){}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||||
|
await wait(6000);
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText',{text:'发夹'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); return 1;})()`);
|
||||||
|
await wait(7000);
|
||||||
|
// 1) joomAddStore 全量结构
|
||||||
|
const st = await c.evalJS(`(()=>{const p=${P};
|
||||||
|
const s=p._s.get('joomAddStore').$state;
|
||||||
|
const keys=Object.keys(s);
|
||||||
|
const pi=s.productInfo||{};
|
||||||
|
const pick=(o,d=0)=>{ if(d>2||!o||typeof o!=='object') return o; const out={}; let n=0;
|
||||||
|
for(const k of Object.keys(o)){ const v=o[k]; out[k]=(v&&typeof v==='object')?(Array.isArray(v)?'['+v.length+']':pick(v,d+1)):String(v).slice(0,40); if(++n>14) break; } return out; };
|
||||||
|
return JSON.stringify({keys, productInfo:pick(pi), attrsInfo:pick(s.attrsInfo), sharedData:pick(s.sharedData)});})()`);
|
||||||
|
console.log('joomAddStore:', S(String(st)).slice(0,1500));
|
||||||
|
// 2) 价格/库存/SKU/图片 控件
|
||||||
|
const ctrls = await c.evalJS(`(()=>{const out={};
|
||||||
|
const bag=(re)=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>{const l=e.querySelector('label'); return l&&new RegExp(re).test(l.innerText||'');});
|
||||||
|
if(!it) return 'no-item';
|
||||||
|
return {txt:(it.innerText||'').replace(/\\s+/g,' ').trim().slice(0,60),
|
||||||
|
inputs:[...it.querySelectorAll('input')].filter(i=>i.offsetParent!==null).map(i=>({ph:i.placeholder||'', name:i.name||'', cls:String(i.className).slice(0,34)})).slice(0,5),
|
||||||
|
selects:[...it.querySelectorAll('.ant-select')].length,};};
|
||||||
|
out['价格']=bag('^\\*?价格'); out['库存']=bag('库存'); out['ParentSKU']=bag('ParentSKU'); out['产品图']=bag('产品图'); out['敏感货类型']=bag('敏感货类型');
|
||||||
|
const sku=p=>0;
|
||||||
|
return JSON.stringify(out);})()`);
|
||||||
|
console.log('控件:', S(String(ctrls)).slice(0,1600));
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||||
|
await wait(6000);
|
||||||
|
// 类目
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText',{text:'发夹'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); return 1;})()`);
|
||||||
|
await wait(6000);
|
||||||
|
// 在标题输入框里打字(走真实键盘)
|
||||||
|
const tpos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/^\\s*\\*?产品标题/.test((e.innerText||'').trim()));
|
||||||
|
const inp=it?it.querySelector('input,textarea'):null; if(!inp) return '[]'; inp.scrollIntoView({block:'center'});
|
||||||
|
inp.setAttribute('data-probe','1'); const r=inp.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
console.log('标题框坐标:', JSON.stringify(tpos));
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:tpos[0],y:tpos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(400);
|
||||||
|
await c.send('Input.insertText',{text:'PROBE-TITLE-XYZ'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
// 全 store 搜索这个字符串
|
||||||
|
const found = await c.evalJS(`(()=>{const p=${P}; const needle='PROBE-TITLE-XYZ'; const hits=[];
|
||||||
|
const ids=(p._s instanceof Map?[...p._s.keys()]:Object.keys(p._s));
|
||||||
|
for(const id of ids){ try{ const s=(p._s.get?p._s.get(id):p._s[id]).$state;
|
||||||
|
const js=JSON.stringify(s); if(js&&js.includes(needle)) hits.push(id+':'+js.slice(Math.max(0,js.indexOf(needle)-70), js.indexOf(needle)+40).replace(/\\\\/g,'')); }catch(e){} }
|
||||||
|
const domVal=(document.querySelector('input[data-probe="1"]')||{}).value||'';
|
||||||
|
// joomAddStore 的非 $state 属性
|
||||||
|
let extra=[];
|
||||||
|
try{ const st=p._s.get('joomAddStore'); extra=Object.keys(st).filter(k=>k!=='$state'&&k!=='_s'&&!/^\\$/.test(k)).slice(0,18); }catch(e){}
|
||||||
|
return JSON.stringify({hits, domVal, joomAddStoreOwnKeys:extra});})()`);
|
||||||
|
console.log('命中:', S(String(found)).slice(0,1600));
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return 1;})()`);
|
||||||
|
await wait(6000);
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText',{text:'发夹'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2})); return 1;})()`);
|
||||||
|
await wait(7000);
|
||||||
|
const d = await c.evalJS(`(()=>{const p=${P};
|
||||||
|
const ids=(p._s instanceof Map?[...p._s.keys()]:Object.keys(p._s)).filter(x=>/smtChoice|joom|image|Image/i.test(x));
|
||||||
|
const out={};
|
||||||
|
const fmt=(v,d=0)=>{ if(v===null||v===undefined) return String(v); if(Array.isArray(v)) return '['+v.length+'] '+(v[0]?fmt(v[0],d+1):'');
|
||||||
|
if(typeof v==='object'){ if(d>1) return '{...}'; const o={}; let n=0; for(const k of Object.keys(v)){ o[k]=fmt(v[k],d+1); if(++n>16){o['…']='';break;} } return o; } return String(v).slice(0,46); };
|
||||||
|
for(const id of ids){ try{ const s=(p._s.get?p._s.get(id):p._s[id]).$state; const f=s.formState;
|
||||||
|
out[id]= f ? fmt(f) : '(no formState) keys='+Object.keys(s).slice(0,10).join(','); }catch(e){ out[id]='ERR'; } }
|
||||||
|
return JSON.stringify(out);})()`);
|
||||||
|
console.log('stores:', JSON.stringify(JSON.parse(String(d)), null, 1).slice(0,3000));
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const pid = process.argv[2] || '676236596821';
|
||||||
|
const it = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json','utf8'))[pid];
|
||||||
|
const IMGS = (it.imgs||[]).slice(0,6);
|
||||||
|
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await installHook(c);
|
||||||
|
// 店
|
||||||
|
console.log('店:', String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return (w.innerText||'').trim();})()`)));
|
||||||
|
await wait(6000);
|
||||||
|
// 类目
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=f.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText',{text: it.cat_kw || '发夹'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
console.log('类目:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||||
|
const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return (op.innerText||'').trim().slice(0,30);})()`)));
|
||||||
|
await wait(7000);
|
||||||
|
// store 填
|
||||||
|
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify(IMGS)};
|
||||||
|
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||||
|
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||||
|
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(pid)}; b.description=(${JSON.stringify((it.bullets||[]).join('. '))}||b.name); b.tags=(${JSON.stringify(it.keywords||'')}.split(', ').slice(0,10));
|
||||||
|
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||||
|
pr.price=String(${JSON.stringify(String(it.price_cny||'19.90'))}); pr.inventory='999'; pr.msrp=''; pr.shipping='0'; pr.declaredValue=String(${JSON.stringify(String(it.cost||'10'))});
|
||||||
|
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||||
|
im.imageList=imgs.map(mk);
|
||||||
|
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||||
|
return JSON.stringify({name:b.name.slice(0,20), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||||
|
console.log('填充:', S(String(fill)).slice(0,400));
|
||||||
|
await wait(4000);
|
||||||
|
const attrs = await fillRequiredSelects(c);
|
||||||
|
console.log('必填属性:', S(String(attrs)).slice(0,300));
|
||||||
|
await wait(2000);
|
||||||
|
// dump(简化版,避免页面里复杂表达式报错)
|
||||||
|
const d = await c.evalJS(`(()=>{ try {
|
||||||
|
const p = document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
|
||||||
|
const im = p._s.get('joomAddImageStore').$state;
|
||||||
|
const labels = [];
|
||||||
|
const all = document.querySelectorAll('.ant-form-item');
|
||||||
|
for (const e of all) {
|
||||||
|
if (e.offsetParent === null) continue;
|
||||||
|
const l = e.querySelector('label');
|
||||||
|
const t = l ? (l.innerText || '') : '';
|
||||||
|
if (t.indexOf('图') >= 0 || t.indexOf('语言') >= 0) {
|
||||||
|
labels.push(t.replace(/\\s+/g, ' ').trim().slice(0, 30) + ' | selects=' + e.querySelectorAll('.ant-select').length + ' imgs=' + e.querySelectorAll('img').length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JSON.stringify({ keys: Object.keys(im), imageList: JSON.stringify(im.imageList).slice(0, 160),
|
||||||
|
detail: JSON.stringify(im.detailImageList).slice(0, 260), labels: labels.slice(0, 12) });
|
||||||
|
} catch (err) { return 'INNER-ERR ' + err.message; } })()`);
|
||||||
|
console.log('图片区:', S(String(d)).slice(0, 1400));
|
||||||
|
try { await c.shot('/tmp/joom_img.jpg'); console.log('shot ok'); } catch (e) {}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const pid = process.argv[2] || '676236596821';
|
||||||
|
const it = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json','utf8'))[pid];
|
||||||
|
const IMGS = (it.imgs||[]).slice(0,6);
|
||||||
|
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await installHook(c);
|
||||||
|
// 店
|
||||||
|
console.log('店:', String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const w=[...it.querySelectorAll('.ant-checkbox-wrapper')].find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click(); return (w.innerText||'').trim();})()`)));
|
||||||
|
await wait(6000);
|
||||||
|
// 类目
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const f=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null).find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=f.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
await c.send('Input.dispatchMouseEvent',{type:'mouseMoved',x:pos[0],y:pos[1],buttons:0}); await wait(250);
|
||||||
|
for(const t of ['mousePressed','mouseReleased']) await c.send('Input.dispatchMouseEvent',{type:t,x:pos[0],y:pos[1],button:'left',clickCount:1,buttons:1});
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText',{text: it.cat_kw || '发夹'}).catch(()=>{});
|
||||||
|
await wait(2500);
|
||||||
|
console.log('类目:', String(await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd'; const op=[...dd.querySelectorAll('.ant-select-item-option')][0]; if(!op) return 'no-op';
|
||||||
|
const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return (op.innerText||'').trim().slice(0,30);})()`)));
|
||||||
|
await wait(7000);
|
||||||
|
// store 填
|
||||||
|
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify(IMGS)};
|
||||||
|
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||||
|
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||||
|
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(pid)}; b.description=(${JSON.stringify((it.bullets||[]).join('. '))}||b.name); b.tags=(${JSON.stringify(it.keywords||'')}.split(', ').slice(0,10));
|
||||||
|
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||||
|
pr.price=String(${JSON.stringify(String(it.price_cny||'19.90'))}); pr.inventory='999'; pr.msrp=''; pr.shipping='0'; pr.declaredValue=String(${JSON.stringify(String(it.cost||'10'))});
|
||||||
|
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||||
|
im.imageList=imgs.map(mk);
|
||||||
|
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||||
|
return JSON.stringify({name:b.name.slice(0,20), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||||
|
console.log('填充:', S(String(fill)).slice(0,400));
|
||||||
|
await wait(4000);
|
||||||
|
const dumpImgs = async (tag) => {
|
||||||
|
const r = await c.evalJS(`(()=>{ try {
|
||||||
|
const p = document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
|
||||||
|
const st = p._s.get('joomAddImageStore').$state;
|
||||||
|
const f = st.formState || {};
|
||||||
|
return JSON.stringify({ keys: Object.keys(f), imageList: Array.isArray(f.imageList) ? f.imageList.length : 'undef',
|
||||||
|
detail: Array.isArray(f.detailImageList) && f.detailImageList[0] && Array.isArray(f.detailImageList[0].images) ? f.detailImageList[0].images.length : 'undef',
|
||||||
|
domImgs: document.querySelectorAll('img').length });
|
||||||
|
} catch (e) { return 'ERR ' + e.message; } })()`);
|
||||||
|
console.log(tag, S(String(r)).slice(0, 300));
|
||||||
|
};
|
||||||
|
await wait(800); await dumpImgs('填完立刻:');
|
||||||
|
await wait(6000); await dumpImgs('6 秒后:');
|
||||||
|
const attrs = await fillRequiredSelects(c);
|
||||||
|
console.log('必填属性:', S(String(attrs)).slice(0, 200));
|
||||||
|
await wait(2500); await dumpImgs('填必填属性后:');
|
||||||
|
try { await c.shot('/tmp/joom_img2.jpg'); console.log('shot ok'); } catch (e) {}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { newPage, closePage, wait } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { SAN as S } from '/tmp/cdp3.mjs';
|
||||||
|
const c = await newPage('https://www.dianxiaomi.com/web/joomProduct/offline');
|
||||||
|
await wait(13000);
|
||||||
|
const o = await c.evalJS(`(()=>{
|
||||||
|
const body=(document.body.innerText||'').replace(/\s+/g,' ');
|
||||||
|
const m=body.match(/共\s*\d+\s*条/);
|
||||||
|
const rows=[...document.querySelectorAll('.vxe-body--row')].slice(0,3).map(r=>(r.innerText||'').replace(/\s+/g,' ').trim().slice(0,90));
|
||||||
|
const n=document.querySelectorAll('.vxe-body--row').length;
|
||||||
|
return JSON.stringify({total:m?m[0]:'', n, rows});})()`);
|
||||||
|
console.log(S(String(o)).slice(0,700));
|
||||||
|
try{ await c.shot('/tmp/joom_offline.jpg'); console.log('shot ok'); }catch(e){}
|
||||||
|
await closePage(c); process.exit(0);
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
/**
|
||||||
|
* Joom 批量建品(84 家店取第一家;与 aliChoice 共用 smtChoice* store)
|
||||||
|
*
|
||||||
|
* 用法: node jmbatch.mjs [--dry] [pid...]
|
||||||
|
*
|
||||||
|
* 单条顺序:勾店铺(复选框) → 选类目(可搜索 ant-select) → 标题/ParentSKU/描述/标签
|
||||||
|
* → 价格/库存/运费/报关金额 → 图片(主图+详情图) → 敏感货类型 → 详情图语言 → 保存
|
||||||
|
*
|
||||||
|
* 被钩子抓出来的两条「没标必填但必须填」:运费 shipping、详情图语言(标签就叫「详情图」)
|
||||||
|
*/
|
||||||
|
import { readFileSync, writeFileSync, appendFileSync } from 'fs';
|
||||||
|
import { newPage, closePage, wait, installHook, readHook } from '/home/admin9webs/shein建品/sh_lib.mjs';
|
||||||
|
import { fillRequiredSelects } from '/home/admin9webs/shein建品/sh_build.mjs';
|
||||||
|
|
||||||
|
const DIR = '/home/admin9webs/joom建品';
|
||||||
|
const P = `document.querySelector('#app').__vue_app__.config.globalProperties.$pinia`;
|
||||||
|
const INPUT = JSON.parse(readFileSync('/home/admin9webs/tiktok建品/tk_input.json', 'utf8'));
|
||||||
|
const DONE_F = `${DIR}/jm_done.json`;
|
||||||
|
const LOG_F = `${DIR}/jm_batch.log`;
|
||||||
|
const SKIP = new Set([
|
||||||
|
'676236596821', // 首条手工测试已进待发布(详情图语言选了阿拉伯语),不再重复建
|
||||||
|
'601964534137', // 遥控器保护套:18 张图全带水印、无干净图 → 所有平台统一跳过
|
||||||
|
]);
|
||||||
|
|
||||||
|
const argv = process.argv.slice(2);
|
||||||
|
const dry = argv.includes('--dry');
|
||||||
|
const only = argv.filter(a => !a.startsWith('--'));
|
||||||
|
let tasks = Object.keys(INPUT)
|
||||||
|
.filter(pid => (INPUT[pid].imgs || []).length > 0) // 没有干净图的直接不参与(全平台统一规则)
|
||||||
|
.map(pid => ({ pid }));
|
||||||
|
if (only.length) tasks = tasks.filter(t => only.includes(t.pid)); else tasks = tasks.filter(t => !SKIP.has(t.pid));
|
||||||
|
let done = {}; try { done = JSON.parse(readFileSync(DONE_F, 'utf8')); } catch {}
|
||||||
|
let ROUTE = {}; try { ROUTE = JSON.parse(readFileSync(`${DIR}/joom_route.json`, 'utf8')); } catch {}
|
||||||
|
const log = m => { const l = `[${new Date().toISOString().slice(11, 19)}] ${m}`; console.log(l); appendFileSync(LOG_F, l + '\n'); };
|
||||||
|
|
||||||
|
/** 勾第一家真实店铺(Joom 店名没有圈号,取 DOM 第一个非「全部」,即 Dummer-Pets) */
|
||||||
|
async function tickShop(c, want = null) {
|
||||||
|
return c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
|
||||||
|
.find(e=>/店铺名称/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
if(!it) return 'no-item';
|
||||||
|
const ws=[...it.querySelectorAll('.ant-checkbox-wrapper')];
|
||||||
|
const want=${JSON.stringify(want)};
|
||||||
|
const w=want ? ws.find(x=>(x.innerText||'').trim()===want) : ws.find(x=>{const t=(x.innerText||'').trim(); return t&&!/^全部/.test(t);});
|
||||||
|
if(!w) return 'no-shop';
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) w.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));
|
||||||
|
const i=w.querySelector('input'); if(i&&!i.checked) i.click();
|
||||||
|
return 'ticked:'+(w.innerText||'').trim();})()`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 类目:点开可搜索下拉 → 输关键词 → 选名字匹配的项(优先去掉括注后完全相等) */
|
||||||
|
async function pickCategory(c, kw) {
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
|
||||||
|
.find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it?it.querySelector('.ant-select-selector'):null; if(!s) return '[]';
|
||||||
|
s.scrollIntoView({block:'center'}); const r=s.getBoundingClientRect();
|
||||||
|
return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]);})()`)));
|
||||||
|
if (!pos.length) return 'no-select';
|
||||||
|
await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: pos[0], y: pos[1], buttons: 0 }); await wait(250);
|
||||||
|
for (const t of ['mousePressed', 'mouseReleased'])
|
||||||
|
await c.send('Input.dispatchMouseEvent', { type: t, x: pos[0], y: pos[1], button: 'left', clickCount: 1, buttons: 1 });
|
||||||
|
await wait(1500);
|
||||||
|
await c.send('Input.insertText', { text: kw }).catch(() => {});
|
||||||
|
await wait(2600);
|
||||||
|
return c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd';
|
||||||
|
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||||
|
if(!ops.length) return 'no-option';
|
||||||
|
const clean=s=>(s||'').replace(/\\s+/g,'').split('(')[0];
|
||||||
|
const op=ops.find(e=>clean(e.innerText)===clean(${JSON.stringify(kw)}))||ops[0];
|
||||||
|
const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return 'picked:'+(op.innerText||'').trim().slice(0,26)+'/'+ops.length;})()`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 详情图语言:标签就是「详情图」,优先英语 */
|
||||||
|
async function pickDetailLang(c) {
|
||||||
|
const pos = JSON.parse(String(await c.evalJS(`(()=>{
|
||||||
|
for(const e of document.querySelectorAll('.ant-form-item')){
|
||||||
|
if(e.offsetParent===null) continue;
|
||||||
|
const l=e.querySelector('label'); const t=l?(l.innerText||''):'';
|
||||||
|
if(t.indexOf('详情图')>=0&&e.querySelector('.ant-select')){
|
||||||
|
const s=e.querySelector('.ant-select-selector'); s.scrollIntoView({block:'center'});
|
||||||
|
const r=s.getBoundingClientRect(); return JSON.stringify([Math.round(r.x+r.width/2),Math.round(r.y+r.height/2)]); } }
|
||||||
|
return '[]';})()`)));
|
||||||
|
if (!pos.length) return 'no-lang';
|
||||||
|
await c.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: pos[0], y: pos[1], buttons: 0 }); await wait(250);
|
||||||
|
for (const t of ['mousePressed', 'mouseReleased'])
|
||||||
|
await c.send('Input.dispatchMouseEvent', { type: t, x: pos[0], y: pos[1], button: 'left', clickCount: 1, buttons: 1 });
|
||||||
|
await wait(2000);
|
||||||
|
// 列表是虚拟滚动的,前 10 项里没有「英语」→ 先试着输「英」过滤,不行就滚到底
|
||||||
|
await c.send('Input.insertText', { text: '英' }).catch(() => {});
|
||||||
|
await wait(1500);
|
||||||
|
let hit = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd';
|
||||||
|
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||||
|
const names=ops.map(e=>(e.innerText||'').trim()).filter(Boolean);
|
||||||
|
const op=ops.find(e=>/英|English/i.test(e.innerText||''));
|
||||||
|
if(!op) return 'none|'+names.slice(0,6).join('/');
|
||||||
|
const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return 'picked:'+(op.innerText||'').trim();})()`);
|
||||||
|
if (String(hit).startsWith('none')) {
|
||||||
|
hit = await c.evalJS(`(()=>{const dd=[...document.querySelectorAll('.ant-select-dropdown')].filter(e=>e.offsetParent!==null).pop();
|
||||||
|
if(!dd) return 'no-dd';
|
||||||
|
const sc=dd.querySelector('.rc-virtual-list-holder')||dd; sc.scrollTop=sc.scrollHeight;
|
||||||
|
const ops=[...dd.querySelectorAll('.ant-select-item-option')].filter(e=>e.offsetParent!==null);
|
||||||
|
const op=ops.find(e=>/英|English/i.test(e.innerText||''));
|
||||||
|
if(!op) return 'still-none|'+ops.map(e=>(e.innerText||'').trim()).slice(-6).join('/');
|
||||||
|
const r=op.getBoundingClientRect();
|
||||||
|
for(const ev of ['mousedown','mouseup','click']) op.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window,clientX:r.x+r.width/2,clientY:r.y+r.height/2}));
|
||||||
|
return 'picked(滚动):'+(op.innerText||'').trim();})()`);
|
||||||
|
}
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Joom 批量开始:任务 ${tasks.length},dry=${dry}`);
|
||||||
|
for (const t of tasks) {
|
||||||
|
if (!dry && done[t.pid] && done[t.pid].ok) { log(`跳过 ${t.pid}(已建)`); continue; }
|
||||||
|
const it = INPUT[t.pid]; if (!it) { log(`✗ ${t.pid} 无输入`); continue; }
|
||||||
|
log(`── ${t.pid} kw=${it.cat_kw || '?'} :: ${String(it.title).slice(0, 40)}`);
|
||||||
|
let c;
|
||||||
|
try {
|
||||||
|
c = await newPage('https://www.dianxiaomi.com/web/joomProduct/add');
|
||||||
|
await wait(12000);
|
||||||
|
await installHook(c);
|
||||||
|
const shop = await tickShop(c, process.env.JOOM_SHOP || null);
|
||||||
|
if (!String(shop).startsWith('ticked:')) throw new Error('选店铺失败:' + shop);
|
||||||
|
await wait(6000);
|
||||||
|
const cat = await pickCategory(c, it.cat_kw || '发夹');
|
||||||
|
log(` 店=${shop.slice(7)} 类目=${cat}`);
|
||||||
|
if (!String(cat).startsWith('picked:')) throw new Error('选类目失败:' + cat);
|
||||||
|
// 记录实际落到的类目名(Joom 类目比 1688 粗,kw 常常只能近似匹配,事后要人工抽查)
|
||||||
|
const catName = String(await c.evalJS(`(()=>{const it=[...document.querySelectorAll('.ant-form-item')].filter(e=>e.offsetParent!==null)
|
||||||
|
.find(e=>/产品分类/.test((e.querySelector('label')||{}).innerText||''));
|
||||||
|
const s=it?it.querySelector('.ant-select'):null; return s?(s.innerText||'').replace(/\\s+/g,' ').trim().slice(0,40):'';})()`));
|
||||||
|
ROUTE[t.pid] = { shop: shop.slice(7), kw: it.cat_kw || '', cat: catName };
|
||||||
|
writeFileSync(`${DIR}/joom_route.json`, JSON.stringify(ROUTE, null, 1));
|
||||||
|
await wait(7000);
|
||||||
|
const fill = await c.evalJS(`(()=>{const p=${P}; const imgs=${JSON.stringify((it.imgs || []).slice(0, 6))};
|
||||||
|
const mk=(u,i)=>({url:u,imgUrl:u,name:'img'+(i+1)+'.jpg',width:800,height:800});
|
||||||
|
const b=p._s.get('smtChoiceAddBasicStore').$state.formState;
|
||||||
|
b.name=${JSON.stringify(it.title)}; b.parentSku='JM-'+${JSON.stringify(t.pid)};
|
||||||
|
b.description=(${JSON.stringify((it.bullets || []).join('. '))}||b.name);
|
||||||
|
b.tags=String(${JSON.stringify(it.keywords || '')}).split(', ').slice(0,10);
|
||||||
|
const pr=p._s.get('joomAddPriceStore').$state.formState;
|
||||||
|
pr.price=String(${JSON.stringify(String(it.price_cny || '19.90'))}); pr.inventory='999';
|
||||||
|
pr.shipping='0'; pr.msrp=''; pr.declaredValue=String(${JSON.stringify(String(it.cost || '10'))});
|
||||||
|
const im=p._s.get('joomAddImageStore').$state.formState;
|
||||||
|
im.imageList=imgs.map(mk);
|
||||||
|
if(Array.isArray(im.detailImageList)&&im.detailImageList[0]) im.detailImageList[0].images=imgs.map(mk);
|
||||||
|
return JSON.stringify({name:b.name.slice(0,18), price:pr.price, imgs:im.imageList.length, cat:b.categoryId});})()`);
|
||||||
|
log(` 填充: ${fill}`);
|
||||||
|
await wait(3500);
|
||||||
|
const attrs = await fillRequiredSelects(c);
|
||||||
|
if (attrs !== 'none') log(` 必填属性: ${attrs}`);
|
||||||
|
await wait(1500);
|
||||||
|
log(` 详情图语言: ${await pickDetailLang(c)}`);
|
||||||
|
await wait(2500);
|
||||||
|
if (dry) { delete done[t.pid]; log(' ✓ dry 构建完成(未保存)'); await closePage(c); continue; }
|
||||||
|
await c.evalJS(`window.scrollTo(0,0)`); await wait(600);
|
||||||
|
await c.evalJS(`(()=>{const xs=[...document.querySelectorAll('button')].filter(e=>e.offsetParent!==null);
|
||||||
|
const b=xs.find(e=>/^\\s*保存\\s*$/.test((e.innerText||'').trim()))||xs.find(e=>/保存/.test(e.innerText||''));
|
||||||
|
if(b) for(const ev of ['mousedown','mouseup','click']) b.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window})); return 1;})()`);
|
||||||
|
await wait(13000);
|
||||||
|
const mods = String(await c.evalJS(`JSON.stringify([...document.querySelectorAll('.ant-modal')].filter(e=>e.offsetParent!==null).map(e=>(e.innerText||'').replace(/\\s+/g,' ').trim().slice(0,110)))`));
|
||||||
|
const cap = await readHook(c);
|
||||||
|
const ok = /已保存|保存成功|待发布/.test(mods);
|
||||||
|
done[t.pid] = ok ? { ok: true } : { ok: false, mods: mods.slice(0, 160), hook: String(cap).slice(-220) };
|
||||||
|
log(ok ? ' ✓ 已保存到待发布' : ` ✗ 失败 ${String(cap).slice(-180)}`);
|
||||||
|
} catch (e) {
|
||||||
|
done[t.pid] = { ok: false, msg: String(e).slice(0, 240) };
|
||||||
|
log(` ✗ 异常: ${String(e).slice(0, 220)}`);
|
||||||
|
} finally {
|
||||||
|
if (!dry) writeFileSync(DONE_F, JSON.stringify(done, null, 1));
|
||||||
|
if (c) await closePage(c);
|
||||||
|
await wait(2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log(`Joom 批量结束:成功 ${Object.values(done).filter(x => x.ok).length}/${tasks.length}`);
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
{
|
||||||
|
"676236596821": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"548459586741": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "口哨",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"661190598139": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发圈",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"764423265986": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "温度计",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"685456757807": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "毛线",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"735147938123": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发箍",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"627879474686": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "袜子",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"610164857063": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发带",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"708420302844": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "钥匙扣",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"866537106657": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "纽扣",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"547183997437": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "项链",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"657617635303": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "收纳盒",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"641673262387": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "首饰袋",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"953869908708": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "窗贴",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"703822717997": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "笔夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"863524134410": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "贴纸",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"694637537727": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "保温袋",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"623011262210": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "窗贴",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"836512561982": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "化妆包",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"714492799386": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "化妆包",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"591994902720": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "笔袋",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"853414558425": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "手机挂绳",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"997450831819": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "贴纸",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"639409307567": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "墙贴",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"902440558099": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发圈",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"823838532886": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "卡套",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"698782391248": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "贴纸",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"707826905566": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "卡包",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"1027332215847": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "收纳盒",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"682195499823": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "笔袋",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"823560564174": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发圈",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"739147684759": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "纽扣",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"621300323670": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "胶带座",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"601964534137": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "遥控器保护套",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"896469995553": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "钥匙扣",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"944470567338": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"787261097583": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"833753630925": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发箍",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"719391062058": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "收纳盒",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"857781294719": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"863384503032": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"774494945453": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "钥匙扣",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"750271307455": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "钥匙扣",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"726442972676": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"775057912118": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "钥匙扣",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"893592264148": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发夹",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"607330049419": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发圈",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
},
|
||||||
|
"854003999648": {
|
||||||
|
"shop": "Dummer-Pets",
|
||||||
|
"kw": "发圈",
|
||||||
|
"cat": "发夹(Hairpins) 发夹(Hairpins)"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user