源文件:
C:\Users\PC\Desktop\仓位信息获取_v6.25.user.js在 v6.17 基础上做了 7 个小版本迭代,核心套路没变,重点从"拼出面板"转向"修数据 + 修交互 + 重新组织记忆"。
| 项目 | 内容 |
|---|---|
| 脚本名 | ERP仓位占有信息栏(统一兼容版 v6.25) |
| 版本 | 6.25(@version 6.25,启动日志却打印 v6.10) |
| 作者 | HuaSao |
| 生效域名 | 5 个明确 @match + 兜底 https://*.erp321.com/** |
| 主要生效页 | 采购出库、调拨单、采购入库、PackItems、其他出库 |
| 权限 | GM_xmlhttpRequest、GM_log、@connect *(跨域免 CORS) |
| 依赖 | 顶层 / 同域 iframe localStorage、document.querySelectorAll('iframe') |
一句话概括:在 ERP 单据页面右侧挂一张可拖动面板,自动捕获货品 SKU 后同时拉 3 路数据——仓位库存(带每仓位订单占有)、全 SKU 的订单预占用、当前 SKU 的剩余发货时间订单清单——并按"单选锁定 / 多选固定 / 计算排除"三档筛选,把结果展示到一排卡片网格里。
它解决什么问题:配货岗原本要在多个 ERP 页面间来回切换拼出"这个货还剩多少、压在哪个仓位、有哪些订单在卡着"。脚本把三件事合并到一张右侧面板,且支持「按仓位记忆固定显示」「按仓位单选只关心一个」「按仓位排除不算库存」三种过滤习惯。
| 版本 | 主题 | 实际改动一行说明 |
|---|---|---|
| v6.17(基线) | 订单剩余发货时间卡片 | 顶部新增 5 张可点击卡片(全部 / 已超时 / 今天必发 / 紧急 ≤3 天 / 正常 >3 天),点击展开对应订单清单 |
| v6.18 | 修"仓位查到但显示为空" | PackItems.aspx 的 LoadDataToJSON 缺 owner_co_id / authorize_co_id 时接口静默返回 0 条;现在自动从 URL → 顶层 → iframe → localStorage 兜底补齐公司上下文 |
| v6.19 | 修"切换仓库后查不到数据" | 表单令牌缓存(5 分钟)绑定公司上下文签名 ctxSig;切仓时签名变化、令牌立刻作废重新取;并加"自动重试 1 次 + 一键解除筛选"防卡死 |
| v6.20 | 折叠功能 | 标题栏 ▾/▸ 按钮;折叠态只剩标题条(可拖位置、可拉宽度);修复 if (!hasMoved) 才保存 的位置记忆逻辑反转,现在拖完一律存 |
| v6.21 | 折叠粒度改为"每卡片" | 不再折叠整个面板,而是每张仓位卡片可单独点标题行折叠、单独拖右下角改大小(横跨 N 列 + 改高度),按仓位 key 自动记忆 |
| v6.22 | 一键折叠 + 区块折叠 | 仓位明细标题栏新增 ⇕ 一键折叠/⊞ 一键展开全部卡片;商品信息、订单时间、固定仓位三个区块各自加 ▾/▸ 独立折叠 |
| v6.23 | 按页面维度独立记忆 + 自动显示开关 | 位置/大小/折叠/已关闭/自动显示/固定仓位/单选锁定/计算排除/卡片状态/区块折叠全部按页面类型后缀存(4 套副本);新增标题栏 ⏰/🚫 按钮控制本页面自动显示 |
| v6.24 | 滚不动修复 | #erp-bin-detail-row 加 flex:1 1 auto + min-height:0 收缩约束,.erp-bin-list 加 max-height:100% 真正触发滚动;滚动条样式从 detail-row 搬到 bin-list;面板全折叠态的最小高度 360→200 |
updateInfo(productInfo) // 来自 XHR/fetch hook 自动捕获的 SKU
├─ loadLockData // 启动 1 秒后已经在跑了(fetch + 分页 20 页)→ 全局 lockData
├─ loadRemCards(sku_id) // 并行触发(GM_xmlhttpRequest + 分页 6 页)→ 订单时间卡片
└─ queryBinStock(sku_id) // 主路径(GM_xmlhttpRequest + POST)→ 仓位明细
├─ 第一次:表单令牌 + 旧公司上下文
├─ 失败:清缓存 + 重新获取令牌 + 重试 1 次
└─ 仍失败:给出"刚切仓库/重新登录请按 F5"的明确提示
| 维度 | ① loadLockData |
② queryBinStock |
③ fetchSkuOrders |
|---|---|---|---|
| API | GetPageListV2 |
LoadDataToJSON(POST) |
GetOrderLockShowV2 |
| 调用方式 | fetch(同源) |
GM_xmlhttpRequest(跨域打包站点) |
GM_xmlhttpRequest |
| 鉴权 | gwfp + u_sso_token + cookie |
表单隐藏域 + owner_co_id(强制覆盖) |
硬编码 uid:21465083, coid:12816726 |
| 分页 | 是(20 页上限,终止条件 order_lock=0 兜底) |
否(一次 50 条) | 是(6 页上限,按 total 决定页数) |
| 缓存 | 无(每次全量刷) | 5 分钟(绑 ctxSig 公司签名) |
内存级 remReqToken 自增防旧请求覆盖 |
| 失败处理 | 警告日志,占位 0 件 | 清缓存重试 1 次 + 明确提示 | 顶部提示"获取失败" |
最值得单独提醒的一条:③ 的 ORDER_API 和 fetchOrderPage 里 uid / coid 仍然是硬编码数字字面量(12816726、21465083)。v6.18/19 修了仓位接口那边的公司上下文硬编码(改成动态从 localStorage / URL / iframe 兜底),但订单时间接口没动。一旦换账号或者多公司共享脚本就直接拉空。后面"静默问题"章节会展开。
┌─────────────────────────────────────────────┐
│ 📦 仓位占有信息 │ 占有 0% │ ▾ ⏰ ◀ │ ← 标题栏(左=拖动区,右=4个按钮)
├─────────────────────────────────────────────┤
│ [输入商品编码搜索仓位...] [🔍搜索] │ ← 搜索行
├─ ▾ ─────────────────────────────────────────┤
│ 🔑编码 📊数量 🔒占有 🏭供应商 │ ← v6.22 可折叠
├─ ▾ ⏱ 订单剩余发货时间(点卡片看明细)──── │
│ [全部] [已超时] [今天必发] [紧急] [正常] │ ← v6.17 5 张可点卡片
│ [点卡片后展开订单清单,最大 60 单] │
├─────────────────────────────────────────────┤
│ ▾ 📌 固定显示仓位... [清空] │ ← 多选勾选(v6.22 可折叠)
│ [chip1] [chip2] [chip3] ... │
├─────────────────────────────────────────────┤
│ 📍 单选锁定: 当前仓位 [清除锁定] │ ← 单选锁定后的精简视图
├─────────────────────────────────────────────┤
│ ⊘ 计算排除: 已排除 2 项 [清除排除] │ ← 排除的仓位不计入占有计算
├─────────────────────────────────────────────┤
│ 仓位明细 │ 共 N 行 数量 N 📌已固定 │ ⇕全折 │
├─────────────────────────────────────────────┤
│ ┌─ 卡片1 ──────────────────────────── ▾ ┐ │
│ │ ● 仓位A 📍单选 ⊘排除 │ │ ← 每卡片单独可折叠
│ │ 999 件 │ │ ← 拉大时数字跟着大
│ │ [仓库ID] [占有 12] ⬚ ←─┤ │ ← 右下角可拖改变大小
│ └──────────────────────────────────────┘ │
│ ┌─ 卡片2 ──────────────────────────────┐ │
│ │ ... │ │
├─────────────────────────────────────────────┤
│ 订单占有 (5 / 100) │ ← 始终显示占用比
│ ▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
└─────────────────────────────────────────────┘
[◢] ← 右下角缩放手柄
| 按钮 | 作用 |
|---|---|
| ▾/▸ | 折叠/展开整个面板(折叠态只留标题条) |
| ⏰/🚫 | 关闭/开启本页面自动显示(关闭后不再自动弹出) |
| (右上数字) | 显示「占有 X%」徽章,按占有比例变色(≥80 红 / ≥50 橙 / 其他白) |
| ◀ | 收起面板回到右侧📦悬浮按钮 |
这两个状态都按仓位 key 自动记忆(erpBinCardStates),下次搜到同一仓位自动恢复。
之前仓位明细区整个列表随内容自然撑高,溢出部分被面板 overflow:hidden 静默切掉。修复要点:
#erp-bin-detail-row { flex: 1 1 auto !important; min-height: 0 !important; overflow: hidden !important; }
.erp-bin-list { flex: 1 1 auto !important; max-height: 100% !important;
overflow-y: auto !important; min-height: 0 !important; }
/* 滚动条样式搬到真正滚动的 .erp-bin-list 上 */
同时把面板"全折叠态"的最小高度从 360px 降到 200px,让"折叠+多卡片折叠"的紧凑场景更小。
const PAGE_KEY = (function () {
const path = location.pathname || '';
if (/purchaseout_Item/i.test(path)) return 'purchaseout';
if (/allocate_item/i.test(path)) return 'allocate';
if (/purchaseinitem/i.test(path)) return 'purchasein';
if (/PackItems/i.test(path)) return 'packitems';
return 'default';
})();
| 旧键(v6.22 及之前) | 新键(v6.23) |
|---|---|
erpBinInfoPos |
erpBinInfoPos__purchaseout |
erpBinInfoSize |
erpBinInfoSize__purchaseout |
erpBinInfoFolded |
erpBinInfoFolded__purchaseout |
erpBinInfoClosed |
erpBinInfoClosed__purchaseout |
erpBinAutoShow |
erpBinAutoShow__purchaseout |
erpBinSelectedWhIds |
erpBinSelectedWhIds__purchaseout |
erpBinSingleWhId |
erpBinSingleWhId__purchaseout |
erpBinSingleMode |
erpBinSingleMode__purchaseout |
erpBinExcludedKeys |
erpBinExcludedKeys__purchaseout |
erpBinCardStates |
erpBinCardStates__purchaseout |
erpBinSecFolded |
erpBinSecFolded__purchaseout |
erpBinTogglePos |
erpBinTogglePos__purchaseout |
allocate / purchasein / packitems / default 同理各一套。
// pStorage.set 同步写两份:拼接新键 + 老键
localStorage.setItem(key + '__' + PAGE_KEY, JSON.stringify(value));
storage.set(key, value); // ← 老键也写,旧客户端/调试可见
// pStorage.get 优先新键,无则读老键(兼容 v6.22 数据)
const raw = localStorage.getItem(key + '__' + PAGE_KEY);
if (raw !== null) return JSON.parse(raw);
return storage.get(key, fallback);
实际效果:同一台电脑、不同 ERP 单据页面之间互不串扰——你在采购出里把面板拖到屏幕右下、放在 PackItems 里把面板拉到左侧、只在调拨单里勾了"只看 7 号仓",互相不影响。
标题栏 ⏰(默认) → 点击 → 变成 🚫 + 立即隐藏面板 + 在右侧📦按钮上加「关」红点
右侧📦红「关」 → 点击 → 重新启用自动显示 + 弹出面板
这之前只有一个 erpBinInfoClosed 字段,关掉后想要恢复必须在控制台操作,很麻烦。现在右侧📦按钮自己有个带 id 的「关」徽章,点击直接恢复。
Bug 现象:跨页打开 PackItems.aspx 后,搜一个 SKU 仓位接口静默返回 0 条,看起来"该 SKU 没仓位"。
根因:旧的表单隐藏域里 owner_co_id 是上一家公司 A 的;URL 上现在的 owner 是公司 B。LoadDataToJSON 用 hidden 域 → 接口在 A 公司下查 SKU → 0 条 → 但 IsSuccess=true 没有任何报错。
修复:getCompanyCtx() 按四层兜底取 owner / auth:
1. 当前 frame URL.search 上的 ?owner_co_id=...
2. window.top URL 上的兜底
3. 同域 <iframe> contentWindow URL 上的兜底
4. localStorage.getItem('gylUser_coId_wy') 兜底
并且在 postBinStock() 里强制覆盖 hidden 域的值:
const ctx = getCompanyCtx();
if (ctx.owner) params.set('owner_co_id', ctx.owner); // ← 强制覆盖,不是只补
if (ctx.auth) params.set('authorize_co_id', ctx.auth);
注意:v6.23 之前
singleWhId是纯wh_id,v6.23 起是带B:/W:前缀的仓位 key。脚本里有一段"旧值自动补前缀"的兼容:
if (_rawSingleWhId && _rawSingleWhId.indexOf(':') === -1) {
singleWhId = 'W:' + _rawSingleWhId; // 旧值默认按仓库处理
}
Bug 现象:刚切换了仓库,搜同样的 SKU 返回 0 条;刷新页面又好了。
根因:getTokensWithGM 缓存表单令牌 5 分钟;切仓时 URL 上的 owner_co_id 变了,但 hidden 域里的还是旧的;缓存的旧令牌被直接复用,接口又查公司 A 的 SKU。
修复:给令牌加个"上下文签名":
const ctxSig = (ctx.owner || '') + '/' + (ctx.auth || '');
if (formTokens && tokenCtxSig === ctxSig && Date.now() - tokenTime < 300000) {
return formTokens; // 同公司 5 分钟内复用
}
// 切公司了 → 缓存失效,重新拉
并且 queryBinStock 在首次调用失败时强制清缓存重试一次:
if (!res.ok) {
console.warn('仓位请求失败(' + res.reason + '),重取令牌后重试...');
formTokens = null; tokenTime = 0; tokenCtxSig = '';
res = await postBinStock(await getTokensWithGM(), sku_id);
}
同时 v6.19 在面板"无数据"时多放了一个
清除筛选,显示全部仓位按钮——因为 90% 的"切换后无数据"实际是因为被记忆的固定筛选挡住的真实数据,不重置筛选就读不出来。
这是这次审阅发现的最有价值的部分——脚本"看似正常但悄悄降级"的问题。
ORDER_API 和 fetchOrderPage 里仍有硬编码 uid/coidconst ORDER_API = '...?owner_co_id=12816726&authorize_co_id=12816726';
fetchOrderPage: data: { uid: '21465083', coid: '12816726' }
v6.18 把仓位接口的公司上下文改成了从 localStorage / iframe / URL 兜底,但订单时间接口完全没动。换账号登录或者多公司共享脚本就静默拉空。
修法:参考 getCompanyCtx + getAuth() 重写 fetchOrderPage,让 uid / coid 都从 localStorage / iframe 兜底。
审查要点:grep -n "12816726\|21465083" 脚本.js,凡是没经过 getCompanyCtx 的数字字面量都打 TODO。
v6.10console.log('[仓位信息栏] 已启动 v6.10(统一兼容版)', isMobile ? ... : ...);
@version 字符串是 6.24,控制台报 v6.10。用户按版本号反馈问题会直接带偏排障方向。改版本时记得 grep "已启动" 脚本 同步更新。
@connect * 的意思是"匹配任意 host"而不是"只跨域信任"// ==UserScript==
// @connect *
配合 GM_xmlhttpRequest 工作,但脚本不会去验证返回内容来自谁。LoadDataToJSON 响应被劫持时没有任何校验:如果中间人把 datas 改成别人家的库存数据,面板会照单全收显示。要做敏感操作的话记得校验 response.finalUrl 与请求 host 一致。
done 桶function remBucketOf(status, planDeliveryDate) {
const st = status || '';
if (st === '已发货' || ...) return 'done';
const pd = parseOrderTime(planDeliveryDate);
if (!pd) return 'done'; // ← 解析失败 = 当成已完成
...
}
真实隐患:订单数据格式悄悄改一下(比如 ISO 字符串变 yyyy/MM/dd HH:mm),所有原本该发的订单都被静默归到「已发出」桶,「全部订单」卡片看着正常,但实际还有未发出的订单被静默隐藏。
修法:
unknown 桶,永远不与正常桶合并parseOrderTime 解析失败时 console.warn 输原值,方便事后查数据格式变更success 后再失败却没有「无数据占位」的提示v6.19 修了一部分 —— "重试后仍失败: <原因>\n · 若刚切换过仓库/货主,请按 F5 刷新页面后再搜一次"。但首战就 0 条且上下文未知的情况是 lastBinHint 而不是 toast 提示:
} else if (res.items.length === 0 && !res.ctxKnown) {
res.hint = '未能识别当前公司/仓库上下文(结果可能不完整),请按 F5 刷新页面后再搜一次';
console.warn('[仓位信息栏] ⚠️ ' + res.hint);
}
hint 只在"完全没数据"时打到 binRow 里。如果上游接口在「上下文正常但就是没数据」分支也走了 0 条,用户看不到这条 hint(因为 res.ctxKnown 是 true,进了 else if 的反分支)。建议把上下文缺失检测单独抽出来做成常驻黄色提示条,不依赖 items.length 是否为 0。
bindCardInteractions() 里只是 e.stopPropagation(),但卡片本身在 .erp-bin-row 里没有禁用 panel 拖动逻辑。面板拖动从 header 触发,但拖到面板底部时鼠标可能悬停到某张卡片的缩放手柄上、不会误触发;但反过来——从卡片缩放手柄开始拖动后松开,会先触发 card resize 再触发 panel mousedown 的 mouseup,导致尺寸被误存为 0 + 偏移。
修法:在 resize 启动时记一个 wasCardResizing flag,handleDragEnd 检测到就跳过位置保存。
需要改阈值就在这里找:
| 行为 | 阈值 / 字段 | 文件位置 | 默认值 |
|---|---|---|---|
| 表单令牌缓存 | CONFIG.TOKEN_EXPIRE |
第 145 行 | 5 分钟 |
| 订单占有一页大小 | CONFIG.PAGE_SIZE |
第 144 行 | 500 |
| 订单占有最大页数 | while (page > 20) |
第 303 行 | 20 |
| 订单时间一页大小 | ORDER_PAGE_SIZE |
第 523 行 | 500 |
| 订单时间最大页数 | ORDER_MAX_PAGES |
第 524 行 | 6 |
| 移动断点 | @media screen and (max-width: 768px) |
第 1668 行 | 768px |
| 面板最小尺寸 | minW, minH |
第 2244 行 | 280×360(v6.24 折叠态 200) |
| 大档位宽度阈值 | .size-lg / .size-xl |
第 2293 行 | 560 / 860 px |
| 占有比例变色 | ≥80 红 / ≥50 橙 / 其他白 | 第 2986 行 | — |
| 保留卡片数上限 | LIMIT in renderRemList |
第 713 行 | 60 单 |
| 卡片折叠态不持久化高度 | prev.minHeight 保留 |
第 2800 行 | — |
.user.js 文件或粘贴脚本内容保存@match 包含你需要的 ERP 单据页面purchaseout_Item.aspx 等生效页// 清掉所有记忆设置(按页面维度 → 4 套 + 兼容老键 12 个键)
localStorage.clear();
// 只看当前页的记忆键
Object.keys(localStorage).filter(k => k.includes('__purchaseout')).forEach(k => console.log(k, localStorage.getItem(k)));
// 手动让面板显示(绕过自动显示开关)
document.getElementById('erp-bin-info-panel').style.display = '';
[仓位信息栏] 是否捕获到 SKU 类日志 + 检查 isRelevantRequest 过滤有没有把新形态请求挡掉清除筛选,显示全部仓位 按钮(v6.19 加的)| 主题 | v6.17 文档 | v6.24 本文档 |
|---|---|---|
| 数据源数量 | 2(仓位 + 全 SKU 占有) | 3(+ 当前 SKU 的剩余发货时间) |
| 记忆键 | 11 个全局共享 | 11 × 5(按 4 种页面 + 1 个兜底副本) |
| 折叠粒度 | 无 | 整面板 + 每卡片 + 三大区块各自折叠 |
| 卡片大小 | 固定 | 每卡片单独按仓位记忆(跨列数 + 高度) |
| 自动显示 | 默认开(只有一个 closed 标志) | 加独立的 autoShow 开关 + 右侧📦「关」徽章 |
| 公司上下文 | 硬编码 | 仓位接口:动态兜底;订单时间接口:仍硬编码 |
| 切换仓库 | 易踩坑 | 自动重试 + 一键解除筛选 |
| 滚动条 | 溢出静默被切 | 真正可滚 + 自定义样式 |
完整升级点全部在第二大节,按版本倒序阅读最快。