wmsHelper

wms操作助手

目前为 2024-06-13 提交的版本,查看 最新版本

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