# 开发提示词:批量导出"购物车"(清单式收集 → 一键打包下载,离线版+在线版两端一致) > 用途:把本文件交给 AI 会话执行。完成后按"验收标准"逐条核对,结果回写 OnebotCatalog\项目总览.md(进展记录 + 踩坑表 + 最后更新行)。 > 背景必读:OnebotCatalog\项目总览.md、OnebotCatalog\src\App\MainWindow.cs(桌面 4 标签页 + 参数面板下载区 + 参数表)、OnebotCatalog\web\app.js / index.html / style.css(网页 4 标签页)、OnebotCatalog\开发提示词-窗口内3D-WebView2方案B.md(提示词文体参照)。 ## 一、角色与任务 你是资深 WPF/前端工程师,在既有项目 **OnebotCatalog**(C# / .NET Framework 4.8 代码式 WPF + 纯静态网页版)上实现**淘宝式购物车批量导出**:用户浏览选型过程中把多个型号"加入清单"(跨系列、跨型号,选型流程完全不受干扰),随时打开清单**一键打包下载**(zip 含全部 STEP + BOM 清单)。**两端功能、交互、文案完全一致**(项目已定规矩)。 ## 二、需求定稿(语义先行,避免歧义) 1. **购物车 = 跨系列清单**:可加入不同系列的不同型号;同一型号重复加入 → 提示"已在清单中"(不清空、不加量、不跳转)。 2. **不影响选型(淘宝式,用户 2026-08-24 明确)**:加入清单是"旁路操作"——加入后当前选型状态、3D 视图、参数面板**完全不变**、不跳转、不清空。 - **购物车生命周期**:网页 = 浏览器会话内(**刷新浏览器才清空**);桌面 = 软件运行期间(重启软件才清空)。 - **切换系列 / 切换型号 / 切标签页 / 搜索跳转 / 平替跳转 / 参数表任何操作,一律不触碰购物车**——购物车状态对象与选型状态彻底解耦(两端 cart 独立于 sel/series 状态)。 3. **三处入口(两端一致)**: - 参数面板下载区新增【加入清单】按钮(与【下载 STEP】【下载 PDF】并排,选型完成才显示) - 参数表每行行尾"+"小按钮(一键加该行,**不触发行双击/筛选**) - 顶部栏常驻【清单 (N)】按钮,N=件数实时角标,点击打开清单面板 4. **清单面板**: - 桌面:独立非模态 Window(Owner=主窗口),DataGrid 列表;网页:右侧滑出抽屉(遮罩点击关闭,不误触面板外按钮) - 列:型号 | 系列 | 规格摘要 | 文件名 | 数量(可编辑≥1,默认1) | 操作(删除) - 底部按钮:【清空】【下载全部 (zip + BOM)】;面板标题与角标实时同步件数 - 数量语义定死:**zip 中每型号只打包一份 STEP**,数量只体现在 BOM.csv 的"数量"列(不做重复文件、不改文件名) 5. **一键打包(两端产物结构完全一致)**: - `ONEBOT_清单_<时间戳>.zip`:内含各型号 STEP(文件名=型号编码,保留原后缀)+ `BOM.csv` + `清单.txt`(人类可读版 BOM) - `BOM.csv` 列:序号, 型号编码, 系列代码, 系列名, 数量, 参数摘要(键=值 分号分隔), STEP文件名;**UTF-8 带 BOM**(Excel 中文不乱码);字段含逗号/引号按 CSV 规范转义 6. **打包引擎**: - 桌面:System.IO.Compression.ZipArchive(net48 内置,零新依赖) - 网页:**JSZip**(MIT 开源,~100KB 单文件,放 `web\libs\jszip.min.js`,**需用户批准下载**,交付说明注明版本/来源/许可)——纯静态无后端,前端逐项 fetch 同源 STEP 二进制 → 内存打包 → `URL.createObjectURL` 触发下载 7. **整合旧功能**:桌面参数表多选 +【批量导出所选】改造为【加入清单】(勾选行批量加入,提示"已加入 N 项"),删除旧直接导出逻辑,统一走清单(避免两套批量路径);网页参数表本就无批量导出,直接加"+"。 ## 三、两端统一文案表(开发时直接抄,保证一致) | 场景 | 中文 | 英文 | |---|---|---| | 加入按钮 | 加入清单 | Add to Cart | | 顶栏角标 | 清单 (N) | Cart (N) | | 重复加入提示 | 该型号已在清单中 | Already in cart | | 加入成功 | 已加入清单 (共 N 项) | Added to cart (N items) | | 批量加入 | 已加入清单 N 项 | N items added to cart | | 面板标题 | 我的清单 | My Cart | | 数量列 | 数量 | Qty | | 删除 | 删除 | Remove | | 清空 | 清空 | Clear All | | 下载全部 | 下载全部 (zip + BOM) | Download All (zip + BOM) | | 打包中 | 打包中... | Packing... | | 空清单 | 清单为空, 请先加入型号 | Cart is empty | | 部分失败 | 打包完成, 但以下型号文件缺失: xxx | Packed, but missing: xxx | ## 四、现状速览(改动前先读) - 桌面 `src\App\MainWindow.cs`:`TryUpdateSelection()`(已选型号后 `_btnDownload/_btnDownloadPdf` 显隐、`_curModelCode` 平替过滤);参数面板 actions 区(2026-08-24 加的 `_btnDownload/_btnDownloadPdf` StackPanel);`BuildGridTab`(`_grid` 多选 + `ExportSelected()` + 右键列头筛选 + 双击 `NavigateToModel`);顶栏 StackPanel `top`;4 标签页索引:0=3D/尺寸图, 1=参数表, 2=搜索, 3=平替对照 - 桌面 STEP 读取:`_pkg.ReadAsset(v.step)` / `OpcPackage.ExtractStepTo(v, path)`;Variant 有 `@params` 字典、`modelCode`、`step` - 网页 `web\app.js`:`selectSeries`(重置 sel/ranges/tableFilter——**不得动 cart**)、`updateSelection`(下载按钮显隐 + `v.step` 相对路径如 `step/UCBM32.stp`)、`renderTable`(行点击带入选型 + 2026-08-24 列筛选,行尾加"+"注意 stopPropagation) - 网页 STEP 位置:站点根 `step\` 目录(发布时从 .opc 解包),fetch 相对路径即可 - 版本号规范:网页 index.html 引 `app.js?v=N`、`style.css?v=M`,每次发布 +1;桌面改 .cs 后必跑 `sample-data\tools\fix-bom.ps1` ## 五、硬性约束(违反即返工) 1. 完全离线可用(两端);桌面编译环境无 .NET SDK(csc + `lib\` DLL `/reference`,新库须放 lib\) 2. 两端交互/文案/输出格式一致(见文案表);桌面中英双语 `_lang.T(zh,en)`,网页 `t(zh,en)` 3. 不影响选型主流程(需求第 2 条——验收重点) 4. 不改数据管线、.opc 格式、发布六步结构 5. JSZip 必须经用户批准下载并在交付说明注明版本/来源/许可(MIT) 6. 编辑含中文 .cs/.ps1 后必须 fix-bom;网页文件 UTF-8 无 BOM 即可 7. 客户版与开发版同一代码(`#if CUSTOMER_BUILD` 只控制维护入口);清单功能两版都有 ## 六、实施步骤 ### 桌面(src\App) 1. **新文件 `Cart.cs`**(命名空间 Ounibo.Catalog.App): `class CartItem { string ModelCode; string SeriesCode; string SeriesName; string StepPath; string ParamsSummary; int Qty = 1; }` 2. **状态**:`List _cart`;`int CartCount => _cart.Sum(x => x.Qty)`(角标显示**件数=数量总和**还是行数?——定死:角标显示行数,BOM 数量列体现 Qty,简单不混淆) 3. **加入清单**:`AddToCart(Variant v, Series s)`——重复判断(ModelCode 忽略大小写)→ 提示已存在;成功仅更新角标 + 状态栏,**严禁写 `_sel`** 4. **三处入口**:参数面板 actions 区【加入清单】(`TryUpdateSelection` 里随 `_btnDownload` 同显隐,点击用当前已解析的 variant);参数表"+"列(`DataGridTemplateColumn` 内 Button,`Click` 里 `e.Handled` 防止干扰行选择/双击/右键筛选);顶栏【清单 (N)】 5. **清单窗口** `CartWindow`:非模态 Window(Owner 主窗口),DataGrid 列:型号/系列/规格/数量(可编辑绑定 Qty)/删除(按钮列);底部【清空】【下载全部 (zip + BOM)】;窗口关闭不丢数据(_cart 在主窗口) 6. **打包**:SaveFileDialog(默认名 `ONEBOT_清单_yyyyMMddHHmmss.zip`)→ ZipArchive:遍历 `_pkg.ReadAsset(item.StepPath)` 写 `ModelCode+原扩展名`;BOM.csv 与 清单.txt 用 UTF8Encoding(true);打包完成后 MessageBox 提示路径 7. **整合**:`ExportSelected` 改为"选中行批量 AddToCart + 提示已加入 N 项";`_btnExport` 文案改【加入清单】 8. 文案走文案表 + 状态栏反馈 ### 网页(web\) 1. **引入 JSZip**:下载官方最新稳定版 jszip.min.js → `web\libs\jszip.min.js`;index.html ``;交付说明注明版本/来源/许可 2. **状态**:`state.cart = []`(结构同 CartItem,StepPath=相对路径)——**selectSeries/refreshOptions/renderTable/jumpToModel 全部不得触碰** 3. **三处入口**:`updateSelection()` 里下载区显【加入清单】;`renderTable` 每行尾"+"(`onclick` 内 `ev.stopPropagation()` 防触发行点击);header 加【清单 (N)】角标按钮 4. **清单抽屉**:右侧 fixed 面板 + 半透明遮罩;行含数量 ``、删除按钮;底部【清空】【下载全部 (zip + BOM)】;遮罩点击只关面板(面板内点击 `stopPropagation`) 5. **打包**:`new JSZip()` → 逐项 `await fetch(item.step)`(失败收集到 missing 列表**继续**)→ `zip.file(name, blob)` → `zip.file('BOM.csv', '' + csv)`、`zip.file('清单.txt', txt)` → `generateAsync({type:'blob'})` → createObjectURL + a[download] 触发;打包中按钮文案"打包中..."并禁用防连点;missing 非空时提示"打包完成, 但以下型号文件缺失: xxx" 6. 文案走文案表;不调用 selectSeries/refreshOptions ### 发布与回归 1. 桌面:fix-bom → build.ps1(双版)→ 冒烟 → `--selftest` 31 项不变 2. 网页:index.html 版本号 +1 → 停 serve-web → publish.ps1 → 重启 serve-web 3. 交付说明:JSZip 来源/版本/许可 + 两端截图对照 ## 七、验收标准(逐条核对) 1. 两端:选型号 A → 加入清单 → **选型界面无任何变化**(参数不清空、3D 不跳)→ 换系列选型号 B → 加入 → 角标 (2);再切回系列 A、切所有标签页、搜索/平替跳转、参数表双击/列筛选,**清单仍 (2) 内容不变**(网页仅刷新、桌面仅重启才清空) 2. 重复加入同型号 → 提示"已在清单中",件数不变 3. 参数表"+"与勾选批量加入正常,且**不干扰**行双击带入选型、右键列筛选、范围筛选 4. 清单面板:数量改 3 → 删除一项 → 清空 → 角标实时同步 5. 下载全部 → zip 含全部 STEP + BOM.csv + 清单.txt;Excel 打开中文无乱码、列齐全;数量 3 的型号 zip 中仍只有一份文件、BOM 数量列=3;两端产物结构一致 6. 网页打包中防连点;断网桌面全流程可用;STEP 缺失时跳过并明确提示缺失型号(不整体失败) 7. 自测 31 项不变;客户版发布目录运行正常 8. 两端文案完全一致(对照文案表,中英切换正确) ## 八、已知陷阱(本项目血泪 + 本功能专属) | # | 陷阱 | 对策 | |---|---|---| | 1 | 中文文件/CSV 乱码 | BOM.csv 必须 UTF-8 BOM(桌面 UTF8Encoding(true) / 网页 '' 前缀);.cs/.ps1 改完 fix-bom | | 2 | GUI exe 用 `&` 启动不等待,$LASTEXITCODE 空 | Start-Process -Wait -PassThru,核对 log 时间戳 | | 3 | 发布目录被运行中程序锁 | 发布前停 serve-web 与运行中 exe(红线) | | 4 | zip 内重名冲突(跨系列存在同名型号) | 打包前校验,冲突加后缀 `_系列码`,BOM 列同步注明 | | 5 | 网页逐项 fetch 大 STEP 内存峰值 | 逐项 fetch 后立即 zip.file(JSZip 持有 blob,不必先全存数组);避免一次性 concat | | 6 | 空串枚举参数摘要显示 | 摘要统一走 displayValue/显示名映射,空值显示"(无)" | | 7 | 加入清单误写 `_sel/state.sel` 导致选型被重置 | AddToCart 只读 variant;code review 时重点查 | | 8 | 桌面 DataGridTemplateColumn 按钮点击干扰行选择/双击 | 按钮 Click 中 `e.Handled = true`;"+"按钮 `Focusable=false` 避免抢焦点 | | 9 | 网页"+"按钮事件冒泡触发整行 onclick(带入选型) | `ev.stopPropagation()`;抽屉面板内所有点击同样处理 | | 10 | 角标件数与 BOM 数量语义混淆 | 定死:角标=行数,Qty 只在 BOM 体现(需求第 4 条) | | 11 | 清单窗口关掉后数据丢失 | 购物车状态放主窗口/state,面板只是视图 | | 12 | 打包中用户再点加入/删除导致 zip 内容快照不一致 | 打包开始时先对清单做快照(ToArray),打包期间 UI 允许操作但只影响下一次下载 | ## 九、交付物 1. 两端可运行清单功能(bin\ 开发版 + 发布目录客户版 + 网页站) 2. `web\libs\jszip.min.js`(注明版本/来源/许可 MIT) 3. `src\App\Cart.cs` + MainWindow/网页改动(注释风格与现有一致) 4. 文档回写:项目总览(进展记录 + 踩坑 + 最后更新行)、维护手册、数据维护说明书(批量导出流程) 5. 本文件验收标准逐条核对结果 ## 十、不做的事(范围外) - 不做清单持久化(网页刷新/软件重启即清空;若要跨会话保留,用 localStorage/文件另立项) - 不做价格/报价单/订单/账号对接(BOM 只是清单文本) - 不做网页服务端打包(保持纯静态) - 不改选型核心逻辑、数据管线、.opc 格式