tools.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. export var camelToKebab = function (camelStr) {
  2. const isUpper = camelStr[0].charCodeAt(0) >= 65 && camelStr[0].charCodeAt(0) <= 90;
  3. const handleStr = camelStr.replace(/([A-Z])/g, "-$1").toLowerCase();
  4. let kebabStr = handleStr;
  5. if (isUpper) {
  6. kebabStr = handleStr.slice(1);
  7. }
  8. const newKebabArr = [];
  9. const kebabSplitArr = kebabStr.split("-");
  10. kebabSplitArr.forEach((item, index) => {
  11. if (item.length > 1) {
  12. newKebabArr.push(item);
  13. } else {
  14. let combineStr = "";
  15. const subKebabArr = kebabSplitArr.slice(index);
  16. for (let i = 0; i < subKebabArr.length; i++) {
  17. if (subKebabArr[i].length > 1)
  18. break;
  19. combineStr += subKebabArr[i];
  20. }
  21. newKebabArr.push(combineStr);
  22. kebabSplitArr.splice(index + 1, combineStr.length - 1);
  23. }
  24. });
  25. return newKebabArr.join("-");
  26. };
  27. export var isNum = function (num) {
  28. return typeof num === "number" && !isNaN(num);
  29. };
  30. export var toStyleStr = (styleObj, unit = "rpx") => {
  31. if (Array.isArray(styleObj)) {
  32. return styleObj.join(";");
  33. } else {
  34. if (typeof styleObj === "object" && styleObj !== null) {
  35. let style = [];
  36. for (const k in styleObj) {
  37. if (typeof k === "string") {
  38. let val = styleObj[k];
  39. if (val !== void 0) {
  40. if (typeof val === "number") {
  41. if (addUnitAttr.includes(k)) {
  42. val = val + unit;
  43. }
  44. }
  45. style.push(`${camelToKebab(k)}:${val}`);
  46. }
  47. }
  48. }
  49. return style.length > 0 ? style.join(";") + ";" : "";
  50. } else {
  51. return styleObj;
  52. }
  53. }
  54. };
  55. function isObject(value) {
  56. var type = typeof value;
  57. return value != null && (type == 'object' || type == 'function');
  58. }
  59. function toNumber(value) {
  60. if (typeof value == 'number') {
  61. return value;
  62. }
  63. if (isSymbol(value)) {
  64. return NAN;
  65. }
  66. if (isObject(value)) {
  67. var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
  68. value = isObject(other) ? (other + '') : other;
  69. }
  70. if (typeof value != 'string') {
  71. return value === 0 ? value : +value;
  72. }
  73. value = value.replace(reTrim, '');
  74. var isBinary = reIsBinary.test(value);
  75. return (isBinary || reIsOctal.test(value))
  76. ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
  77. : (reIsBadHex.test(value) ? NAN : +value);
  78. }
  79. export function debounce(func, wait, options) {
  80. var lastArgs,
  81. lastThis,
  82. maxWait,
  83. result,
  84. timerId,
  85. lastCallTime,
  86. lastInvokeTime = 0,
  87. leading = false,
  88. maxing = false,
  89. trailing = true;
  90. if (typeof func != 'function') {
  91. throw new TypeError(FUNC_ERROR_TEXT);
  92. }
  93. wait = toNumber(wait) || 0;
  94. if (isObject(options)) {
  95. leading = !!options.leading;
  96. maxing = 'maxWait' in options;
  97. maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
  98. trailing = 'trailing' in options ? !!options.trailing : trailing;
  99. }
  100. function invokeFunc(time) {
  101. var args = lastArgs,
  102. thisArg = lastThis;
  103. lastArgs = lastThis = undefined;
  104. lastInvokeTime = time;
  105. result = func.apply(thisArg, args);
  106. return result;
  107. }
  108. function leadingEdge(time) {
  109. // Reset any `maxWait` timer.
  110. lastInvokeTime = time;
  111. // Start the timer for the trailing edge.
  112. timerId = setTimeout(timerExpired, wait);
  113. // Invoke the leading edge.
  114. return leading ? invokeFunc(time) : result;
  115. }
  116. function remainingWait(time) {
  117. var timeSinceLastCall = time - lastCallTime,
  118. timeSinceLastInvoke = time - lastInvokeTime,
  119. timeWaiting = wait - timeSinceLastCall;
  120. return maxing
  121. ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
  122. : timeWaiting;
  123. }
  124. function shouldInvoke(time) {
  125. var timeSinceLastCall = time - lastCallTime,
  126. timeSinceLastInvoke = time - lastInvokeTime;
  127. // Either this is the first call, activity has stopped and we're at the
  128. // trailing edge, the system time has gone backwards and we're treating
  129. // it as the trailing edge, or we've hit the `maxWait` limit.
  130. return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
  131. (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
  132. }
  133. function timerExpired() {
  134. var time = now();
  135. if (shouldInvoke(time)) {
  136. return trailingEdge(time);
  137. }
  138. // Restart the timer.
  139. timerId = setTimeout(timerExpired, remainingWait(time));
  140. }
  141. function trailingEdge(time) {
  142. timerId = undefined;
  143. // Only invoke if we have `lastArgs` which means `func` has been
  144. // debounced at least once.
  145. if (trailing && lastArgs) {
  146. return invokeFunc(time);
  147. }
  148. lastArgs = lastThis = undefined;
  149. return result;
  150. }
  151. function cancel() {
  152. if (timerId !== undefined) {
  153. clearTimeout(timerId);
  154. }
  155. lastInvokeTime = 0;
  156. lastArgs = lastCallTime = lastThis = timerId = undefined;
  157. }
  158. function flush() {
  159. return timerId === undefined ? result : trailingEdge(now());
  160. }
  161. function debounced() {
  162. var time = now(),
  163. isInvoking = shouldInvoke(time);
  164. lastArgs = arguments;
  165. lastThis = this;
  166. lastCallTime = time;
  167. if (isInvoking) {
  168. if (timerId === undefined) {
  169. return leadingEdge(lastCallTime);
  170. }
  171. if (maxing) {
  172. // Handle invocations in a tight loop.
  173. timerId = setTimeout(timerExpired, wait);
  174. return invokeFunc(lastCallTime);
  175. }
  176. }
  177. if (timerId === undefined) {
  178. timerId = setTimeout(timerExpired, wait);
  179. }
  180. return result;
  181. }
  182. debounced.cancel = cancel;
  183. debounced.flush = flush;
  184. return debounced;
  185. }
  186. // 功能说明:统一处理后端可能返回的 {val}/{value} 结构,供组件内部字段适配复用 by xu 20260122
  187. export function normalizeMaybeVal(input) {
  188. if (input === undefined || input === null) return input;
  189. if (typeof input === "object") {
  190. if (Object.prototype.hasOwnProperty.call(input, "val")) return input.val;
  191. if (Object.prototype.hasOwnProperty.call(input, "value")) return input.value;
  192. }
  193. return input;
  194. }
  195. // 功能说明:缩略图 URL 构建(dlByHttp 直出图片),支持 thn 为字符串或 {val}/{value} by xu 20260122
  196. export function buildThumbUrl(thn) {
  197. const v = normalizeMaybeVal(thn);
  198. if (v === undefined || v === null || v === "") return "";
  199. return "/service?ssServ=dlByHttp&type=img&path=" + encodeURIComponent(String(v));
  200. }
  201. // 功能说明:统一按 fmt 动态格式化 {val, fmt}(支持任意组合 yyyy/MM/dd/HH/mm/ss),供卡片 tags/标题复用 by xu 20260122
  202. export function formatValByFmt(val, fmt) {
  203. try {
  204. if (!fmt || val === undefined || val === null || val === "") return val;
  205. const raw = typeof val === "string"
  206. ? val.replace(/[\u202f\u00a0]/g, " ").replace(/ /g, " ").trim()
  207. : val;
  208. const d = raw instanceof Date ? raw : new Date(raw);
  209. if (isNaN(d.getTime())) return val;
  210. const pad2 = (n) => String(n).padStart(2, "0");
  211. const tokens = {
  212. yyyy: String(d.getFullYear()),
  213. MM: pad2(d.getMonth() + 1),
  214. M: String(d.getMonth() + 1),
  215. dd: pad2(d.getDate()),
  216. d: String(d.getDate()),
  217. HH: pad2(d.getHours()),
  218. H: String(d.getHours()),
  219. mm: pad2(d.getMinutes()),
  220. m: String(d.getMinutes()),
  221. ss: pad2(d.getSeconds()),
  222. s: String(d.getSeconds()),
  223. };
  224. const f = String(fmt);
  225. if (!/(yyyy|MM|dd|HH|mm|ss|M|d|H|m|s)/.test(f)) return val;
  226. return f.replace(/yyyy|MM|dd|HH|mm|ss|M|d|H|m|s/g, (k) => tokens[k] ?? k);
  227. } catch (_) {
  228. return val;
  229. }
  230. }
  231. // 功能说明:解析 parm/param 参数(兼容 JSON 字符串、qs 字符串、对象) by xu 20260122
  232. export function parseParmLike(parmOrParam) {
  233. if (!parmOrParam) return {};
  234. if (typeof parmOrParam === "object") return parmOrParam;
  235. if (typeof parmOrParam !== "string") return {};
  236. const s = String(parmOrParam).trim();
  237. if (!s) return {};
  238. // JSON 格式
  239. if (s[0] === "{" || s[0] === "[") {
  240. try {
  241. const obj = JSON.parse(s);
  242. if (obj && typeof obj === "object") return obj;
  243. } catch (_) {}
  244. }
  245. // qs 格式(&a=b 或 a=b)
  246. return { __rawQs: s };
  247. }
  248. // 功能说明:追加查询参数到 URL(支持去重、数组值) by xu 20260122
  249. export function appendQueryParam(url, key, value) {
  250. if (!key) return url;
  251. if (value === undefined || value === null) return url;
  252. const s = String(value);
  253. if (s === "") return url;
  254. // 检查参数是否已存在
  255. const escapeRegExp = (str) => String(str || "").replace(/[.*+?^{}$()|[\]\\]/g, "\\$&");
  256. const hasParam = new RegExp("([?&])" + escapeRegExp(key) + "=").test(String(url || ""));
  257. if (hasParam) return url;
  258. const separator = url.indexOf("?") > -1 ? "&" : "?";
  259. return url + separator + encodeURIComponent(String(key)) + "=" + encodeURIComponent(s);
  260. }
  261. // 功能说明:从搜索字段列表和表单中提取参数(用于透传搜索条件到弹窗) by xu 20260122
  262. export function pickSearchParams(searchFieldList, form) {
  263. const params = {};
  264. if (!Array.isArray(searchFieldList) || !form) return params;
  265. searchFieldList.forEach((field) => {
  266. const key = field && field.name;
  267. if (!key) return;
  268. const value = form[key];
  269. if (value === undefined || value === null) return;
  270. if (Array.isArray(value)) {
  271. params[key] = value.filter((v) => v !== undefined && v !== null && v !== "");
  272. } else if (value !== "") {
  273. params[key] = value;
  274. }
  275. });
  276. return params;
  277. }
  278. // 功能说明:构建服务 URL(统一处理 ssToken/servName+dest+parm 三种模式) by xu 20260122
  279. export function buildServiceUrl(srv, options = {}) {
  280. if (!srv) return "";
  281. const { extraParams = {} } = options;
  282. let url = "";
  283. // 模式1:优先 ssToken
  284. const token = String(srv.ssToken || "").trim();
  285. if (token) {
  286. url = "/service?ssToken=" + encodeURIComponent(token);
  287. } else if (srv.servName) {
  288. // 模式2:servName + dest + parm
  289. url = "/service?ssServ=" + encodeURIComponent(String(srv.servName));
  290. if (srv.dest) url += "&ssDest=" + encodeURIComponent(String(srv.dest));
  291. // 解析 parm/param
  292. const parm = srv.parm !== undefined ? srv.parm : srv.param;
  293. const parsed = parseParmLike(parm);
  294. if (parsed.__rawQs) {
  295. // qs 格式直接拼接
  296. const qs = parsed.__rawQs;
  297. url += (qs.startsWith("&") ? "" : "&") + qs;
  298. } else {
  299. // 对象格式遍历拼接
  300. Object.entries(parsed).forEach(([k, v]) => {
  301. if (v === undefined || v === null) return;
  302. url += "&" + encodeURIComponent(k) + "=" + encodeURIComponent(String(v));
  303. });
  304. }
  305. } else if (srv.dest) {
  306. // 模式3:仅 dest(报表场景)
  307. url = "/service?ssDest=" + encodeURIComponent(String(srv.dest));
  308. const parm = srv.parm !== undefined ? srv.parm : srv.param;
  309. const parsed = parseParmLike(parm);
  310. if (parsed.__rawQs) {
  311. const qs = parsed.__rawQs;
  312. url += (qs.startsWith("&") ? "" : "&") + qs;
  313. } else {
  314. Object.entries(parsed).forEach(([k, v]) => {
  315. if (v === undefined || v === null) return;
  316. url += "&" + encodeURIComponent(k) + "=" + encodeURIComponent(String(v));
  317. });
  318. }
  319. }
  320. // 追加额外参数
  321. Object.entries(extraParams).forEach(([key, value]) => {
  322. if (Array.isArray(value)) {
  323. value.forEach((v) => {
  324. url = appendQueryParam(url, key, v);
  325. });
  326. } else {
  327. url = appendQueryParam(url, key, value);
  328. }
  329. });
  330. return url;
  331. }
  332. // 功能说明:打开服务弹窗(统一封装 wd.display.showComponent) by xu 20260122
  333. export function openServiceDialog(srv, options = {}) {
  334. if (!srv) return;
  335. const { extraParams = {}, onError = null } = options;
  336. const url = buildServiceUrl(srv, { extraParams });
  337. if (!url) return;
  338. try {
  339. window.wd.display.showComponent({
  340. show: ["wdDialog"],
  341. url,
  342. title: srv.title || srv.desc || "",
  343. width: srv.width,
  344. height: srv.height,
  345. minHeight: srv.minHeight,
  346. maxHeight: srv.maxHeight,
  347. showTitle: srv.showTitle,
  348. });
  349. } catch (error) {
  350. console.error("[openServiceDialog] failed:", error);
  351. if (onError) onError(error);
  352. }
  353. }
  354. // 功能说明:全局暴露工具函数,供 JSP 中的 Vue 实例使用 by xu 20260122
  355. if (typeof window !== "undefined") {
  356. window.ssTools = {
  357. normalizeMaybeVal,
  358. buildThumbUrl,
  359. formatValByFmt,
  360. parseParmLike,
  361. appendQueryParam,
  362. pickSearchParams,
  363. buildServiceUrl,
  364. openServiceDialog,
  365. };
  366. }