wmsHelper

wms操作助手

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

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