wmsHelper

wms操作助手

  1. // ==UserScript==
  2. // @name wmsHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4 切换图片生成到40084
  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:80%; 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:10%; 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. // toolbar.insertBefore(createTextButton("PDA理货", () => {
  97. // getCurrentRelocation(true, id => {
  98. // if (isNull(id)) {
  99. // window.pageHelper.showToast("当前登录用户在当前仓库暂无实时理货任务", 4000)
  100. // } else {
  101. // GM_openInTab("https://" + domain + "wms.yqn.com/62100/transfer-ship/detail/" + id, false)
  102. // }
  103. // })
  104. // }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  105. //
  106. // toolbar.insertBefore(createTextButton("PDA移库", () => {
  107. // getCurrentRelocation(false, id => {
  108. // if (isNull(id)) {
  109. // window.pageHelper.showToast("当前登录用户在当前仓库暂无移库计划任务", 4000)
  110. // } else {
  111. // GM_openInTab("https://" + domain + "wms.yqn.com/62100/transfer-ship/detail/" + id, false)
  112. // }
  113. // })
  114. // }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  115. //
  116. // toolbar.insertBefore(createTextButton("PDA拣货", () => {
  117. // getCurrentPickingOrder(id => {
  118. // if (isNull(id)) {
  119. // window.pageHelper.showToast("当前登录用户在当前仓库暂无拣货任务", 4000)
  120. // } else {
  121. // GM_openInTab("https://" + domain + "wms.yqn.com/62100/picking/detail/" + id, false)
  122. // }
  123. // })
  124. // }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  125.  
  126. toolbar.insertBefore(createTextButton("叉车", () => {
  127. getMaxForkliftCode(forkLift => {
  128. if (forkLift === '') {
  129. window.pageHelper.showToast("没找到叉车", 1000)
  130. } else {
  131. window.pageHelper.showToast(imageHtml(forkLift), 4000)
  132. }
  133. })
  134. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  135.  
  136. toolbar.insertBefore(createTextButton("入库码", () => {
  137. getFirstContainerNo(1, containerNo => {
  138. if (isNull(containerNo)) {
  139. window.pageHelper.showToast("好像没容器了", 1000)
  140. } else {
  141. window.pageHelper.showToast(imageHtml(containerNo), 4000)
  142. }
  143. })
  144. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  145. toolbar.insertBefore(createTextButton("出库码", () => {
  146. getFirstContainerNo(2, containerNo => {
  147. if (isNull(containerNo)) {
  148. window.pageHelper.showToast("好像没容器了", 1000)
  149. } else {
  150. window.pageHelper.showToast(imageHtml(containerNo), 4000)
  151. }
  152. })
  153. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  154. toolbar.insertBefore(createTextButton("装车码", () => {
  155. getFirstContainerNo(3, containerNo => {
  156. if (isNull(containerNo)) {
  157. window.pageHelper.showToast("好像没容器了", 1000)
  158. } else {
  159. window.pageHelper.showToast(imageHtml(containerNo), 4000)
  160. }
  161. })
  162. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  163. })
  164. // 处理表格
  165. parseTable()
  166. // 监听 content 页面变动
  167. waitObserve(".layout-common-page", () => {
  168. // 关闭非 Chrome 游览器顶部下载提示
  169. const svgIcon = document.querySelector(".yqn-icon.close");
  170. if (svgIcon) {
  171. const clickEvent = new MouseEvent("click", {
  172. bubbles: true,
  173. cancelable: true
  174. });
  175. svgIcon.dispatchEvent(clickEvent);
  176. }
  177.  
  178. const module = document.querySelector(".yqn-header")
  179. if (isNull(module)) {
  180. return;
  181. }
  182. // 拣货单详情页面
  183. if (module.textContent.indexOf("拣货单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  184. pickingOrder()
  185. }
  186. // 移库单详情页面
  187. if (module.textContent.indexOf("移库单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  188.  
  189. }
  190. // 出库单详情页面
  191. if (module.textContent.indexOf("出库单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  192. outboundOrder()
  193. }
  194. // 拣货单列表
  195. if (module.textContent.indexOf("拣货单") >= 0 && module.textContent.indexOf("详情") <= 0) {
  196. pickingOrderList()
  197. }
  198. // 装车单列表
  199. if (module.textContent.indexOf("装车单") >= 0 && module.textContent.indexOf("详情") <= 0) {
  200. packingOrderList()
  201. }
  202. // 验货复核
  203. if (module.textContent.indexOf("验货复核") >= 0) {
  204. inspectionGoods()
  205. }
  206. })
  207. })
  208.  
  209. function parseTable() {
  210. setTimeout(() => {
  211. parseTable()
  212. tableExecute()
  213. }, 1500)
  214. }
  215.  
  216. // 拣货单页面
  217. function pickingOrder() {
  218. const buttons = document.querySelector(".yqn-parser-button-list");
  219. if (nonNull(buttons) && isNull(document.querySelector(".custom-outbound-button"))) {
  220. appendFlagNode(buttons, "custom-outbound-button")
  221. buttons.appendChild(createButton("去复核", () => {
  222. const customOutCode = document.querySelector(".pop-card-c.plaintext");
  223. GM_openInTab("https://" + domain + "wms.yqn.com/62100/outbound-order/inspection-goods?code=" + customOutCode.textContent, false)
  224. }))
  225. }
  226. }
  227.  
  228. // 出库单页面
  229. function outboundOrder() {
  230. const buttons = document.querySelector(".yqn-parser-button-list");
  231. if (nonNull(buttons) && isNull(document.querySelector(".custom-outbound-button"))) {
  232. appendFlagNode(buttons, "custom-outbound-button")
  233. queryPickingOrder(data => {
  234. if (nonNull(data)) {
  235. // 跳转拣货单按钮
  236. buttons.appendChild(createButton("拣货单", () => {
  237. const customOutCode = document.querySelector(".pop-card-c.plaintext");
  238. GM_openInTab("https://" + domain + "wms.yqn.com/62100/picking/list?customOutCode=" + customOutCode.textContent, false)
  239. }))
  240. // 去复核按钮
  241. buttons.appendChild(createButton("去复核", () => {
  242. GM_openInTab("https://" + domain + "wms.yqn.com/62100/outbound-order/inspection-goods?code=" + data.code, false)
  243. }))
  244. }
  245. })
  246. queryLoadingOrder(data => {
  247. if (nonNull(data)) {
  248. // 跳转装车单按钮
  249. buttons.appendChild(createButton("装车单", () => {
  250. const customOutCode = document.querySelector(".pop-card-c.plaintext");
  251. GM_openInTab("https://" + domain + "wms.yqn.com/62100/packing/list?customOutCode=" + customOutCode.textContent, false)
  252. }))
  253. }
  254. })
  255. }
  256. }
  257.  
  258. // 拣货单列表
  259. function pickingOrderList() {
  260. const customOutCode = new URLSearchParams(window.location.href.split('?')[1]).get('customOutCode');
  261. if (isNull(customOutCode)) {
  262. return
  263. }
  264. // 搜索出库单号的拣货单
  265. const node = document.querySelector("#outboundCode");
  266. if (nonNull(node) && isNull(document.querySelector(".custom-input"))) {
  267. appendFlagNode(document.body, "custom-input")
  268. window.pageHelper.clickUpAndDown(node, customOutCode)
  269. }
  270. }
  271.  
  272. // 装车单列表
  273. function packingOrderList() {
  274. const customOutCode = new URLSearchParams(window.location.href.split('?')[1]).get('customOutCode');
  275. if (isNull(customOutCode)) {
  276. return
  277. }
  278. // 搜索出库单号的装车单
  279. const node = document.querySelector("#outBoundOrderCode");
  280. if (nonNull(node) && isNull(document.querySelector(".custom-input"))) {
  281. appendFlagNode(document.body, "custom-input")
  282. window.pageHelper.sleep(400)
  283. .then(() => {
  284. window.pageHelper.clickUpAndDown(node, customOutCode)
  285. const search = document.querySelector(".yqn-filter-operation .ant-btn.ant-btn-primary.perf-tracked.yqn-button");
  286. search.click()
  287. })
  288. }
  289. }
  290.  
  291. // 验货复核
  292. function inspectionGoods() {
  293. const code = new URLSearchParams(window.location.href.split('?')[1]).get('code');
  294. if (isNull(code)) {
  295. return
  296. }
  297. const node = document.querySelector("#pickContainerNo");
  298. if (nonNull(node) && isNull(document.querySelector(".custom-input"))) {
  299. appendFlagNode(document.body, "custom-input")
  300. window.pageHelper.clickUpAndDown(node, code)
  301. // 创建一个KeyboardEvent对象并设置相关属性
  302. const event = new KeyboardEvent('keydown', {
  303. key: 'Enter',
  304. bubbles: true,
  305. cancelable: true,
  306. keyCode: 13
  307. });
  308. // 通过目标元素触发KeyboardEvent对象
  309. node.dispatchEvent(event);
  310. }
  311. }
  312.  
  313. // 表格处理
  314. function tableExecute() {
  315. try {
  316. // table 容器
  317. const containers = document.querySelectorAll(".ant-table-container");
  318. if (containers == null || containers.length === 0) {
  319. return
  320. }
  321. for (let i = 0; i < containers.length; i++) {
  322. const container = containers[i];
  323. const tHeader = container.querySelector(".ant-table-header")
  324. let keyIndexArray = []
  325. const ths = tHeader.querySelectorAll("th");
  326. for (let i = 0; i < ths.length; i++) {
  327. const text = ths[i].innerText.toLowerCase();
  328. if (text.indexOf("sku".toLowerCase()) >= 0 ||
  329. text.indexOf("UPC".toLowerCase()) >= 0 ||
  330. text.indexOf("识别码".toLowerCase()) >= 0 ||
  331. text.indexOf("容器号".toLowerCase()) >= 0 ||
  332. text.indexOf("库位".toLowerCase()) >= 0 ||
  333. text.indexOf("叉车编码".toLowerCase()) >= 0 ||
  334. text.indexOf("容器编码".toLowerCase()) >= 0 ||
  335. text.indexOf("序列号".toLowerCase()) >= 0 ||
  336. text.indexOf("SN码".toLowerCase()) >= 0) {
  337. keyIndexArray.push(i)
  338. }
  339. }
  340. const tBody = container.querySelector(".ant-table-tbody")
  341. let lines = tBody.querySelectorAll(".ant-table-row.ant-table-row-level-0");
  342. if (lines == null) {
  343. lines = tBody.querySelectorAll(".ant-table-row.ant-table-row-level-0")
  344. }
  345. for (let i = 0; i < lines.length; i++) {
  346. const line = lines[i];
  347. let tds = line.querySelectorAll("td");
  348. if (tds[keyIndexArray[0]].innerText.indexOf("QRC") >= 0 || line.innerText.indexOf("QRC") >= 0) {
  349. continue
  350. }
  351. let dataArray = []
  352. for (let i = 0; i < tds.length; i++) {
  353. if (keyIndexArray.indexOf(i) >= 0) {
  354. dataArray.push(tds[i].innerText)
  355. }
  356. }
  357. if (dataArray.length > 0 && tds[keyIndexArray[0]].innerText.indexOf("QRC") < 0) {
  358. let td = tds[keyIndexArray[0]];
  359. if (td.querySelector("span") != null) {
  360. td = td.querySelector("span")
  361. }
  362. td.appendChild(createTextButton("QRC", () => {
  363. window.pageHelper.showToast(imageHtml(dataArray), 7000)
  364. }))
  365. }
  366. }
  367. }
  368. } catch (e) {
  369. throw e
  370. }
  371. }
  372.  
  373. // 追加标记节点
  374. function appendFlagNode(node, flag) {
  375. const divFlag = document.createElement("div");
  376. node.appendChild(divFlag)
  377. divFlag.className = flag;
  378. divFlag.style.display = "none"
  379. }
  380.  
  381. function imageHtml() {
  382. let html = "<div style=\"display: flex;margin-top: 10px;margin-bottom: 10px\">";
  383. if (arguments.length === 1 && arguments[0] instanceof Array) {
  384. for (let i = 0; i < arguments[0].length; i++) {
  385. html += " <img src=\"https://gw-wms.yqn.com/api/40084/yqn_video/additional/qrcode_create_png?code=" + arguments[0][i] + "&width=220&height=220&desc=1\" alt='" + arguments[0][i] + "'/>";
  386. if (i !== arguments[0].length - 1) {
  387. html += " <span style=\"width: 170px\"></span>";
  388. }
  389. }
  390. } else {
  391. for (let i = 0; i < arguments.length; i++) {
  392. html += " <img src=\"https://gw-wms.yqn.com/api/40084/yqn_video/additional/qrcode_create_png?code=" + arguments[i] + "&width=220&height=220&desc=1\" alt='" + arguments[i] + "'/>";
  393. if (i !== arguments.length - 1) {
  394. html += " <span style=\"width: 170px\"></span>";
  395. }
  396. }
  397. }
  398. html += "</div>";
  399. return html;
  400. }
  401.  
  402. function nonNull(o) {
  403. return o !== null && o !== undefined;
  404. }
  405.  
  406. function isNull(o) {
  407. return o === null || o === undefined;
  408. }
  409.  
  410. // 等待出现并监听变化
  411. function waitObserve(visibleTag, fun, attributes = true) {
  412. window.pageHelper.waitElementVisible(visibleTag, 0, () => {
  413. new MutationObserver(function (mutationsList) {
  414. fun()
  415. }).observe(document.querySelector(visibleTag), {
  416. attributes: attributes,
  417. childList: true,
  418. subtree: true,
  419. characterData: true
  420. })
  421. })
  422. }
  423.  
  424. // 创建文本按钮
  425. function createTextButton(name, listener, className = "ant-btn ant-btn-link perf-tracked yqn-button yqn-link-no-padding customer-button") {
  426. const button = document.createElement("button")
  427. button.type = "button"
  428. button.id = name
  429. button.className = className
  430. button.onclick = listener
  431. const span = document.createElement("span")
  432. span.textContent = name
  433. button.appendChild(span)
  434. return button;
  435. }
  436.  
  437. // 创建按钮
  438. function createButton(name, listener) {
  439. const button = document.createElement("button")
  440. button.type = "button"
  441. button.id = name
  442. button.className = "ant-btn ant-btn-default perf-tracked yqn-button"
  443. button.onclick = listener
  444. const a = document.createElement("a")
  445. a.textContent = name
  446. a.className = "render-button-text"
  447. button.appendChild(a)
  448. return button;
  449. }
  450.  
  451. // 拿一个新容器
  452. function getFirstContainerNo(type, fuc) {
  453. request('/yqn_wms/bg/container/v2/list', {
  454. "statusList": [1],
  455. "containerType": type
  456. }, data => {
  457. if (nonNull(data.content) && data.content.length > 0) {
  458. fuc(data.content[0].containerCode)
  459. } else {
  460. fuc(null)
  461. }
  462. })
  463. }
  464.  
  465. // 拿一个全能叉车
  466. function getMaxForkliftCode(fuc) {
  467. request('/yqn_wms/bg/forklift/v2/list', {}, data => {
  468. if (nonNull(data.content) && data.content.length > 0) {
  469. let emptyCode = ''
  470. let code = ''
  471. let areMaxLayer = 0
  472. let areMaxLen = 0
  473. let emptyAreMaxLayer = 0;
  474. for (let i = 0; i < data.content.length; i++) {
  475. const forklift = data.content[i];
  476. const num = isNull(forklift.maximumLayer) ? 0 : forklift.maximumLayer;
  477. // 空库区
  478. if (isNull(forklift.warehouseAreaList) || forklift.warehouseAreaList.length === 0) {
  479. if (emptyAreMaxLayer <= num) {
  480. emptyAreMaxLayer = num;
  481. emptyCode = forklift.code;
  482. }
  483. } else {
  484. // 非空库区
  485. if (areMaxLen <= forklift.warehouseAreaList.length && areMaxLayer <= num) {
  486. areMaxLen = forklift.warehouseAreaList.length;
  487. areMaxLayer = num;
  488. code = forklift.code;
  489. }
  490. }
  491. }
  492. fuc(emptyCode.length !== 0 ? emptyCode : code)
  493. } else {
  494. fuc(null)
  495. }
  496. })
  497. }
  498.  
  499. // 拿当前拣货单
  500. function getCurrentPickingOrder(fuc) {
  501. request('/yqn_wms/app/picking/v2/get_task', {}, data => {
  502. fuc(data.id)
  503. })
  504. }
  505.  
  506. // 拿移库单
  507. function getCurrentRelocation(real, fuc) {
  508. request('/yqn_wms/app/relocation/v2/' + (real ? 'get_immediately' : 'get_task'), {}, data => {
  509. fuc(data.id)
  510. })
  511. }
  512.  
  513. // 查询出库单对应拣货单
  514. function queryPickingOrder(fuc) {
  515. request('/yqn_wms/bg/picking/v2/list', {
  516. "outboundCode": new URLSearchParams(window.location.href.split('?')[1]).get('code'),
  517. }, data => {
  518. if (nonNull(data.content) && data.content.length > 0) {
  519. for (let i = 0; i < data.content.length; i++) {
  520. fuc(data.content[i])
  521. }
  522. } else {
  523. fuc(null)
  524. }
  525. })
  526. }
  527.  
  528. // 查询出库单对应装车单
  529. function queryLoadingOrder(fuc) {
  530. request('/yqn_wms/bg/loading_order/v2/list', {
  531. "outBoundOrderCode": new URLSearchParams(window.location.href.split('?')[1]).get('code')
  532. }, data => {
  533. if (nonNull(data.content) && data.content.length > 0) {
  534. for (let i = 0; i < data.content.length; i++) {
  535. fuc(data.content[i])
  536. }
  537. } else {
  538. fuc(null)
  539. }
  540. })
  541. }
  542.  
  543. // 发起请求
  544. function request(interfaceUrl, model, success) {
  545. const url = 'https://' + domain + 'gw-wms.yqn.com/api/40081/api/call' + interfaceUrl
  546. model.page = 1
  547. model.size = 100
  548. model.warehouseId = localStorage.getItem("warehouseId")
  549. jq.ajax({
  550. url: url,
  551. method: 'POST',
  552. xhrFields: {
  553. withCredentials: true
  554. },
  555. crossDomain: true,
  556. contentType: 'application/json',
  557. data: JSON.stringify({
  558. "header": {
  559. "xSourceAppId": "63008",
  560. "guid": "6f87e073-1da1-4017-b2de-c109abcd6d123",
  561. "lang": "zh",
  562. "timezone": "Asia/Shanghai"
  563. },
  564. "model": model
  565. }),
  566. success: function (response) {
  567. if (response.code === 200 && nonNull(response.data)) {
  568. success(response.data)
  569. }
  570. },
  571. error: function (xhr, status, error) {
  572. console.log('Request failed:', error);
  573. }
  574. });
  575. }
  576. })();