wmsHelper

wms操作助手

当前为 2023-12-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name wmsHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.5
  5. // @description wms操作助手
  6. // @author Ziker
  7. // @match https://wms.yqn.com/62100/*
  8. // @match https://pr-wms.yqn.com/62100/*
  9. // @match https://qa4-wms.yqn.com/62100/*
  10. // @require https://code.jquery.com/jquery-3.4.1.min.js
  11. // @icon https://favicon.qqsuu.cn/work.yqn.com
  12. // @grant GM_openInTab
  13. // @grant unsafeWindow
  14. // @grant window.close
  15. // @grant window.focus
  16. // @run-at document-body
  17. // @noframes
  18. // @license AGPL License
  19. // ==/UserScript==
  20.  
  21. window.jq = $.noConflict(true);
  22.  
  23. (function (window) {
  24. window.pageHelper = {
  25. // 等待元素可见
  26. async waitElementVisible(visibleTag, index, fun) {
  27. let node = jq(visibleTag)
  28. if (node === null || node[index] === null || node[index] === undefined) {
  29. setTimeout(() => {
  30. pageHelper.waitElementVisible(visibleTag, index, fun)
  31. }, 500)
  32. } else {
  33. fun()
  34. }
  35. },
  36. sleep(duration) {
  37. return new Promise(resolve => {
  38. setTimeout(resolve, duration)
  39. })
  40. },
  41. showToast(msg, duration) {
  42. const oldNotify = document.querySelector(".custom-notify");
  43. if (oldNotify !== undefined && oldNotify !== null) {
  44. document.body.removeChild(oldNotify)
  45. }
  46. // 显示提示
  47. duration = isNaN(duration) ? 3000 : duration;
  48. const m = document.createElement('div');
  49. m.className = "custom-notify"
  50. m.innerHTML = msg;
  51. m.style.cssText = "display: flex;justify-content: center;align-items: center;width:60%; min-width:180px; " +
  52. "background:#000000; opacity:0.98; height:auto;min-height: 50px;font-size:25px; color:#fff; " +
  53. "line-height:30px; text-align:center; border-radius:4px; position:fixed; top:60%; left:20%; z-index:999999;";
  54. document.body.appendChild(m);
  55. setTimeout(function () {
  56. const d = 0.5;
  57. m.style.webkitTransition = '-webkit-transform ' + d + 's ease-in, opacity ' + d + 's ease-in';
  58. m.style.opacity = '0';
  59. setTimeout(function () {
  60. if (document.body.contains(m)) {
  61. document.body.removeChild(m)
  62. }
  63. }, d * 1000);
  64. }, duration);
  65. },
  66. // 模拟键盘按下
  67. clickUpAndDown(inputElement, keyChar) {
  68. let lastValue = inputElement.value;
  69. inputElement.value = keyChar;
  70. let event = new Event("input", {bubbles: true});
  71. // React15
  72. event.simulated = true;
  73. // React16 内部定义了descriptor拦截value,此处重置状态
  74. let tracker = inputElement._valueTracker;
  75. if (tracker) {
  76. tracker.setValue(lastValue);
  77. }
  78. inputElement.dispatchEvent(event);
  79. }
  80. }
  81. })(window);
  82.  
  83.  
  84. (function () {
  85. 'use strict';
  86. const domain = window.location.href.indexOf("qa4") >= 0 ? 'qa4-' : window.location.href.indexOf("pr-") >= 0 ? 'pr-' : '';
  87. jq(document).ready(function () {
  88. // 顶部工具栏变化
  89. waitObserve("#app", () => {
  90. if (nonNull(document.querySelector(".forkButton"))) {
  91. return;
  92. }
  93. const toolbar = document.querySelector(".yqn-topbar-module");
  94. appendFlagNode(toolbar, "forkButton")
  95. const firstChildNode = toolbar.childNodes[0];
  96.  
  97. toolbar.insertBefore(createTextButton("PDA理货", () => {
  98. getCurrentRelocation(true, id => {
  99. if (isNull(id)) {
  100. window.pageHelper.showToast("当前登录用户在当前仓库暂无实时理货任务", 4000)
  101. } else {
  102. GM_openInTab("https://" + domain + "wms.yqn.com/62100/transfer-ship/detail/" + id, false)
  103. }
  104. })
  105. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  106.  
  107. toolbar.insertBefore(createTextButton("PDA移库", () => {
  108. getCurrentRelocation(false, id => {
  109. if (isNull(id)) {
  110. window.pageHelper.showToast("当前登录用户在当前仓库暂无移库计划任务", 4000)
  111. } else {
  112. GM_openInTab("https://" + domain + "wms.yqn.com/62100/transfer-ship/detail/" + id, false)
  113. }
  114. })
  115. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  116.  
  117. toolbar.insertBefore(createTextButton("PDA拣货", () => {
  118. getCurrentPickingOrder(id => {
  119. if (isNull(id)) {
  120. window.pageHelper.showToast("当前登录用户在当前仓库暂无拣货任务", 4000)
  121. } else {
  122. GM_openInTab("https://" + domain + "wms.yqn.com/62100/picking/detail/" + id, false)
  123. }
  124. })
  125. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  126.  
  127. toolbar.insertBefore(createTextButton("叉车", () => {
  128. getMaxForkliftCode(forkLift => {
  129. if (forkLift === '') {
  130. window.pageHelper.showToast("没找到叉车", 1000)
  131. } else {
  132. window.pageHelper.showToast(imageHtml(forkLift), 4000)
  133. }
  134. })
  135. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  136.  
  137. toolbar.insertBefore(createTextButton("容器", () => {
  138. getFirstContainerNo(containerNo => {
  139. if (isNull(containerNo)) {
  140. window.pageHelper.showToast("好像没容器了", 1000)
  141. } else {
  142. window.pageHelper.showToast(imageHtml(containerNo), 4000)
  143. }
  144. })
  145. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  146. })
  147. // 监听 content 页面变动
  148. waitObserve(".layout-common-page", () => {
  149. const module = document.querySelector(".yqn-header")
  150. console.log(12356)
  151. if (isNull(module)) {
  152. return;
  153. }
  154. // 拣货单详情页面
  155. if (module.textContent.indexOf("拣货单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  156. pickingOrder()
  157. }
  158. // 拣货单详情页面
  159. if (module.textContent.indexOf("移库单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  160. relocationOrder()
  161. }
  162. // 出库单详情页面
  163. if (module.textContent.indexOf("出库单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  164. outboundOrder()
  165. }
  166. // 拣货单列表
  167. if (module.textContent.indexOf("拣货单") >= 0 && module.textContent.indexOf("详情") <= 0) {
  168. pickingOrderList()
  169. }
  170. // 装车单列表
  171. if (module.textContent.indexOf("装车单") >= 0 && module.textContent.indexOf("详情") <= 0) {
  172. packingOrderList()
  173. }
  174. // 验货复核
  175. if (module.textContent.indexOf("验货复核") >= 0) {
  176. inspectionGoods()
  177. }
  178. })
  179. })
  180.  
  181. // 拣货单页面
  182. function pickingOrder() {
  183. const buttons = document.querySelector(".yqn-parser-button-list");
  184. if (nonNull(buttons) && isNull(document.querySelector(".custom-outbound-button"))) {
  185. appendFlagNode(buttons, "custom-outbound-button")
  186. buttons.appendChild(createButton("去复核", () => {
  187. const customOutCode = document.querySelector(".pop-card-c.plaintext");
  188. GM_openInTab("https://" + domain + "wms.yqn.com/62100/outbound-order/inspection-goods?code=" + customOutCode.textContent, false)
  189. }))
  190. }
  191. tableExecute(line => {
  192. const sku = line.querySelectorAll("td")[1].innerText;
  193. const warehouseLocation1 = line.querySelectorAll("td")[4].innerText;
  194. line.querySelectorAll("td")[1].appendChild(createTextButton("=>生成", () => {
  195. window.pageHelper.showToast(imageHtml(sku, warehouseLocation1), 7000)
  196. }))
  197. })
  198. }
  199.  
  200. // 移库单页面
  201. function relocationOrder() {
  202. tableExecute(line => {
  203. const sku = line.querySelectorAll("td")[1].innerText;
  204. const warehouseLocation1 = line.querySelectorAll("td")[4].innerText;
  205. const warehouseLocation2 = line.querySelectorAll("td")[5].innerText;
  206. line.querySelectorAll("td")[1].appendChild(createTextButton("=>生成", () => {
  207. window.pageHelper.showToast(imageHtml(sku, warehouseLocation1, warehouseLocation2), 7000)
  208. }))
  209. })
  210. }
  211.  
  212. // 出库单页面
  213. function outboundOrder() {
  214. const buttons = document.querySelector(".yqn-parser-button-list");
  215. if (nonNull(buttons) && isNull(document.querySelector(".custom-outbound-button"))) {
  216. appendFlagNode(buttons, "custom-outbound-button")
  217. queryPickingOrder(data => {
  218. if (nonNull(data)) {
  219. // 跳转拣货单按钮
  220. buttons.appendChild(createButton("拣货单", () => {
  221. const customOutCode = document.querySelector(".pop-card-c.plaintext");
  222. GM_openInTab("https://" + domain + "wms.yqn.com/62100/picking/list?customOutCode=" + customOutCode.textContent, false)
  223. }))
  224. // 去复核按钮
  225. buttons.appendChild(createButton("去复核", () => {
  226. GM_openInTab("https://" + domain + "wms.yqn.com/62100/outbound-order/inspection-goods?code=" + data.code, false)
  227. }))
  228. }
  229. })
  230. queryLoadingOrder(data => {
  231. if (nonNull(data)) {
  232. // 跳转装车单按钮
  233. buttons.appendChild(createButton("装车单", () => {
  234. const customOutCode = document.querySelector(".pop-card-c.plaintext");
  235. GM_openInTab("https://" + domain + "wms.yqn.com/62100/packing/list?customOutCode=" + customOutCode.textContent, false)
  236. }))
  237. }
  238. })
  239. }
  240. }
  241.  
  242. // 拣货单列表
  243. function pickingOrderList() {
  244. const customOutCode = new URLSearchParams(window.location.href.split('?')[1]).get('customOutCode');
  245. if (isNull(customOutCode)) {
  246. return
  247. }
  248. // 搜索出库单号的拣货单
  249. const node = document.querySelector("#outboundCode");
  250. if (nonNull(node) && isNull(document.querySelector(".custom-input"))) {
  251. appendFlagNode(document.body, "custom-input")
  252. window.pageHelper.clickUpAndDown(node, customOutCode)
  253. }
  254. }
  255.  
  256. // 装车单列表
  257. function packingOrderList() {
  258. const customOutCode = new URLSearchParams(window.location.href.split('?')[1]).get('customOutCode');
  259. if (isNull(customOutCode)) {
  260. return
  261. }
  262. // 搜索出库单号的装车单
  263. const node = document.querySelector("#outBoundOrderCode");
  264. if (nonNull(node) && isNull(document.querySelector(".custom-input"))) {
  265. appendFlagNode(document.body, "custom-input")
  266. window.pageHelper.sleep(400)
  267. .then(() => {
  268. window.pageHelper.clickUpAndDown(node, customOutCode)
  269. const search = document.querySelector(".yqn-filter-operation .ant-btn.ant-btn-primary.perf-tracked.yqn-button");
  270. search.click()
  271. })
  272. }
  273. }
  274.  
  275. // 验货复核
  276. function inspectionGoods() {
  277. const code = new URLSearchParams(window.location.href.split('?')[1]).get('code');
  278. if (isNull(code)) {
  279. return
  280. }
  281. console.log(123)
  282. const node = document.querySelector("#recheck-pick-container-input");
  283. if (nonNull(node) && isNull(document.querySelector(".custom-input"))) {
  284. appendFlagNode(document.body, "custom-input")
  285. window.pageHelper.clickUpAndDown(node, code)
  286. // 创建一个KeyboardEvent对象并设置相关属性
  287. const event = new KeyboardEvent('keydown', {
  288. key: 'Enter',
  289. bubbles: true,
  290. cancelable: true,
  291. keyCode: 13
  292. });
  293. // 通过目标元素触发KeyboardEvent对象
  294. node.dispatchEvent(event);
  295. }
  296. }
  297.  
  298. // 表格处理
  299. function tableExecute(lineFun) {
  300. waitObserve(".ant-tabs-content.ant-tabs-content-top .ant-table-container .ant-table-tbody", () => {
  301. if (nonNull(document.querySelector(".customer-button-sku"))) {
  302. return;
  303. }
  304. appendFlagNode(document.querySelector(".yqn-header"), "customer-button-sku")
  305. window.pageHelper.sleep(500)
  306. .then(() => {
  307. const tbody = document.querySelector(".ant-tabs-content.ant-tabs-content-top .ant-table-container .ant-table-tbody")
  308. const lines = tbody.querySelectorAll(".ant-table-row.ant-table-row-level-0");
  309. for (let i = 0; i < lines.length; i++) {
  310. lineFun(lines[i])
  311. }
  312. })
  313. })
  314. }
  315.  
  316. // 追加标记节点
  317. function appendFlagNode(node, flag) {
  318. const divFlag = document.createElement("div");
  319. node.appendChild(divFlag)
  320. divFlag.className = flag;
  321. divFlag.style.display = "none"
  322. }
  323.  
  324. function imageHtml() {
  325. let html = "<div style=\"display: flex;margin-top: 10px;margin-bottom: 10px\">";
  326. for (let i = 0; i < arguments.length; i++) {
  327. html += " <img src=\"https://wwsix.cn/endpoint/v1/qrcode/create?code=" + arguments[i] + "&width=220&height=220&desc=1\" alt='" + arguments[i] + "'/>";
  328. if (i !== arguments.length - 1) {
  329. html += " <span style=\"width: 170px\"></span>";
  330. }
  331. }
  332. html += "</div>";
  333. return html;
  334. }
  335.  
  336. function nonNull(o) {
  337. return o !== null && o !== undefined;
  338. }
  339.  
  340. function isNull(o) {
  341. return o === null || o === undefined;
  342. }
  343.  
  344. // 等待出现并监听变化
  345. function waitObserve(visibleTag, fun, attributes = true) {
  346. window.pageHelper.waitElementVisible(visibleTag, 0, () => {
  347. new MutationObserver(function (mutationsList) {
  348. fun()
  349. }).observe(document.querySelector(visibleTag), {
  350. attributes: attributes,
  351. childList: true,
  352. subtree: true,
  353. characterData: true
  354. })
  355. })
  356. }
  357.  
  358. // 创建文本按钮
  359. function createTextButton(name, listener, className = "ant-btn ant-btn-link perf-tracked yqn-button yqn-link-no-padding customer-button") {
  360. const button = document.createElement("button")
  361. button.type = "button"
  362. button.id = name
  363. button.className = className
  364. button.onclick = listener
  365. const span = document.createElement("span")
  366. span.textContent = name
  367. button.appendChild(span)
  368. return button;
  369. }
  370.  
  371. // 创建按钮
  372. function createButton(name, listener) {
  373. const button = document.createElement("button")
  374. button.type = "button"
  375. button.id = name
  376. button.className = "ant-btn ant-btn-default perf-tracked yqn-button"
  377. button.onclick = listener
  378. const a = document.createElement("a")
  379. a.textContent = name
  380. a.className = "render-button-text"
  381. button.appendChild(a)
  382. return button;
  383. }
  384.  
  385. // 拿一个新容器
  386. function getFirstContainerNo(fuc) {
  387. request('/yqn_wms/bg/container/v2/list', {}, data => {
  388. if (nonNull(data.content) && data.content.length > 0) {
  389. fuc(data.content[0].containerCode)
  390. } else {
  391. fuc(null)
  392. }
  393. })
  394. }
  395.  
  396. // 拿一个全能叉车
  397. function getMaxForkliftCode(fuc) {
  398. request('/yqn_wms/bg/forklift/v2/list', {}, data => {
  399. if (nonNull(data.content) && data.content.length > 0) {
  400. let emptyCode = ''
  401. let code = ''
  402. let areMaxLayer = 0
  403. let areMaxLen = 0
  404. let emptyAreMaxLayer = 0;
  405. for (let i = 0; i < data.content.length; i++) {
  406. const forklift = data.content[i];
  407. const num = isNull(forklift.maximumLayer) ? 0 : forklift.maximumLayer;
  408. // 空库区
  409. if (isNull(forklift.warehouseAreaList) || forklift.warehouseAreaList.length === 0) {
  410. if (emptyAreMaxLayer <= num) {
  411. emptyAreMaxLayer = num;
  412. emptyCode = forklift.code;
  413. }
  414. } else {
  415. // 非空库区
  416. if (areMaxLen <= forklift.warehouseAreaList.length && areMaxLayer <= num) {
  417. areMaxLen = forklift.warehouseAreaList.length;
  418. areMaxLayer = num;
  419. code = forklift.code;
  420. }
  421. }
  422. }
  423. fuc(emptyCode.length !== 0 ? emptyCode : code)
  424. } else {
  425. fuc(null)
  426. }
  427. })
  428. }
  429.  
  430. // 拿当前拣货单
  431. function getCurrentPickingOrder(fuc) {
  432. request('/yqn_wms/app/picking/v2/get_task', {}, data => {
  433. fuc(data.id)
  434. })
  435. }
  436.  
  437. // 拿移库单
  438. function getCurrentRelocation(real, fuc) {
  439. request('/yqn_wms/app/relocation/v2/' + (real ? 'get_immediately' : 'get_task'), {}, data => {
  440. fuc(data.id)
  441. })
  442. }
  443.  
  444. // 查询出库单对应拣货单
  445. function queryPickingOrder(fuc) {
  446. request('/yqn_wms/bg/picking/v2/list', {
  447. "outboundCode": new URLSearchParams(window.location.href.split('?')[1]).get('code'),
  448. }, data => {
  449. if (nonNull(data.content) && data.content.length > 0) {
  450. for (let i = 0; i < data.content.length; i++) {
  451. fuc(data.content[i])
  452. }
  453. } else {
  454. fuc(null)
  455. }
  456. })
  457. }
  458.  
  459. // 查询出库单对应装车单
  460. function queryLoadingOrder(fuc) {
  461. request('/yqn_wms/bg/loading_order/v2/list', {
  462. "outBoundOrderCode": new URLSearchParams(window.location.href.split('?')[1]).get('code')
  463. }, data => {
  464. if (nonNull(data.content) && data.content.length > 0) {
  465. for (let i = 0; i < data.content.length; i++) {
  466. fuc(data.content[i])
  467. }
  468. } else {
  469. fuc(null)
  470. }
  471. })
  472. }
  473.  
  474. // 发起请求
  475. function request(interfaceUrl, model, success) {
  476. const url = 'https://' + domain + 'gw-wms.yqn.com/api/40081/api/call' + interfaceUrl
  477. model.page = 1
  478. model.size = 100
  479. model.warehouseId = localStorage.getItem("warehouseId")
  480. jq.ajax({
  481. url: url,
  482. method: 'POST',
  483. xhrFields: {
  484. withCredentials: true
  485. },
  486. crossDomain: true,
  487. contentType: 'application/json',
  488. data: JSON.stringify({
  489. "header": {
  490. "xSourceAppId": "63008",
  491. "guid": "6f87e073-1da1-4017-b2de-c109abcd6d123",
  492. "lang": "zh",
  493. "timezone": "Asia/Shanghai"
  494. },
  495. "model": model
  496. }),
  497. success: function (response) {
  498. if (response.code === 200 && nonNull(response.data)) {
  499. success(response.data)
  500. }
  501. },
  502. error: function (xhr, status, error) {
  503. console.log('Request failed:', error);
  504. }
  505. });
  506. }
  507. })();