wmsHelper

wms操作助手

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

  1. // ==UserScript==
  2. // @name wmsHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.1
  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("容器编码".toLowerCase()) >= 0 ||
  324. text.indexOf("序列号".toLowerCase()) >= 0 ||
  325. text.indexOf("SN码".toLowerCase()) >= 0) {
  326. keyIndexArray.push(i)
  327. }
  328. }
  329. const tBody = container.querySelector(".ant-table-tbody")
  330. let lines = tBody.querySelectorAll(".ant-table-row.ant-table-row-level-0");
  331. if (lines == null) {
  332. lines = tBody.querySelectorAll(".ant-table-row.ant-table-row-level-0")
  333. }
  334. for (let i = 0; i < lines.length; i++) {
  335. const line = lines[i];
  336. let tds = line.querySelectorAll("td");
  337. if (tds[keyIndexArray[0]].innerText.indexOf("QRC") >= 0 || line.innerText.indexOf("QRC") >= 0) {
  338. continue
  339. }
  340. let dataArray = []
  341. for (let i = 0; i < tds.length; i++) {
  342. if (keyIndexArray.indexOf(i) >= 0) {
  343. dataArray.push(tds[i].innerText)
  344. }
  345. }
  346. if (dataArray.length > 0 && tds[keyIndexArray[0]].innerText.indexOf("QRC") < 0) {
  347. let td = tds[keyIndexArray[0]];
  348. if (td.querySelector("span") != null) {
  349. td = td.querySelector("span")
  350. }
  351. td.appendChild(createTextButton("QRC", () => {
  352. window.pageHelper.showToast(imageHtml(dataArray), 7000)
  353. }))
  354. }
  355. }
  356. }
  357. } catch (e) {
  358. throw e
  359. }
  360. }
  361.  
  362. // 追加标记节点
  363. function appendFlagNode(node, flag) {
  364. const divFlag = document.createElement("div");
  365. node.appendChild(divFlag)
  366. divFlag.className = flag;
  367. divFlag.style.display = "none"
  368. }
  369.  
  370. function imageHtml() {
  371. let html = "<div style=\"display: flex;margin-top: 10px;margin-bottom: 10px\">";
  372. if (arguments.length === 1 && arguments[0] instanceof Array) {
  373. for (let i = 0; i < arguments[0].length; i++) {
  374. html += " <img src=\"https://wwsix.cn/endpoint/v1/qrcode/create?code=" + arguments[0][i] + "&width=220&height=220&desc=1\" alt='" + arguments[0][i] + "'/>";
  375. if (i !== arguments[0].length - 1) {
  376. html += " <span style=\"width: 170px\"></span>";
  377. }
  378. }
  379. } else {
  380. for (let i = 0; i < arguments.length; i++) {
  381. html += " <img src=\"https://wwsix.cn/endpoint/v1/qrcode/create?code=" + arguments[i] + "&width=220&height=220&desc=1\" alt='" + arguments[i] + "'/>";
  382. if (i !== arguments.length - 1) {
  383. html += " <span style=\"width: 170px\"></span>";
  384. }
  385. }
  386. }
  387. html += "</div>";
  388. return html;
  389. }
  390.  
  391. function nonNull(o) {
  392. return o !== null && o !== undefined;
  393. }
  394.  
  395. function isNull(o) {
  396. return o === null || o === undefined;
  397. }
  398.  
  399. // 等待出现并监听变化
  400. function waitObserve(visibleTag, fun, attributes = true) {
  401. window.pageHelper.waitElementVisible(visibleTag, 0, () => {
  402. new MutationObserver(function (mutationsList) {
  403. fun()
  404. }).observe(document.querySelector(visibleTag), {
  405. attributes: attributes,
  406. childList: true,
  407. subtree: true,
  408. characterData: true
  409. })
  410. })
  411. }
  412.  
  413. // 创建文本按钮
  414. function createTextButton(name, listener, className = "ant-btn ant-btn-link perf-tracked yqn-button yqn-link-no-padding customer-button") {
  415. const button = document.createElement("button")
  416. button.type = "button"
  417. button.id = name
  418. button.className = className
  419. button.onclick = listener
  420. const span = document.createElement("span")
  421. span.textContent = name
  422. button.appendChild(span)
  423. return button;
  424. }
  425.  
  426. // 创建按钮
  427. function createButton(name, listener) {
  428. const button = document.createElement("button")
  429. button.type = "button"
  430. button.id = name
  431. button.className = "ant-btn ant-btn-default perf-tracked yqn-button"
  432. button.onclick = listener
  433. const a = document.createElement("a")
  434. a.textContent = name
  435. a.className = "render-button-text"
  436. button.appendChild(a)
  437. return button;
  438. }
  439.  
  440. // 拿一个新容器
  441. function getFirstContainerNo(type, fuc) {
  442. request('/yqn_wms/bg/container/v2/list', {
  443. "statusList": [1],
  444. "containerType": type
  445. }, data => {
  446. if (nonNull(data.content) && data.content.length > 0) {
  447. fuc(data.content[0].containerCode)
  448. } else {
  449. fuc(null)
  450. }
  451. })
  452. }
  453.  
  454. // 拿一个全能叉车
  455. function getMaxForkliftCode(fuc) {
  456. request('/yqn_wms/bg/forklift/v2/list', {}, data => {
  457. if (nonNull(data.content) && data.content.length > 0) {
  458. let emptyCode = ''
  459. let code = ''
  460. let areMaxLayer = 0
  461. let areMaxLen = 0
  462. let emptyAreMaxLayer = 0;
  463. for (let i = 0; i < data.content.length; i++) {
  464. const forklift = data.content[i];
  465. const num = isNull(forklift.maximumLayer) ? 0 : forklift.maximumLayer;
  466. // 空库区
  467. if (isNull(forklift.warehouseAreaList) || forklift.warehouseAreaList.length === 0) {
  468. if (emptyAreMaxLayer <= num) {
  469. emptyAreMaxLayer = num;
  470. emptyCode = forklift.code;
  471. }
  472. } else {
  473. // 非空库区
  474. if (areMaxLen <= forklift.warehouseAreaList.length && areMaxLayer <= num) {
  475. areMaxLen = forklift.warehouseAreaList.length;
  476. areMaxLayer = num;
  477. code = forklift.code;
  478. }
  479. }
  480. }
  481. fuc(emptyCode.length !== 0 ? emptyCode : code)
  482. } else {
  483. fuc(null)
  484. }
  485. })
  486. }
  487.  
  488. // 拿当前拣货单
  489. function getCurrentPickingOrder(fuc) {
  490. request('/yqn_wms/app/picking/v2/get_task', {}, data => {
  491. fuc(data.id)
  492. })
  493. }
  494.  
  495. // 拿移库单
  496. function getCurrentRelocation(real, fuc) {
  497. request('/yqn_wms/app/relocation/v2/' + (real ? 'get_immediately' : 'get_task'), {}, data => {
  498. fuc(data.id)
  499. })
  500. }
  501.  
  502. // 查询出库单对应拣货单
  503. function queryPickingOrder(fuc) {
  504. request('/yqn_wms/bg/picking/v2/list', {
  505. "outboundCode": new URLSearchParams(window.location.href.split('?')[1]).get('code'),
  506. }, data => {
  507. if (nonNull(data.content) && data.content.length > 0) {
  508. for (let i = 0; i < data.content.length; i++) {
  509. fuc(data.content[i])
  510. }
  511. } else {
  512. fuc(null)
  513. }
  514. })
  515. }
  516.  
  517. // 查询出库单对应装车单
  518. function queryLoadingOrder(fuc) {
  519. request('/yqn_wms/bg/loading_order/v2/list', {
  520. "outBoundOrderCode": new URLSearchParams(window.location.href.split('?')[1]).get('code')
  521. }, data => {
  522. if (nonNull(data.content) && data.content.length > 0) {
  523. for (let i = 0; i < data.content.length; i++) {
  524. fuc(data.content[i])
  525. }
  526. } else {
  527. fuc(null)
  528. }
  529. })
  530. }
  531.  
  532. // 发起请求
  533. function request(interfaceUrl, model, success) {
  534. const url = 'https://' + domain + 'gw-wms.yqn.com/api/40081/api/call' + interfaceUrl
  535. model.page = 1
  536. model.size = 100
  537. model.warehouseId = localStorage.getItem("warehouseId")
  538. jq.ajax({
  539. url: url,
  540. method: 'POST',
  541. xhrFields: {
  542. withCredentials: true
  543. },
  544. crossDomain: true,
  545. contentType: 'application/json',
  546. data: JSON.stringify({
  547. "header": {
  548. "xSourceAppId": "63008",
  549. "guid": "6f87e073-1da1-4017-b2de-c109abcd6d123",
  550. "lang": "zh",
  551. "timezone": "Asia/Shanghai"
  552. },
  553. "model": model
  554. }),
  555. success: function (response) {
  556. if (response.code === 200 && nonNull(response.data)) {
  557. success(response.data)
  558. }
  559. },
  560. error: function (xhr, status, error) {
  561. console.log('Request failed:', error);
  562. }
  563. });
  564. }
  565. })();