wmsHelper

wms操作助手

目前为 2023-12-04 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name wmsHelper
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.2
  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 unsafeWindow
  13. // @grant window.close
  14. // @grant window.focus
  15. // @run-at document-body
  16. // @noframes
  17. // @license AGPL License
  18. // ==/UserScript==
  19.  
  20. window.jq = $.noConflict(true);
  21.  
  22. (function (window) {
  23. window.pageHelper = {
  24. // 等待元素可见
  25. async waitElementVisible(visibleTag, index, fun) {
  26. let node = jq(visibleTag)
  27. if (node === null || node[index] === null || node[index] === undefined) {
  28. setTimeout(() => {
  29. pageHelper.waitElementVisible(visibleTag, index, fun)
  30. }, 500)
  31. } else {
  32. fun()
  33. }
  34. },
  35. sleep(duration) {
  36. return new Promise(resolve => {
  37. setTimeout(resolve, duration)
  38. })
  39. },
  40. showToast(msg, duration) {
  41. const oldNotify = document.querySelector(".custom-notify");
  42. if (oldNotify !== undefined && oldNotify !== null) {
  43. document.body.removeChild(oldNotify)
  44. }
  45. // 显示提示
  46. duration = isNaN(duration) ? 3000 : duration;
  47. const m = document.createElement('div');
  48. m.className = "custom-notify"
  49. m.innerHTML = msg;
  50. m.style.cssText = "display: flex;justify-content: center;align-items: center;width:60%; min-width:180px; " +
  51. "background:#000000; opacity:0.98; height:auto;min-height: 50px;font-size:25px; color:#fff; " +
  52. "line-height:30px; text-align:center; border-radius:4px; position:fixed; top:70%; left:20%; z-index:999999;";
  53. document.body.appendChild(m);
  54. setTimeout(function () {
  55. const d = 0.5;
  56. m.style.webkitTransition = '-webkit-transform ' + d + 's ease-in, opacity ' + d + 's ease-in';
  57. m.style.opacity = '0';
  58. setTimeout(function () {
  59. if (document.body.contains(m)) {
  60. document.body.removeChild(m)
  61. }
  62. }, d * 1000);
  63. }, duration);
  64. }
  65. }
  66. })(window);
  67.  
  68.  
  69. (function () {
  70. 'use strict';
  71. jq(document).ready(function () {
  72. // 顶部工具栏变化
  73. waitObserve("#app", () => {
  74. if (nonNull(document.querySelector(".forkButton"))) {
  75. return;
  76. }
  77. const toolbar = document.querySelector(".yqn-topbar-module");
  78. appendFlagNode(toolbar, "forkButton")
  79. const firstChildNode = toolbar.childNodes[0];
  80. toolbar.insertBefore(createTextButton("----万能叉车----", () => {
  81. getMaxForkliftCode(forkLift => {
  82. if (forkLift === '') {
  83. window.pageHelper.showToast("没找到叉车", 1000)
  84. } else {
  85. window.pageHelper.showToast(imageHtml(forkLift), 4000)
  86. }
  87. })
  88. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  89.  
  90. toolbar.insertBefore(createTextButton("----新容器----", () => {
  91. getFirstContainerNo(containerNo => {
  92. if (containerNo === '') {
  93. window.pageHelper.showToast("好像没容器了", 1000)
  94. } else {
  95. window.pageHelper.showToast(imageHtml(containerNo), 4000)
  96. }
  97. })
  98. }, "ant-btn ant-btn-link perf-tracked yqn-button download-record"), firstChildNode)
  99. })
  100. // 监听 content 页面变动
  101. waitObserve(".yqn-base-page", () => {
  102. const module = document.querySelector(".yqn-header")
  103. if (isNull(module)) {
  104. return;
  105. }
  106. // 拣货单详情页面
  107. if (module.textContent.indexOf("拣货单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  108. pickingOrder()
  109. }
  110. // 拣货单详情页面
  111. if (module.textContent.indexOf("移库单") >= 0 && module.textContent.indexOf("详情") >= 0) {
  112. relocationOrder()
  113. }
  114. })
  115. })
  116.  
  117. // 拣货单页面
  118. function pickingOrder() {
  119. tableExecute(line => {
  120. const sku = line.querySelectorAll("td")[1].innerText;
  121. const warehouseLocation1 = line.querySelectorAll("td")[4].innerText;
  122. line.querySelectorAll("td")[1].appendChild(createTextButton("=>生成", () => {
  123. window.pageHelper.showToast(imageHtml(sku, warehouseLocation1), 7000)
  124. }))
  125. })
  126. }
  127.  
  128. // 移库单页面
  129. function relocationOrder() {
  130. tableExecute(line => {
  131. const sku = line.querySelectorAll("td")[1].innerText;
  132. const warehouseLocation1 = line.querySelectorAll("td")[4].innerText;
  133. const warehouseLocation2 = line.querySelectorAll("td")[5].innerText;
  134. line.querySelectorAll("td")[1].appendChild(createTextButton("=>生成", () => {
  135. window.pageHelper.showToast(imageHtml(sku, warehouseLocation1, warehouseLocation2), 7000)
  136. }))
  137. })
  138. }
  139.  
  140. // 表格处理
  141. function tableExecute(lineFun) {
  142. waitObserve(".ant-tabs-content.ant-tabs-content-top .ant-table-container .ant-table-tbody", () => {
  143. if (nonNull(document.querySelector(".customer-button-sku"))) {
  144. return;
  145. }
  146. appendFlagNode(document.querySelector(".yqn-header"), "customer-button-sku")
  147. window.pageHelper.sleep(500)
  148. .then(() => {
  149. const tbody = document.querySelector(".ant-tabs-content.ant-tabs-content-top .ant-table-container .ant-table-tbody")
  150. const lines = tbody.querySelectorAll(".ant-table-row.ant-table-row-level-0");
  151. for (let i = 0; i < lines.length; i++) {
  152. lineFun(lines[i])
  153. }
  154. })
  155. })
  156. }
  157.  
  158. // 追加标记节点
  159. function appendFlagNode(node, flag) {
  160. const divFlag = document.createElement("div");
  161. node.appendChild(divFlag)
  162. divFlag.className = flag;
  163. divFlag.style.display = "none"
  164. }
  165.  
  166. function imageHtml() {
  167. let html = "<div style=\"display: flex;margin-top: 10px;margin-bottom: 10px\">";
  168. for (let i = 0; i < arguments.length; i++) {
  169. html += " <img src=\"https://wwsix.cn/endpoint/v1/qrcode/create?code=" + arguments[i] + "&width=200&height=200&desc=1\" alt='" + arguments[i] + "'/>";
  170. html += " <span style=\"width: 100px\"></span>";
  171. }
  172. html += "</div>";
  173. return html;
  174. }
  175.  
  176. function nonNull(o) {
  177. return o !== null && o !== undefined;
  178. }
  179.  
  180. function isNull(o) {
  181. return o === null || o === undefined;
  182. }
  183.  
  184. // 等待出现并监听变化
  185. function waitObserve(visibleTag, fun, attributes = true) {
  186. window.pageHelper.waitElementVisible(visibleTag, 0, () => {
  187. new MutationObserver(function (mutationsList) {
  188. fun()
  189. }).observe(document.querySelector(visibleTag), {
  190. attributes: attributes,
  191. childList: true,
  192. subtree: true,
  193. characterData: true
  194. })
  195. })
  196. }
  197.  
  198. // 创建文本按钮
  199. function createTextButton(name, listener, className = "ant-btn ant-btn-link perf-tracked yqn-button yqn-link-no-padding customer-button") {
  200. const button = document.createElement("button")
  201. button.type = "button"
  202. button.id = name
  203. button.className = className
  204. button.onclick = listener
  205. const span = document.createElement("span")
  206. span.textContent = name
  207. button.appendChild(span)
  208. return button;
  209. }
  210.  
  211. // 创建按钮
  212. function createButton(name, listener) {
  213. const button = document.createElement("button")
  214. button.type = "button"
  215. button.id = name
  216. button.className = "ant-btn ant-btn-default perf-tracked yqn-button"
  217. button.onclick = listener
  218. const a = document.createElement("a")
  219. a.textContent = name
  220. a.className = "render-button-text"
  221. button.appendChild(a)
  222. return button;
  223. }
  224.  
  225. const domain = window.location.href.indexOf("qa4") >= 0 ? 'qa4-' : window.location.href.indexOf("pr-") >= 0 ? 'pr-' : '';
  226.  
  227. // 拿一个新容器
  228. function getFirstContainerNo(fuc) {
  229. jq.ajax({
  230. url: 'https://' + domain + 'gw-wms.yqn.com/api/40081/api/call/yqn_wms/bg/container/v2/list',
  231. method: 'POST',
  232. xhrFields: {
  233. withCredentials: true
  234. },
  235. crossDomain: true,
  236. contentType: 'application/json',
  237. data: JSON.stringify({
  238. "header": {
  239. "xSourceAppId": "63008",
  240. "guid": "6f87e073-1da1-4017-b2de-c109abcd6d123",
  241. "lang": "zh",
  242. "timezone": "Asia/Shanghai"
  243. },
  244. "model": {
  245. "page": 1,
  246. "size": 20,
  247. "statusList": [
  248. 1
  249. ],
  250. "warehouseId": localStorage.getItem("warehouseId")
  251. }
  252. }),
  253. success: function (response) {
  254. if (response.code === 200 && nonNull(response.data) && nonNull(response.data.content)) {
  255. if (response.data.content.length > 0) {
  256. fuc(response.data.content[0].containerCode)
  257. } else {
  258. fuc('')
  259. }
  260. }
  261. },
  262. error: function (xhr, status, error) {
  263. console.log('Request failed:', error);
  264. }
  265. });
  266. }
  267.  
  268. // 拿一个全能叉车
  269. function getMaxForkliftCode(fuc) {
  270. jq.ajax({
  271. url: 'https://' + domain + 'gw-wms.yqn.com/api/40081/api/call/yqn_wms/bg/forklift/v2/list',
  272. method: 'POST',
  273. xhrFields: {
  274. withCredentials: true
  275. },
  276. crossDomain: true,
  277. contentType: 'application/json',
  278. data: JSON.stringify({
  279. "header": {
  280. "xSourceAppId": "63008",
  281. "guid": "6f87e073-1da1-4017-b2de-c109abcd6d123",
  282. "lang": "zh",
  283. "timezone": "Asia/Shanghai"
  284. },
  285. "model": {
  286. "page": 1,
  287. "size": 100,
  288. "warehouseId": localStorage.getItem("warehouseId")
  289. }
  290. }),
  291. success: function (response) {
  292. if (response.code === 200 && nonNull(response.data) && nonNull(response.data.content) && response.data.content.length > 0) {
  293. let emptyCode = ''
  294. let code = ''
  295. let areMaxLayer = 0
  296. let areMaxLen = 0
  297. let emptyAreMaxLayer = 0;
  298. for (let i = 0; i < response.data.content.length; i++) {
  299. const forklift = response.data.content[i];
  300. const num = isNull(forklift.maximumLayer) ? 0 : forklift.maximumLayer;
  301. // 空库区
  302. if (isNull(forklift.warehouseAreaList) || forklift.warehouseAreaList.length === 0) {
  303. if (emptyAreMaxLayer <= num) {
  304. emptyAreMaxLayer = num;
  305. emptyCode = forklift.code;
  306. }
  307. } else {
  308. // 非空库区
  309. if (areMaxLen <= forklift.warehouseAreaList.length && areMaxLayer <= num) {
  310. areMaxLen = forklift.warehouseAreaList.length;
  311. areMaxLayer = num;
  312. code = forklift.code;
  313. }
  314. }
  315. }
  316. fuc(emptyCode.length !== 0 ? emptyCode : code)
  317. }
  318. },
  319. error: function (xhr, status, error) {
  320. console.log('Request failed:', error);
  321. }
  322. });
  323. }
  324. })();