Commit Graph
39 Commits
Author SHA1 Message Date
ila 3c3ed47b14 fix: 淘宝与1688共用专属Chrome配置 (#37) 2026-10-07 16:14:05 +08:00
ila 2d125a655e feat: 增加 1688 视频来源及逐条内容检测 (#37) 2026-10-07 15:51:28 +08:00
QiuSW 6366462be2 feat: 展示批量上传逐商品进度 (#35) 2026-10-06 15:07:30 +08:00
QiuSW 2c6a476581 ui: 下载商品数据前确认同步范围 2026-10-06 14:24:30 +08:00
QiuSW f9994885d0 fix: 支持同目录 ffprobe 并在启动时检查 (#34) 2026-10-06 14:19:12 +08:00
QiuSW 9ada5f62cb fix: Chrome 文件选择框兼容缺失的默认目录 2026-10-06 12:01:52 +08:00
QiuSW 1541ef56d3 feat: 配置批量视频上传并发数 (#31) 2026-10-05 18:04:01 +08:00
QiuSW 622473021a fix: 批量操作只使用当前页勾选 (#30) 2026-10-05 18:03:11 +08:00
QiuSW 5702286f42 fix: 未决视频操作使用原键恢复确认 (#29) 2026-10-05 10:30:16 +08:00
QiuSW e9a613dbad fix: 区分视频上传未决状态并及时关闭确认框 (#29) 2026-10-05 10:26:24 +08:00
QiuSW ade038ee9b fix: ERPGo 上传直连并阻止处理中重复提交 (#28) 2026-10-05 10:10:05 +08:00
QiuSW 1f83a34b4f fix: 上传前视频状态未知时阻止提交 (#27) 2026-09-30 11:13:17 +08:00
QiuSW 375f20cdfe feat: 视频上传改走 erpgo 幂等操作接口 (#27) 2026-09-30 09:42:58 +08:00
QiuSW 5447e3f63a fix: 视频任务逐商品完成后刷新列表 2026-09-30 09:06:20 +08:00
QiuSW 309e3eeb11 feat: 未选择店铺时同步全部店铺商品 (#26) 2026-09-29 17:18:16 +08:00
QiuSW 1a2b6feaf6 feat: 商品列表支持每页20至500条 (#25) 2026-09-29 16:52:30 +08:00
QiuSW 88dba284ea feat: 缺少视频与未下载组合筛选 (#24) 2026-09-29 16:21:00 +08:00
QiuSW 275b938b21 fix: 淘宝访问异常立即停批并减少重复访问 (#21) 2026-09-29 10:20:44 +08:00
QiuSW 3f1b27b4e2 feat: 接入 erpgo 店铺与商品查询并原子同步 (#20) 2026-09-28 17:50:48 +08:00
QiuSWandClaude Opus 5 c6bafd968e feat: 批量上传视频,以磁盘为准并按货憨憨规则本地预检 (#19)
解除 #17 的单商品限制,改为勾选多个一次上传,逐个串行处理。

选定文件的方式彻底改了:不再查 videos 表,改为扫描
<video_dir>/<蝦皮ID>/*.mp4 按文件名升序取第一个。原因是使用者可能手工把
别处来的视频放进目录,那类文件在 videos 表里没有记录;而且库与磁盘的漂移
无法根绝(手动挪文件、换 video_dir、外部程序写入),以磁盘为事实来源更稳。

上传前用 ffprobe 按货憨憨的要求本地预检,不合规的跳过而不是白传:

  大小  <= 30 MB
  时长  10.0 <= duration <= 60.0,严格比较不取整
  格式  容器为 mp4
  像素  宽 <= 1280 且高 <= 1280

时长必须严格判定:本地已有的 49365131605_1.mp4 是 9.985011 秒,
差 0.015 秒卡在门槛下,任何取整都会把它误判为合规。
ProbeResult 相应增加 Width / Height。

每个商品四条分支,任一分支结束后继续下一个:

  远端已有视频且勾选 > 1  -> existing,跳过,不发任何写请求
  子目录没有 mp4          -> missing,跳过
  有文件但不合规          -> invalid,last_error 写明违反哪条及具体数值
  合规                    -> 上传,done / failed

覆盖策略:batchUpdateShopProductVideo 是覆盖语义且不可恢复,所以批量永不
覆盖远端已有视频,单个(勾选 == 1)保持 #17 的红字警告加二次确认后可覆盖。
判断依据是实时读取的远端状态,不用本地 video_diagnosis——后者只在
「下载数据」时刷新,可能已经过期。

这三个新状态只是上一次尝试的结果记录,不构成断点:批量只处理当次勾选的
商品,不会因为状态是 missing/invalid/existing/failed 就把商品排除在外,
每次点上传都重新扫盘、重新预检、重新读远端。这是为了避免重蹈
video_status='none' 被断点永久跳过的覆辙(见 #15)。

上传成功后按 product_id + local_path 写入或更新 videos 记录,
source_item 留空(磁盘还原不出来自哪个淘宝同款),不使用 ReplaceVideos。

按负责人明确决定,不做容量门禁,仅保留确认框中的静态提示。

测试全部用 httptest 假服务器,未向真实货憨憨发出任何请求。
真机验证尚未进行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 17:08:12 +08:00
QiuSWandClaude Opus 5 d1149b629d feat: 视频上传回货憨憨并关联商品,首版限单商品 (#17)
接口契约来自 payloads/huohanhan_batch_save_product_video.har,两步:

  POST product/material/uploadFiles              multipart
       files / isLocalFile=true / fileType=1  →  bean[0] 是 COS 地址
  POST product/batchEdit/batchUpdateShopProductVideo   json
       [{id, platformShopId, videoUrl}]        裸数组,只有三个字段

id 是货憨憨内部记录 ID(products.id),不是蝦皮商品 ID。

不采用 common-product/updateProductItem 那条路:它要回传整份商品共 56 个
字段 26589 字节,而 getDetail 返回的结构无法直接回传,model[].priceInfo
的值就对不上——那是价格数据,照抄会写坏真实店铺且不可逆。
batchUpdateShopProductVideo 只改 videoUrl 一个字段。

这是本项目第一个会往真实店铺写、且删不回来的功能,所以:

- 只有显式点击「上传数据」才触发,不作为任何查询流程的副作用
- 必须二次确认,列出蝦皮 ID、商品名、本地文件名与大小
- 该商品已有视频时额外红字警告会覆盖且无法恢复(接口是覆盖语义,
  videoUrl 传空即删除)
- 首版一次只能一个商品,拦截在 Go 侧,前端只做提示
- 任一步非 SUCCESS 立即停止,不自动重试

关联成功后必须回读 getShopItemInfoPage 确认 records[0].video 非空,
才写 upload_status=done。接口返回 SUCCESS 不等于视频真的挂上了。

容量判断整体延后:payloads 下所有 HAR 都没有 getMaterialSize 的证据,
路径与返回结构均属推测,不凭猜测实现。本次只在确认框放一行静态提示,
待补抓包后另开工单。

测试全部用 httptest 假服务器,未向真实货憨憨发出任何请求。
真机验证尚未进行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 15:19:34 +08:00
QiuSWandClaude Opus 5 6cc334ab92 fix: 取视频链路与 Python 参考实现对齐,识别风控降级 (#15)
淘宝会在登录仍然有效的情况下停止下发视频资源:不跳登录页、不出验证码,
详情页里就是没有 mp4 地址。原实现识别不了,把它当成「这个同款没视频」,
继续跑满 20 个同款加重风控,最后写 video_status='none',
而断点规则会永久跳过 none,导致账号恢复后这批商品也不再重试。

与参考实现对齐:

- 详情页等待 4 秒改为 8 秒,提为 detail_wait_seconds
- 每个同款详情页前做一次 my_itaobao 深度守卫,在两次详情页之间插入
  正常页面访问;守卫等待提为 guard_wait_seconds
- 详情页后补一次 Cookie 级快速守卫,复用 judgeLogin
- 淘宝首页导航等待 3 秒改为 6 秒。签名请求在该页面上下文里 fetch,
  页面没加载完就发可能带不上 Cookie
- 下载失败重试,5xx 与连接错误重试 download_retries 次,
  4xx 和 ffprobe 校验失败不重试
- 每次下载尝试各有独立的 180 秒超时。此前超时套在整个重试循环外面,
  大视频首次跑到一半失败后剩余预算不足,重试等于不生效

新增风控降级识别:连续 risk_empty_threshold 个同款打开成功但取不到视频时,
判为疑似风控,中断整批任务并保持商品 pending,绝不写入 none。
停止原因通过 stopReason 枚举传给前端,不依赖中文文本分支。

数据订正提供显式按钮,不写进 migrations。刻意不做日期过滤:
synced_at 记录的是商品数据何时从货憨憨拉取,与 video_status 何时被写成
none 无关,每次「下载数据」都会把它刷成当天。某商品是否需要重做的长期
答案来自货憨憨每次全量拉取覆盖的 video_diagnosis,不来自本地时间戳。

淘宝与 Chrome 相关逻辑由假实现覆盖,真机验证尚未进行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 14:29:03 +08:00
QiuSWandClaude Opus 5 f79208cc5c feat: 批量取视频,队列、进度与全局停止门 (#14)
由 Codex (gpt-5.6-sol) 实施,Claude 审核。MVP2 最后一块。

internal/task/task.go
- 串行阶段(Prepare)一次一个商品,全程持有同一个 CDP:
  登录深度检查 → 图搜 → 逐个同款提取视频地址。
  并发只作用于下载阶段(纯 HTTP + ffprobe,不碰 CDP)。
  这样既避免多协程踩同一个浏览器页面,也不抬高淘宝风控概率。
- 登录失效走独立的 ErrLoginRequired,触发后立即终止整批,
  不再启动后续商品,不自动重试也不自动重新登录
- 其它错误属于单商品失败,记录后继续处理下一个
- 断点复用商品自身的 download_status,不另建断点文件或断点表,
  因此不会出现断点与数据库不一致

app.go 抽出单商品核心供两处复用,未复制逻辑。

审核补测(跑完删除,未入库),五项全部通过:
- 登录失效发生在第 3 个商品时,后续 D/E/F 一个都没被处理
- Concurrency 设为 4 时 Prepare 峰值仍为 1,串行确实生效
- 下载阶段峰值恰为 2,并发上限真的起作用且确实并行
- 连续 5 个普通失败仍跑完全部,状态为 completed
- 停止后可以重新启动,不会被永久锁死

硬性验收项全部通过:前端无 window.go 等绕过生成绑定的写法
(前三个工单连续出现,本次未再出现);internal 未 import wails;
无新增依赖。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 10:58:55 +08:00
QiuSWandClaude Opus 5 46378db86b feat: 商品主图可点击查看原图,支持右键另存为
- 表格主图列的缩略图可点击,弹出原图弹窗,鼠标指针改为 zoom-in
- main.go 打开 EnableDefaultContextMenu,这样在弹窗图片上右键才有
  「图片另存为」。代价是整个界面右键都会出现浏览器菜单,其中包含
  「检查」,同事可能误点开发者工具;权衡后仍然打开,因为另存图片是
  实际需要,误点开发者工具关掉即可,没有破坏性。同时移除 main.go 中
  已不再成立的「关掉右键检查」注释。
- 弹窗另配一个「另存为」按钮:WebView2 的原生右键菜单在个别机器上
  不给图片选项,这条路径不依赖浏览器菜单
- app.go 新增 SaveImageAs:只接受 http/https,避免变成可读取本地
  任意文件的入口;30 秒超时、20 MB 上限、空响应判失败
- 原图限制在 70vh 内并可滚动,避免大图撑出屏幕

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 10:24:18 +08:00
QiuSWandClaude Opus 5 73f5688ab1 feat: 单商品全链路,提取详情视频并下载到本地 (#13)
由 Codex (gpt-5.6-sol) 实施,Claude 审核。R4a,仍只处理单个商品。

internal/taobao/detail.go
- 从同款详情页 HTML 正则提取 mp4 地址,还原 / 与 \/ 转义
- 只保留 cloud.video.taobao.com 与 cloudvideocdn 的地址
- 去重按(主机、路径、查询串)三元组,忽略 http/https 差异

internal/downloader
- 先写 .part,ffprobe 校验通过后才改名为正式文件
- ffprobe 找不到时报明确中文错误,不静默跳过校验——跳过等于允许
  损坏文件流入不可逆的上传环节
- 请求带 Referer(同款商品页)与浏览器 UA,否则淘宝 CDN 可能拒绝
- 目标文件已存在且非空时跳过

internal/store/video.go
- 视频仓储,按商品全量替换
- source_item 记录实际采用的淘宝同款商品 ID(Q3 的落库要求)

同款选择按 Q3 结论:按图搜顺序依次尝试,第一个有视频的即采用,
不做人工确认;详情页之间按配置随机等待降低风控概率;
登录失效立即停止,不继续尝试下一个同款。

app.go 的 OpenFolder 用 EvalSymlinks + filepath.Rel 校验目标必须在
配置的 VideoDir 之内,避免成为可打开任意目录的入口。

审核补测(跑完删除,未入库):下载中途断开、HTTP 403/404/500、
空响应三种情况均不得留下正式文件或 .part,实测全部通过;
Referer 与 UA 确实发出。

审核期修正:Codex 又用 window.go.main.App 绕过生成绑定三处
(FetchVideosForProduct、OpenFolder、GetVideoSummary),已重新生成
绑定并改为正常 import。这是第三个工单出现同一问题。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 10:04:20 +08:00
QiuSWandClaude Opus 5 e8db874c04 feat: 单个商品以图搜同款 (#12)
由 Codex (gpt-5.6-sol) 实施,Claude 审核。R3a,先验证签名算法是否仍有效。

签名与参考实现字节级一致(审核时用固定随机值和时间戳对拍):

  pcSign      44 字节    一致
  params    1151 字节    一致
  requestText 1390 字节  一致
  MTOP sign   32 字节    一致
  token 提取             一致

Go 特有的陷阱已避开:签名是对 JSON 字符串做哈希,键顺序会改变结果。
Python 的 dict 按插入顺序序列化,而 Go 的 encoding/json 对 map 按键名
字母序排序,用 map 会产出完全不同的 JSON 导致签名必错,且淘宝只返回
含糊的业务错误码,极难定位。实现用 struct 按声明顺序输出,并用
json.Encoder + SetEscapeHTML(false) 对齐 Python 的 ensure_ascii=False。
四个价格字段用空指针输出真正的 null。有专门测试断言键顺序与字节,
防止后人改回 map。

internal/taobao/cdp.go 新增 CookieValue,仅用于读取 _m_h5_tk 计算签名。
AGENTS.md 明确允许该用途;全项目仅 imagesearch.go 调用一次,用完即弃,
不落库、不写日志、不返回前端。

以图搜前先做登录深度检查,未通过即停止,不发起 MTOP 请求,
避免抬高风控概率。

前端:表格操作列新增「搜同款」,结果以弹窗展示缩略图、标题、价格、
店铺、销量,点标题用系统浏览器打开淘宝商品页。

本工单不含批量、队列、并发与落库,那些属于 R3b。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 09:39:58 +08:00
QiuSWandClaude Opus 5 656b7a8c49 feat: 专属 Chrome 管理与淘宝登录检查 (#9)
由 Codex (gpt-5.6-sol) 实施,Claude 审核。MVP2 第一块。

internal/taobao/chrome.go
- EnsureBrowser 按四项条件判断能否复用已有实例:PID 存在、进程命令行
  属于本程序专属 profile、CDP 端口可访问、存在 page 类型目标
- 启动前遍历整个端口段,发现已有 Chrome 但状态文件丢失时报错而不是
  再起一个,避免同一 profile 出现两个实例损坏登录数据
- CloseBrowser 必须先校验 PID 归属才允许 taskkill,防止误杀使用者
  自己的 Chrome

internal/taobao/cdp.go
- 基于 gorilla/websocket 的最小 CDP 客户端(该依赖原本就在模块图里,
  由 Wails 引入,此处仅从 indirect 提为直接依赖)
- CookieNames 的结构体只声明 Name 和 Domain,Cookie 的值根本不会
  进入内存,比"取到但不使用"更彻底

internal/taobao/login.go
- 两层判定:四个必需 Cookie 齐全,且我的淘宝页面标题与正文不含
  六个阻断词
- 昵称只作展示,不参与判定。这是对参考 Python 实现的一处有意偏离:
  参考实现要求解析出昵称才算有效,但该解析依赖淘宝页面文案,
  淘宝一改版就会永远解析不到,导致登录永远判为无效、全局停止门
  永久触发、工具彻底不可用。放宽的代价可控:误判为已登录时后续
  MTOP 请求会失败,那条链路本就有 token 失效处理。

app.go 只在点击按钮时启动 Chrome,startup 不自动启动。

前端:设置页两个按钮接真实方法并显示检查结果;侧栏淘宝状态灯
监听 taobao:status 事件。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 09:13:40 +08:00
QiuSWandClaude Opus 5 5bcfea24ba feat: 状态默认选中在售中;明确不清理已下架商品
- 「状态」筛选默认值改回 NORMAL(在售中)
- 「类型」筛选默认 missing(缺少视频),两者配合后,打开程序即为
  「在售中且缺少视频」的商品,正是需要去淘宝搜视频的工作对象

已下架商品不清理(负责人 2026-09-03 决定):货憨憨那边删除或下架的
商品,本地保留不删。因此本地总数可能大于店铺当前商品数——负责人
本机为 5459 对 4935,多出的 524 条即已消失的商品。这是预期行为,
已写入文件头注释,避免后来人自作主张加自动清理。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 08:50:07 +08:00
QiuSWandClaude Opus 5 948945d188 feat: 诊断筛选标签改为「类型」并默认选中缺少视频
- 标签「诊断」改为「类型」
- 默认值由「全部」改为「缺少视频」:这些才是需要拿主图去淘宝
  搜视频的商品,是本工具的主要工作对象,打开即可直接干活
- 「重置」按钮同样回到「缺少视频」,与默认值保持一致

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-03 08:47:57 +08:00
QiuSWandClaude Opus 5 2e8a01460d fix: 状态筛选默认改为全部;修正 build.bat 产生垃圾目录
1. 商品列表的「状态」筛选默认值由 NORMAL 改为空(全部)。
   按负责人要求,默认不预设商品状态过滤。

2. build.bat 结尾用 explorer "%CD%\build\bin" 打开产物目录,
   在某些环境下 %CD% 未展开,会在仓库里建出一个名为 %SystemDrive%
   的垃圾目录(已实际出现并删除)。改为 start "" explorer "%~dp0build\bin",
   并加 if exist 保护。

   同时修复该文件在多次编辑中被引入的字节级损坏:反斜杠被重复转义成
   \,且部分行尾退化为 LF。已统一还原为单反斜杠 + CRLF,
   实测前三项检查可正常执行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 22:44:19 +08:00
QiuSWandClaude Opus 5 c8ce79f886 fix: 修正历史脏值并改进店铺刷新入口 (#10 #11)
负责人反馈「下拉选择缺少视频筛不出数据」,查库发现 5305 条商品的
video_diagnosis 全是 unknown,product_diagnoses 表 0 条。

根因是我中途取消 Codex 造成的:第一版按三态实现时,负责人的
wails dev 正开着,Codex 一存文件就自动重编译重启,把带
DEFAULT 'unknown' 的迁移真正执行进了数据库。随后我回滚了源码,
但数据库已升到 schema_version=10,那几条迁移不会再跑,
于是库里留下当前代码不认识的 unknown。

三处修改:

1. 追加自愈迁移,把非 missing/ok 的历史值统一收敛为 ok。
   已在负责人真实数据库上验证:5305 条 unknown 全部变为 ok,
   版本升到 11。下次「下载数据」会写入真实诊断值。

2. 去掉「刷新店铺」按钮,改为展开店铺下拉时按需刷新。
   带 5 分钟间隔,避免每次点下拉都要等一次货憨憨登录;
   自动刷新失败不弹窗,下拉框继续用缓存,旁边的「更新于 …」
   会显示旧时间,看得出数据不是最新的。启动时的刷新失败仍然弹窗,
   那是需要立刻知道的。

3. 修掉三个绕过 Wails 生成绑定的 window.go.main.App 调用。
   Codex 这样写是因为 frontend/wailsjs 在禁改清单里,做法自洽,
   但绕过类型包装且会被后来者照抄。已重新生成绑定并改为正常 import。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 21:13:12 +08:00
QiuSWandClaude Opus 5 393c942762 feat: 解析商品质量诊断并支持按「缺少视频」筛选 (#11)
由 Codex (gpt-5.6-sol) 实施,Claude 审核。

货憨憨 getPage 响应里带有商品质量诊断,其中「缺少视频」直接标出了
哪些商品才需要去淘宝找视频,能把工作量从全店商品缩小到真正需要处理的那部分。
此前这个字段被完全丢弃。

字段名是 diagnosisInfo(不是 diagnoses),结构比表面看到的多一层:
{itemId, qualityLevel, diagnoses:[{field, diagnosisResults:[{type, solution}]}]}

判定规则(负责人 2026-09-02 决定,见工单评论):
- missing:诊断明确报了「缺少视频」
- ok:其余全部情况,含 diagnosisInfo 为 null
只有明确报缺少视频才算缺少,其它一律当作有视频。

500 条样本里 293 条 diagnosisInfo 为 null,按此规则归入 ok。
我曾建议保留 unknown 三态以区分「尚未诊断」,负责人已知悉并选择两态,
措辞为「初定」;顾虑与重新评估条件记录在工单 #11 评论中。

- internal/store:products 加 video_diagnosis、quality_level 两列;
  新增 product_diagnoses 表保存全部 6 种诊断类型(缺少尺寸表、缺少标准变体、
  缺少品牌信息、所需属性过少、缺少视频、合格级属性数量不足)。
  只存「缺少视频」的话,另外 5 种将来要用就得重新全量拉一遍,而采集成本为零。
  迁移全部在 migrations 末尾追加,未改动任何已有条目。
- internal/huohanhan:ProductRecord.DiagnosisInfo 用指针以区分 null;
  转换时按上述规则算出 video_diagnosis。
- 前端:工具栏加诊断筛选(全部 / 缺少视频 / 有视频);
  「视频」列改为显示诊断结果。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 21:02:58 +08:00
QiuSWandClaude Opus 5 d34846e26e feat: 店铺列表缓存到 SQLite 并在启动时后台刷新 (#10)
由 Codex (gpt-5.6-sol) 实施,Claude 审核。

以前每次打开商品列表都要现调 erp/shop/all,而这条链路要先完成
货憨憨登录(CID/CST → 验证码 → OCR → 登录 → 验证 token),
任何一环失败下拉框就是空的,连店铺名都看不到。

internal/store/shop.go(新建)
- shops 表在 migrations 末尾追加,未改动任何已有迁移条目
- ReplaceShops 是全量替换:同一事务内先 DELETE 再批量插入。
  只做 upsert 会让货憨憨那边已删除的店铺永远留在下拉框里,
  选中后拉不到任何数据
- 缓存字段是白名单,不含 accessToken、refreshToken、createUser

app.go
- ListShops 改为只读缓存;新增 GetCachedShops、GetShopsUpdatedAt、
  RefreshShops
- startup 里用协程后台刷新,不阻塞窗口显示;且仅在账号密码都已配置时
  才刷新——同事第一次安装还没填账号就启动时,刷新必然失败,
  一开机弹「登录失败」体验很差
- 成功发 shops:refreshed 事件,失败发 shops:refresh-failed 并带上
  失败原因、缓存时间和缓存条数

前端
- 启动先读缓存立即填充下拉框,再由后台刷新更新
- 刷新失败时弹窗同时说明失败原因和缓存时间,只说「更新失败」
  使用者无法判断该不该继续用
- 下拉框旁常驻显示缓存更新时间,并提供手动刷新

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 18:00:11 +08:00
QiuSWandClaude Opus 5 f568cd1061 feat: 商品表格补上操作列(详情 / 目录)
原型里每行末尾有「详情 / 目录」两个操作,实现时漏掉了整个操作列。

- 详情:按店铺地区拼 Shopee 商品页地址并用系统浏览器打开。
  地区来自 erp/shop/all 的 region 字段,在前端建 平台店铺ID -> 地区
  的映射表;认不出地区时置灰,不拼一个打不开的地址。
  各站点域名集中在 shopeeDomains 里,不要在别处硬编码。
- 目录:打开本地视频所在文件夹。视频尚未下载,先置灰并提示
  「下载视频后可用」,等 R4(#5 MVP2)实现下载后解开禁用即可,
  届时不用再动表格结构。

app.go 新增 OpenInBrowser,只允许 http/https,避免变成可以
启动任意程序的入口。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 17:48:51 +08:00
QiuSWandClaude Opus 5 d5e32f4d2b fix: 下载数据点击后无反馈,且大店铺会被分页上限截断 (#8)
负责人反馈「点击下载数据没反应」。用真实登录态做只读诊断后确认
后端正常:ListShops 返回 19 个店铺,getPage 返回 total=4304。
问题有两个,都在前端反馈和分页参数上。

1. 前端没有任何运行状态
   downloadProductData 是个纯 await,期间按钮不禁用、不转圈、不提示。
   商品多的店铺要跑几十秒到几分钟,看起来就像点了没用。
   改为:未选店铺时先给出提示不发请求;开始时提示「进度见运行日志」;
   按钮显示 loading 并互斥禁用;失败用 error 而不是 warning。
   下载视频、上传数据两个按钮同样处理。

2. 每页 20 条导致大店铺超出 200 页上限被截断
   实测同一店铺不同 size 的表现:
     size=20   216 页  506ms/页   约 108 秒
     size=200   22 页  836ms/页   约 18 秒
     size=500    9 页  1.48s/页   约 13 秒
   4304 个商品按 20 条一页是 216 页,超过 maximumProductPages=200,
   会在 4000 条处截断并只记一条警告。
   改为每页 200 条:耗时降到约六分之一,且上限对应 4 万商品,
   远超实际店铺规模。选 200 而非 500 是响应体积与速度的平衡。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 17:43:25 +08:00
QiuSWandClaude Opus 5 406fb80e46 fix: 补回缺失的 DownloadProductData 导入 (#8)
点击「下载数据」报 ReferenceError: DownloadProductData is not defined。

这是审核阶段我自己引入的回归:Codex 原先绕过 Wails 生成的绑定,
直接调 window.go.main.App.DownloadProductData,同时把该函数从 import
列表里删掉了。我把调用改回正规写法时忘了把 import 加回去。

顺带核对了三个前端文件的全部绑定调用,确认没有其它「调用了但没导入」
的情况;移除 ProductListView 中未使用的 CountProducts 导入。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 17:38:04 +08:00
QiuSWandClaude Opus 5 5524fe958c feat: 店铺列表与商品数据下载 (#8)
由 Codex (gpt-5.6-sol) 实施,Claude 审核。

实现以真实抓包为准,纠正了 Python 参考实现与接口文档的三处错误:

1. erp/shop/all 请求体是 userId=(空值),不是文档写的 type=1
2. product/shop/getPage 用 form 编码,不是 JSON
3. 两个接口的响应都没有 {type, message, code, bean} 包裹:
   shop/all 返回裸数组,getPage 直接返回分页对象

internal/huohanhan/shop.go
- ListShops 解析裸数组,只保留 platform=="0" 的 Shopee 店铺,
  店铺名去首尾空格后按名称排序
- Shop 结构体只声明界面需要的 8 个字段。货憨憨返回的
  accessToken、refreshToken 是 Shopee OAuth 凭据,createUser 是手机号,
  一律不解析、不记录、不存储、不返回前端;有测试用反射与 JSON
  序列化双重断言

internal/huohanhan/product.go
- GetProductPage 发送完整表单,自定义 UnmarshalJSON 兼容
  total/size/current/pages 既可能是数字也可能是字符串
- DownloadAllProducts 按页拉全量,带 200 页上限保护防止 pages
  异常导致死循环,达上限记警告而非静默截断

app.go
- ListShops、DownloadProductData 换成真实实现
- 未选店铺时返回「请先选择店铺」。该账号有 19 个店铺,
  单店铺 1256 个商品 63 页,默认拉全部会非常慢

前端
- 店铺加载失败不再静默吞掉,改为可见提示
- 「下载数据」传当前店铺 ID,完成后刷新列表

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 17:16:41 +08:00
QiuSWandClaude Opus 5 d8e90789a2 fix: 界面无法正常显示的两个问题 (#4)
由负责人实机运行后发现,均为骨架工单遗留缺陷。

1. Naive UI 组件从未注册
   main.js 少了 app.use(naive),模板里的 n-button / n-input /
   n-select / n-data-table / n-modal 全被 Vue 当成未知元素:
   按钮渲染成纯文本、输入框和表格不显示、日志弹窗不受 v-model:show
   控制直接常驻页面。侧边栏因为是手写 HTML+CSS 所以正常,
   这正是定位问题的线索。
   修复后打包产物由 258 KB 增至 1.46 MB,可反证此前确实没打进去。

2. 窗口比屏幕高,标题栏被顶到屏幕外
   Width/Height 是逻辑像素,Windows 按系统缩放放大。1440x900
   在 125% 缩放下变成 1800x1125 物理像素,超过工作区高度 1030,
   窗口居中后标题栏与最大化/关闭按钮移出屏幕。
   改为 WindowStartState: options.Maximised,还原尺寸降到 1280x760,
   最小尺寸降到 1024x600,并在代码中写明这个坑。

顺带给工具栏加 flex-wrap,窄窗口下筛选控件换行而不是挤在一起。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 16:55:12 +08:00
QiuSWandClaude Opus 5 2cefa67001 feat: 初始化 Wails v2 + Vue3 骨架与 SQLite 存储 (#4)
建立可运行、可构建的 Go 骨架,供后续 R1~R5 在其上叠加。

后端:
- main.go / app.go:Wails v2 窗口与 14 个前端可调方法。Wails 专有 API
  只出现在这两个文件,internal/ 对 Wails 零依赖,便于将来迁移 v3
- internal/config:config.yaml 的读写、校验、脱敏。保存时按固定模板
  渲染以保留注释,不用 yaml.Marshal
- internal/store:SQLite(modernc.org/sqlite,无需 CGO)。建表迁移、
  商品仓储、KV。重复同步不会覆盖本地下载与上传状态
- internal/logx:9 条脱敏规则,覆盖淘宝 token、Cookie、Authorization、
  密码和图片 base64;并发安全的内存日志缓冲

前端(Vue 3 + Naive UI,按已确认原型 v4):
- Sidebar:176px 可折叠,仅商品列表与参数设置两项,底部是登录状态的
  唯一显示位置
- ProductListView:两行工具栏(四项筛选 + 重置搜索 / 四个操作按钮)、
  商品表格、分页
- SettingsView:四张卡片,路径用文件与目录选择框
- LogWindow:运行日志子窗口,按级别过滤、自动滚动、导出

配置改用 YAML:分 huohanhan / taobao / download 三段。taobao 段刻意
没有账号密码字段,并有单元测试挡住后续加入。

构建相关:
- .gitignore 只忽略 build/bin/,不忽略 build/。Wails 把 build/ 当源码
  目录(图标、manifest),忽略掉会让别人克隆后构建失败
- .gitignore 忽略 config.yaml、payloads/ 和 *.har(含真实手机号与凭据)

未实现的下载数据、下载视频、上传数据三个方法返回明确的「还没实现」,
不静默失败。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbdtsD3ohhSMy3KPoCgARq
2026-09-02 15:59:54 +08:00