diff --git a/src/components/ModalLogin/login.tsx b/src/components/ModalLogin/login.tsx index ef5e822a2239e6ac4b2b6148797ad9976aaaf16d..6d946d0b90ec7c70599ce7d58bff0e49285032ce 100644 --- a/src/components/ModalLogin/login.tsx +++ b/src/components/ModalLogin/login.tsx @@ -185,7 +185,7 @@ const PassportLogin: React.FC = ({ reAuth, onFinished }) => { 获取验证码 - {!isNew &&
[请从以往短信验证码中获取该编号的验证码]
} + {!isNew &&
注意:今日已达接收验证码最大次数,请在手机短信中查找编号为({dynamicId})的验证码。
} ), }, diff --git a/src/executor/getAuthKey.tsx b/src/executor/getAuthKey.tsx index a8cbcf505ba7349ef7bf8e1ed43232e8e8efaade..8a4f891c86b66574ec6bb9eb724b15334139cb13 100644 --- a/src/executor/getAuthKey.tsx +++ b/src/executor/getAuthKey.tsx @@ -101,7 +101,7 @@ const AccountLogin: React.FC<{ entity: IStorage | IAgent; finished: () => void } {/* 非新用户提示 */} - {!isNew &&
[请从以往短信验证码中获取该编号的验证码]
} + {!isNew &&
注意:今日已达接收验证码最大次数,请在手机短信中查找编号为({dynamicId})的验证码。
} ), }, diff --git a/src/pages/Auth/content/login.tsx b/src/pages/Auth/content/login.tsx index 322785dfa773ef397b28dbf662ae7d02d48e1d2c..93e521ddec9d99b6e34427fcf12da6ed5847cb24 100644 --- a/src/pages/Auth/content/login.tsx +++ b/src/pages/Auth/content/login.tsx @@ -120,7 +120,7 @@ const PassportLogin: React.FC = ({ to, thirdUser, onSuccess }) => { 获取验证码 - {!isNew &&
[请从以往短信验证码中获取该编号的验证码]
} + {!isNew &&
注意:今日已达接收验证码最大次数,请在手机短信中查找编号为({dynamicId})的验证码。
} ), }, diff --git a/src/utils/array.ts b/src/utils/array.ts new file mode 100644 index 0000000000000000000000000000000000000000..6aeeec2a984be4fc6d0e31623d1fb4fa3a62a689 --- /dev/null +++ b/src/utils/array.ts @@ -0,0 +1,18 @@ +/** + * 对象数组中 根据key,进行除重 + * @param arr 数据源 + * @param key 过滤凭证 + */ +export function uniqueArrayBy(arr: T[], key: keyof T): T[] { + const uniqueValues = new Set(); + const result: T[] = []; + + for (const item of arr) { + const findValue = uniqueValues.has(item[key]); + if (!findValue) { + uniqueValues.add(item[key]); + result.push(item); + } + } + return result; +} \ No newline at end of file diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000000000000000000000000000000000000..46891403796c581387d1b90c5b4d880c65f6ca2e --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,225 @@ +/** + * Date 转化为指定格式的String
+ * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q)可以用 1-2 个占位符
+ * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) + * + * @param {string | number} date string支持形式:20160126 12:00:00,2016-01-26 12:00:00,2016.01.26 12:00:00,20160126,2016-01-26 12:00:00.0 + * @param {string} fmt + * @returns {string} + * @example + * + * formatDate(Date.now(), 'yyyy-MM-dd hh:mm:ss.S'); + * // => 2006-07-02 08:09:04.423 + * + * formatDate(Date.now(), 'yyyy-MM-dd E HH:mm:ss'); + * // => 2009-03-10 二 20:09:04 + * + * formatDate(Date.now(), 'yyyy-MM-dd EE hh:mm:ss'); + * // => 2009-03-10 周二 08:09:04 + * + * formatDate(Date.now(), 'yyyy-MM-dd EEE hh:mm:ss'); + * // => 2009-03-10 星期二 08:09:04 + * + * formatDate(Date.now(), 'yyyy-M-d h:m:s.S') + * // => 2006-7-2 8:9:4.18 + */ +export const formatDate = (date?: any, fmt?: string) => { + if (date === undefined || date === '') return ''; + try { + if (date === void 0 || date == null) date = new Date(); + if (fmt === void 0 || fmt == null) fmt = 'yyyy-MM-dd HH:mm:ss'; + + if (typeof date === 'string') { + date = new Date(date); + } else if (typeof date === 'number') { + date = new Date(date); + } + let o: any = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, + 'H+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds(), + }; + let week: any = { + '0': '\u65e5', + '1': '\u4e00', + '2': '\u4e8c', + '3': '\u4e09', + '4': '\u56db', + '5': '\u4e94', + '6': '\u516d', + }; + + if (/(y+)/.test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + (date.getFullYear() + '').substr(4 - RegExp.$1.length), + ); + } + + if (/(E+)/.test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + (RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') : '') + + week[date.getDay() + ''], + ); + } + + for (let k in o) { + if (new RegExp('(' + k + ')').test(fmt)) { + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length), + ); + } + } + + return fmt; + } catch (error) { + console.error('格式化时间出错:', error); + return ''; + } +}; + +export function getWeek(date: Date): Map { + var one_day = 86400000; + var day = date.getDay(); + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + date.setMilliseconds(0); + var week_start_time = date.getTime() - (day - 1) * one_day; + var week_end_time = date.getTime() + (7 - day) * one_day; + var last = week_start_time - 2 * 24 * 60 * 60 * 1000; + var next = week_end_time + 24 * 60 * 60 * 1000; + var tomorrow = date.getTime() + 86400000; + var month1: string | number = new Date(week_start_time).getMonth() + 1; + var month2: string | number = new Date(week_end_time).getMonth() + 1; + var day1: string | number = new Date(week_start_time).getDate(); + var day2: string | number = new Date(week_end_time).getDate(); + if (month1 < 10) { + month1 = '0' + month1; + } + if (month2 < 10) { + month2 = '0' + month2; + } + if (day1 < 10) { + day1 = '0' + day1; + } + if (day2 < 10) { + day2 = '0' + day2; + } + var time1 = month1 + '.' + day1; + var time2 = month2 + '.' + day2; + var map = new Map(); + map.set('stime', week_start_time); + map.set('etime', week_end_time); + map.set('stext', time1); + map.set('etext', time2); + map.set('last', last); + map.set('next', next); + map.set('text', time1 + '-' + time2); + map.set('tomorrow', tomorrow); + return map; +} + +export function formatTimeByPattern(val: any) { + if (val.length > 19) { + val = val.substring(0, 19); + } + + let pattern = /-|\./g; + let year; + let month; + let day; + let reset; + + if (pattern.test(val)) { + return val.replace(pattern, '/'); + } else { + if (!~val.indexOf('-')) { + year = val.slice(0, 4); + month = val.slice(4, 6); + day = val.slice(6, 8); + reset = val.slice(8); + return year + '/' + month + '/' + day + reset; + } + } +} + +/** + * 将时间转化为几天前,几小时前,几分钟前 + * + * @param {number} ms + * @returns {*} + * @example + * + * formatTimeAgo(1505232000000); + * // => 1天前 + */ +export function formatTimeAgo(ms: any) { + ms = parseInt(ms); + + let timeNow = Date.now(); + let diff = (timeNow - ms) / 1000; + let date = new Date(); + let days = Math.floor(diff / (24 * 60 * 60)); + let hours = Math.floor(diff / (60 * 60)); + let minutes = Math.floor(diff / 60); + let second = Math.floor(diff); + + if (days > 0 && days < 2) { + return days + '天前'; + } else if (days <= 0 && hours > 0) { + return hours + '小时前'; + } else if (hours <= 0 && minutes > 0) { + return minutes + '分钟前'; + } else if (minutes <= 0 && second >= 0) { + return '刚刚'; + } else { + date.setTime(ms); + + return ( + date.getFullYear() + + '-' + + f(date.getMonth() + 1) + + '-' + + f(date.getDate()) + + ' ' + + f(date.getHours()) + + ':' + + f(date.getMinutes()) + ); + } + + function f(n: any) { + return n < 10 ? '0' + n : n; + } +} + +export const parseChineseTimeToStandard = (timeStr: string): string => { + const timeRegex = /(\d+)年(\d+)月(\d+)日(\d+)时(\d+)分/; + const match = timeStr.match(timeRegex); + + if (!match) { + return timeStr; + } + + const [, year, month, day, hour, minute] = match; + + const date = new Date( + parseInt(year), + parseInt(month) - 1, + parseInt(day), + parseInt(hour), + parseInt(minute), + 0, + 0, + ); + + return formatDate(date, 'yyyy-MM-dd HH:mm:ss') + '.000'; +}; \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 8b423824fc9eaf59d704aad507a7f6a6e6f56fcd..c2a5abd2a83c33ec0c4eacdc9cf4167faa35fe6b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,629 +1,8 @@ -// import { round } from '@/ts/scripting/core/functions/primitive'; -import Decimal from 'decimal.js'; -/* eslint-disable no-useless-escape */ -/* eslint-disable no-prototype-builtins */ -/* eslint-disable no-unused-vars */ -/* --------------------------------------------公共方法--------------------------------- */ -// 获取URL参数 -const getQueryString = (name: string) => { - const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); - const r = window.location.search.substr(1).match(reg); - if (r !== null) return decodeURI(r[2]); - return null; -}; - -/** - * Date 转化为指定格式的String
- * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q)可以用 1-2 个占位符
- * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) - * - * @param {string | number} date string支持形式:20160126 12:00:00,2016-01-26 12:00:00,2016.01.26 12:00:00,20160126,2016-01-26 12:00:00.0 - * @param {string} fmt - * @returns {string} - * @example - * - * formatDate(Date.now(), 'yyyy-MM-dd hh:mm:ss.S'); - * // => 2006-07-02 08:09:04.423 - * - * formatDate(Date.now(), 'yyyy-MM-dd E HH:mm:ss'); - * // => 2009-03-10 二 20:09:04 - * - * formatDate(Date.now(), 'yyyy-MM-dd EE hh:mm:ss'); - * // => 2009-03-10 周二 08:09:04 - * - * formatDate(Date.now(), 'yyyy-MM-dd EEE hh:mm:ss'); - * // => 2009-03-10 星期二 08:09:04 - * - * formatDate(Date.now(), 'yyyy-M-d h:m:s.S') - * // => 2006-7-2 8:9:4.18 - */ -const formatDate = (date?: any, fmt?: string) => { - if (date === undefined || date === '') return ''; - try { - if (date === void 0 || date == null) date = new Date(); - if (fmt === void 0 || fmt == null) fmt = 'yyyy-MM-dd HH:mm:ss'; - - if (typeof date === 'string') { - date = new Date(date); - } else if (typeof date === 'number') { - date = new Date(date); - } - let o: any = { - 'M+': date.getMonth() + 1, // 月份 - 'd+': date.getDate(), // 日 - 'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, // 小时 - 'H+': date.getHours(), // 小时 - 'm+': date.getMinutes(), // 分 - 's+': date.getSeconds(), // 秒 - 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度 - S: date.getMilliseconds(), // 毫秒 - }; - let week: any = { - '0': '\u65e5', - '1': '\u4e00', - '2': '\u4e8c', - '3': '\u4e09', - '4': '\u56db', - '5': '\u4e94', - '6': '\u516d', - }; - - if (/(y+)/.test(fmt)) { - fmt = fmt.replace( - RegExp.$1, - (date.getFullYear() + '').substr(4 - RegExp.$1.length), - ); - } - - if (/(E+)/.test(fmt)) { - fmt = fmt.replace( - RegExp.$1, - (RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') : '') + - week[date.getDay() + ''], - ); - } - - for (let k in o) { - if (new RegExp('(' + k + ')').test(fmt)) { - fmt = fmt.replace( - RegExp.$1, - RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length), - ); - } - } - - return fmt; - } catch (error) { - console.error('格式化时间出错:', error); - return ''; - } -}; -//判断是否是JSON字符串且为数组或者对象 -const isJSONString = (str: string) => { - try { - const parsed = JSON.parse(str); - return Array.isArray(parsed) || (typeof parsed === 'object' && parsed !== null); - } catch (e) { - return false; - } -}; -// 添加时间转换函数匹配时间段 -const parseChineseTimeToStandard = (timeStr: string): string => { - // 解析中文时间格式:2025年8月1日0时0分 - const timeRegex = /(\d+)年(\d+)月(\d+)日(\d+)时(\d+)分/; - const match = timeStr.match(timeRegex); - - if (!match) { - return timeStr; // 如果格式不匹配,返回原字符串 - } - - const [, year, month, day, hour, minute] = match; - - // 创建Date对象 - const date = new Date( - parseInt(year), - parseInt(month) - 1, // 月份从0开始 - parseInt(day), - parseInt(hour), - parseInt(minute), - 0, // 秒 - 0, // 毫秒 - ); - - // 使用formatDate函数转换为目标格式 - return formatDate(date, 'yyyy-MM-dd HH:mm:ss') + '.000'; -}; -export function getWeek(date: Date): Map { - var one_day = 86400000; - var day = date.getDay(); - // 设置时间为当天的0点 - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - 1; - date.setMilliseconds(0); - var week_start_time = date.getTime() - (day - 1) * one_day; - var week_end_time = date.getTime() + (7 - day) * one_day; - var last = week_start_time - 2 * 24 * 60 * 60 * 1000; - var next = week_end_time + 24 * 60 * 60 * 1000; - var tomorrow = date.getTime() + 86400000; - var month1: string | number = new Date(week_start_time).getMonth() + 1; - var month2: string | number = new Date(week_end_time).getMonth() + 1; - var day1: string | number = new Date(week_start_time).getDate(); - var day2: string | number = new Date(week_end_time).getDate(); - if (month1 < 10) { - month1 = '0' + month1; - } - if (month2 < 10) { - month2 = '0' + month2; - } - if (day1 < 10) { - day1 = '0' + day1; - } - if (day2 < 10) { - day2 = '0' + day2; - } - var time1 = month1 + '.' + day1; - var time2 = month2 + '.' + day2; - var map = new Map(); - map.set('stime', week_start_time); // 当前周周一零点的毫秒数 - map.set('etime', week_end_time); // 当前周周日零点的毫秒数 - map.set('stext', time1); // 当前周 周一的日期 mm.dd 如 03.14 - map.set('etext', time2); // 当前周周一零点的毫秒数 - map.set('last', last); // 上一周 周六零点的毫秒数 - map.set('next', next); // 下一周 周一零点的毫秒数 - map.set('text', time1 + '-' + time2); // 上一周 周六零点的毫秒数 - map.set('tomorrow', tomorrow); // 明天 零点的毫秒数 - return map; -} - -function formatTimeByPattern(val: any) { - // 2016-05-23 13:58:02.0 - if (val.length > 19) { - val = val.substring(0, 19); - } - - let pattern = /-|\./g; - let year; - let month; - let day; - let reset; - - if (pattern.test(val)) { - return val.replace(pattern, '/'); - } else { - // 若无’-‘,则不处理 - if (!~val.indexOf('-')) { - year = val.slice(0, 4); - month = val.slice(4, 6); - day = val.slice(6, 8); - reset = val.slice(8); - return year + '/' + month + '/' + day + reset; - } - } -} - -/** - * 将时间转化为几天前,几小时前,几分钟前 - * - * @param {number} ms - * @returns {*} - * @example - * - * formatTimeAgo(1505232000000); - * // => 1天前 - */ -function formatTimeAgo(ms: any) { - ms = parseInt(ms); - - let timeNow = Date.now(); - let diff = (timeNow - ms) / 1000; - let date = new Date(); - // 向下取整更精确些 - let days = Math.floor(diff / (24 * 60 * 60)); - let hours = Math.floor(diff / (60 * 60)); - let minutes = Math.floor(diff / 60); - let second = Math.floor(diff); - - if (days > 0 && days < 2) { - return days + '天前'; - } else if (days <= 0 && hours > 0) { - return hours + '小时前'; - } else if (hours <= 0 && minutes > 0) { - return minutes + '分钟前'; - } else if (minutes <= 0 && second >= 0) { - return '刚刚'; - } else { - date.setTime(ms); - - return ( - date.getFullYear() + - '-' + - f(date.getMonth() + 1) + - '-' + - f(date.getDate()) + - ' ' + - f(date.getHours()) + - ':' + - f(date.getMinutes()) - ); - } - - function f(n: any) { - return n < 10 ? '0' + n : n; - } -} - -/** - * 数字格式化 - * @param number 要格式化的数字或者字符串形式的数字 - * @param decimalPlaces 保留小数位,默认2,传入`null`不处理 - * @param showThousandSeparator 是否显示千分位分隔符 - * @param asPercentage 是否作为百分比输出(乘以100并增加百分号) - * @returns 格式化的数字 - */ -export function formatNumber( - number: string | number | null | undefined, - decimalPlaces: number | null = 2, - showThousandSeparator = false, - asPercentage = false, -) { - if (number === null || number === undefined || number === '') { - return ''; - } - - number = Number(number); - if (isNaN(number)) { - return 'NaN'; - } - - if (asPercentage) { - number *= 100; - } - - let formatted = String(number); - - if (typeof decimalPlaces === 'number') { - let t = new Decimal(number); - formatted = t.toFixed(decimalPlaces, Decimal.ROUND_HALF_UP); - } - - if (showThousandSeparator) { - if (formatted.includes('.')) { - formatted = formatted.replace(/\B(?=(\d{3})+(?=\.))/g, ','); - } else { - formatted = formatted.replace(/\B(?=(\d{3})+(?!\d))/g, ','); - } - } - - if (asPercentage) { - formatted += '%'; - } - - return formatted; -} - -/** - * 检查是否是emoji表情 - * @param {*} value 正则校验变量 - * @return {boolean} 正则校验结果 true: 是emoji表情 false: 不是emoji表情 - */ -function isEmoji(value: any) { - let arr = ['\ud83c[\udf00-\udfff]', '\ud83d[\udc00-\ude4f]', '\ud83d[\ude80-\udeff]']; - - return new RegExp(arr.join('|'), 'g').test(value); -} - -/** - * 检查是否为特殊字符 - * @param {string} value 正则校验的变量 - * @returns {boolean} 正则校验结果 true: 是特殊字符 false: 不是特殊字符 - */ -function isSpecialChar(value: any) { - let regEn = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]\s]/im; - let regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]\s]/im; - - return regEn.test(value) || regCn.test(value); -} - -/** - * 过滤对象中为空的属性 - * - * @param obj - * @returns {*} - * @example - * - * filterEmptyPropObj({name: 'foo', sex: ''}) - * // => {name: 'foo'} - */ -function filterEmptyPropObj(obj: any) { - if (!(typeof obj == 'object')) { - return; - } - - for (let key in obj) { - if ( - obj.hasOwnProperty(key) && - (obj[key] == null || obj[key] == undefined || obj[key] === '') - ) { - delete obj[key]; - } - } - return obj; -} - -/** - * 递归访问整个树 - */ -function visitTree( - tree: any[], - cb: (item: any, parent: any, deep: number) => void, - options?: { - /** 子项名,默认:`'children'` */ - childrenMapName?: string; - }, -) { - options = { - childrenMapName: 'children', - ...options, - }; - const inFn = (data: any[], parent: any, deep: number) => { - for (const item of data) { - cb(item, parent, deep); - const childrenVal = item[options!.childrenMapName!]; - if (childrenVal && childrenVal.length > 0) { - inFn(childrenVal, item, deep + 1); - } - } - }; - inFn(tree, null, 1); -} - -/** - * 处理表格组件,添加字段展示宽度,文本溢出省略显示 - * @param propsColumns 传入的表头 - * @returns - */ -function getScrollX(propsColumns: { [key: string]: any }[]) { - let scrollx = 0 as number; - const columnsRes = propsColumns.map((item) => { - const { dataIndex, title, width, key, fixed } = item; - const type = Object.prototype.toString.call(title); - let _width = 0 as number; - if (!width) { - switch (type) { - case '[object Object]': - _width = title.props.title.length * 14 + 20; - break; - case '[object String]': - _width = title.length * 14 + 20; - break; - default: - _width = width; - } - } else { - _width = width; - } - scrollx += _width; - return { - ...item, - key: key || dataIndex, - width: _width, - ellipsis: !fixed, - }; - }); - return { columnsRes, scrollx }; -} - -function getJsonText(fileUrl: string): Promise { - return new Promise((ok, error) => { - const xhr = new XMLHttpRequest(); - xhr.open('GET', fileUrl); - xhr.onreadystatechange = function () { - if (xhr.readyState === XMLHttpRequest.DONE) { - if (xhr.status === 200) { - // 获取响应数据的原始文本内容 - const rawText = xhr.responseText; - ok(rawText); - } else { - error('请求失败'); - } - } - }; - xhr.send(); - }); -} - -const ellipsisText = (text: string, length: number) => { - if (text.length > length) { - return text.substring(0, length) + '...'; - } - return text; -}; - -/** - * 根据传入keys顺序,对传入obj对象键值对排序 - * @param obj - * @param sortedKeys - */ -function sortObjByKeys(obj: T, sortedKeys: string[]): T { - const sortedObj: Partial = {}; - sortedKeys.forEach((key) => { - if (obj?.hasOwnProperty(key)) { - sortedObj[key as keyof T] = obj[key as keyof T]; - } - }); - // 将原对象的其他键值对复制到排序后的对象中 - for (const key in obj) { - if (!sortedObj.hasOwnProperty(key)) sortedObj[key] = obj[key]; - } - return sortedObj as T; -} - -/** - * 赋给新对象中没有的老对象的值 - * @param old - */ -function assignment(oldObj: { [key: string]: any }, newObj: { [key: string]: any }) { - Object.keys(oldObj).forEach((key) => { - if (!(key in newObj)) { - newObj[key] = oldObj[key]; - } - }); -} - -/** - * 对象数组中 根据key,进行除重 - * @param arr 数据源 - * @param key 过滤凭证 - */ -function uniqueArrayBy(arr: T[], key: keyof T): T[] { - const uniqueValues = new Set(); - const result: T[] = []; - - for (const item of arr) { - const findValue = uniqueValues.has(item[key]); - if (!findValue) { - uniqueValues.add(item[key]); - result.push(item); - } - } - return result; -} - -function isLoadOptions(obj: any): { - success: boolean; - msg: string; -} { - if (typeof obj !== 'object' || obj === null) { - console.log('不是对象', obj); - return { - success: false, - msg: '不是一个对象或空', - }; - } - const allowedProperties = [ - 'belongId', - 'collName', - 'filter', - 'group', - 'options', - 'userData', - 'skip', - 'take', - 'formId', - 'requireTotalCount', - 'isCountQuery', - 'sort', - 'extraReations', - 'isExporting', - 'clusterId', - ]; - - for (const key in obj) { - if (!allowedProperties.includes(key)) { - return { - success: false, - msg: `查询语句不符合规范。属性${key}是非法的`, - }; - } - } - return { - success: true, - msg: '', - }; -} - -interface SortOptions { - selector: string; - desc: boolean; -} -interface MongoSortQuery { - [key: string]: 1 | -1; -} - -// 转换排序条件 -function convertToMongoSort(sortConditions: SortOptions[] | undefined): MongoSortQuery { - if (!Array.isArray(sortConditions)) { - throw new Error('排序条件必须是数组'); - } - const sortQuery: MongoSortQuery = {}; - for (const condition of sortConditions) { - const { selector, desc } = condition; - if (typeof selector !== 'string' || selector.trim() === '') { - throw new Error('排序字段名 (selector) 必须是非空字符串'); - } - sortQuery[selector] = desc ? -1 : 1; - } - - return sortQuery; -} - -interface DevExtremeSummaryCondition { - selector: string; - summaryType: '_sum_' | '_avg_' | '_min_' | '_max_' | '_count_'; // 支持常见的汇总类型 -} -interface MongoSummaryQuery { - [key: string]: { - [key: string]: string; - }; -} - -// 转换汇总条件 -function convertToMongoSummary( - summaryConditions: DevExtremeSummaryCondition[], -): MongoSummaryQuery { - if (!Array.isArray(summaryConditions)) { - throw new Error('聚合条件必须是数组'); - } - const summaryQuery: MongoSummaryQuery = {}; - // 遍历聚合条件数组 - for (const condition of summaryConditions) { - const { selector, summaryType } = condition; - - // 检查 selector 是否有效 - if (typeof selector !== 'string' || selector.trim() === '') { - throw new Error('字段名 (selector) 必须是非空字符串'); - } - if ( - typeof summaryType !== 'string' || - !['sum', 'avg', 'min', 'max', 'count'].includes(summaryType) - ) { - throw new Error(`不支持的汇总类型: ${summaryType}`); - } - const mongoOperator = `_${summaryType}_`; - summaryQuery[selector] = { - [mongoOperator]: `$${selector}`, - }; - } - - return summaryQuery; -} - -function vaildAccount(account: string): boolean { - var isEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(account); - var isPhone = /(^1[3|4|5|6|7|8|9]\d{9}$)|(^09\d{8}$)/.test(account); - return isEmail || isPhone; -} - -export { - assignment, - ellipsisText, - filterEmptyPropObj, - formatDate, - formatTimeAgo, - getJsonText, - getQueryString, - getScrollX, - isEmoji, - isSpecialChar, - sortObjByKeys, - uniqueArrayBy, - visitTree, - isLoadOptions, - convertToMongoSort, - convertToMongoSummary, - parseChineseTimeToStandard, - isJSONString, - vaildAccount, -}; +export { formatDate, getWeek, formatTimeByPattern, formatTimeAgo, parseChineseTimeToStandard } from './date'; +export { formatNumber } from './number'; +export { uniqueArrayBy } from './array'; +export { filterEmptyPropObj, sortObjByKeys, assignment, isLoadOptions, convertToMongoSort, convertToMongoSummary } from './object'; +export { isEmoji, isSpecialChar, ellipsisText, isJSONString } from './string'; +export { vaildAccount } from './validation'; +export { visitTree } from './tree'; +export { getQueryString, getScrollX, getJsonText } from './other'; \ No newline at end of file diff --git a/src/utils/number.ts b/src/utils/number.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd37642b888d21bee36cb1ee5c52331a3edf68ba --- /dev/null +++ b/src/utils/number.ts @@ -0,0 +1,50 @@ +import Decimal from 'decimal.js'; + +/** + * 数字格式化 + * @param number 要格式化的数字或者字符串形式的数字 + * @param decimalPlaces 保留小数位,默认2,传入`null`不处理 + * @param showThousandSeparator 是否显示千分位分隔符 + * @param asPercentage 是否作为百分比输出(乘以100并增加百分号) + * @returns 格式化的数字 + */ +export function formatNumber( + number: string | number | null | undefined, + decimalPlaces: number | null = 2, + showThousandSeparator = false, + asPercentage = false, +) { + if (number === null || number === undefined || number === '') { + return ''; + } + + number = Number(number); + if (isNaN(number)) { + return 'NaN'; + } + + if (asPercentage) { + number *= 100; + } + + let formatted = String(number); + + if (typeof decimalPlaces === 'number') { + let t = new Decimal(number); + formatted = t.toFixed(decimalPlaces, Decimal.ROUND_HALF_UP); + } + + if (showThousandSeparator) { + if (formatted.includes('.')) { + formatted = formatted.replace(/\B(?=(\d{3})+(?=\.))/g, ','); + } else { + formatted = formatted.replace(/\B(?=(\d{3})+(?!\d))/g, ','); + } + } + + if (asPercentage) { + formatted += '%'; + } + + return formatted; +} \ No newline at end of file diff --git a/src/utils/object.ts b/src/utils/object.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b3fde370b0c7b4455ae1b0e26b914b195e8edd6 --- /dev/null +++ b/src/utils/object.ts @@ -0,0 +1,160 @@ +/** + * 过滤对象中为空的属性 + * + * @param obj + * @returns {*} + * @example + * + * filterEmptyPropObj({name: 'foo', sex: ''}) + * // => {name: 'foo'} + */ +export function filterEmptyPropObj(obj: any) { + if (!(typeof obj == 'object')) { + return; + } + + for (let key in obj) { + if ( + obj.hasOwnProperty(key) && + (obj[key] == null || obj[key] == undefined || obj[key] === '') + ) { + delete obj[key]; + } + } + return obj; +} + +/** + * 根据传入keys顺序,对传入obj对象键值对排序 + * @param obj + * @param sortedKeys + */ +export function sortObjByKeys(obj: T, sortedKeys: string[]): T { + const sortedObj: Partial = {}; + sortedKeys.forEach((key) => { + if (obj?.hasOwnProperty(key)) { + sortedObj[key as keyof T] = obj[key as keyof T]; + } + }); + for (const key in obj) { + if (!sortedObj.hasOwnProperty(key)) sortedObj[key] = obj[key]; + } + return sortedObj as T; +} + +/** + * 赋给新对象中没有的老对象的值 + * @param oldObj + */ +export function assignment(oldObj: { [key: string]: any }, newObj: { [key: string]: any }) { + Object.keys(oldObj).forEach((key) => { + if (!(key in newObj)) { + newObj[key] = oldObj[key]; + } + }); +} + +export function isLoadOptions(obj: any): { + success: boolean; + msg: string; +} { + if (typeof obj !== 'object' || obj === null) { + console.log('不是对象', obj); + return { + success: false, + msg: '不是一个对象或空', + }; + } + const allowedProperties = [ + 'belongId', + 'collName', + 'filter', + 'group', + 'options', + 'userData', + 'skip', + 'take', + 'formId', + 'requireTotalCount', + 'isCountQuery', + 'sort', + 'extraReations', + 'isExporting', + 'clusterId', + ]; + + for (const key in obj) { + if (!allowedProperties.includes(key)) { + return { + success: false, + msg: `查询语句不符合规范。属性${key}是非法的`, + }; + } + } + return { + success: true, + msg: '', + }; +} + +interface SortOptions { + selector: string; + desc: boolean; +} +interface MongoSortQuery { + [key: string]: 1 | -1; +} + +export function convertToMongoSort(sortConditions: SortOptions[] | undefined): MongoSortQuery { + if (!Array.isArray(sortConditions)) { + throw new Error('排序条件必须是数组'); + } + const sortQuery: MongoSortQuery = {}; + for (const condition of sortConditions) { + const { selector, desc } = condition; + if (typeof selector !== 'string' || selector.trim() === '') { + throw new Error('排序字段名 (selector) 必须是非空字符串'); + } + sortQuery[selector] = desc ? -1 : 1; + } + + return sortQuery; +} + +interface DevExtremeSummaryCondition { + selector: string; + summaryType: '_sum_' | '_avg_' | '_min_' | '_max_' | '_count_'; +} +interface MongoSummaryQuery { + [key: string]: { + [key: string]: string; + }; +} + +export function convertToMongoSummary( + summaryConditions: DevExtremeSummaryCondition[], +): MongoSummaryQuery { + if (!Array.isArray(summaryConditions)) { + throw new Error('聚合条件必须是数组'); + } + const summaryQuery: MongoSummaryQuery = {}; + for (const condition of summaryConditions) { + const { selector, summaryType } = condition; + + if (typeof selector !== 'string' || selector.trim() === '') { + throw new Error('字段名 (selector) 必须是非空字符串'); + } + if ( + typeof summaryType !== 'string' || + !['sum', 'avg', 'min', 'max', 'count'].includes(summaryType) + ) { + throw new Error(`不支持的汇总类型: ${summaryType}`); + } + const mongoOperator = `_${summaryType}_`; + summaryQuery[selector] = { + [mongoOperator]: `$${selector}`, + }; + } + + return summaryQuery; +} \ No newline at end of file diff --git a/src/utils/other.ts b/src/utils/other.ts new file mode 100644 index 0000000000000000000000000000000000000000..7cc9e1a24a7cec0d7a1d52f04b8d2d6240063d56 --- /dev/null +++ b/src/utils/other.ts @@ -0,0 +1,57 @@ +const getQueryString = (name: string) => { + const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); + const r = window.location.search.substr(1).match(reg); + if (r !== null) return decodeURI(r[2]); + return null; +}; + +function getScrollX(propsColumns: { [key: string]: any }[]) { + let scrollx = 0 as number; + const columnsRes = propsColumns.map((item) => { + const { dataIndex, title, width, key, fixed } = item; + const type = Object.prototype.toString.call(title); + let _width = 0 as number; + if (!width) { + switch (type) { + case '[object Object]': + _width = title.props.title.length * 14 + 20; + break; + case '[object String]': + _width = title.length * 14 + 20; + break; + default: + _width = width; + } + } else { + _width = width; + } + scrollx += _width; + return { + ...item, + key: key || dataIndex, + width: _width, + ellipsis: !fixed, + }; + }); + return { columnsRes, scrollx }; +} + +function getJsonText(fileUrl: string): Promise { + return new Promise((ok, error) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', fileUrl); + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + const rawText = xhr.responseText; + ok(rawText); + } else { + error('请求失败'); + } + } + }; + xhr.send(); + }); +} + +export { getQueryString, getScrollX, getJsonText }; \ No newline at end of file diff --git a/src/utils/string.ts b/src/utils/string.ts new file mode 100644 index 0000000000000000000000000000000000000000..807723e760a4d3f1f39ed075ef68373d05dcf281 --- /dev/null +++ b/src/utils/string.ts @@ -0,0 +1,38 @@ +/** + * 检查是否是emoji表情 + * @param {*} value 正则校验变量 + * @return {boolean} 正则校验结果 true: 是emoji表情 false: 不是emoji表情 + */ +export function isEmoji(value: any) { + let arr = ['\ud83c[\udf00-\udfff]', '\ud83d[\udc00-\ude4f]', '\ud83d[\ude80-\udeff]']; + + return new RegExp(arr.join('|'), 'g').test(value); +} + +/** + * 检查是否为特殊字符 + * @param {string} value 正则校验的变量 + * @returns {boolean} 正则校验结果 true: 是特殊字符 false: 不是特殊字符 + */ +export function isSpecialChar(value: any) { + let regEn = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]\s]/im; + let regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]\s]/im; + + return regEn.test(value) || regCn.test(value); +} + +export const ellipsisText = (text: string, length: number) => { + if (text.length > length) { + return text.substring(0, length) + '...'; + } + return text; +}; + +export const isJSONString = (str: string) => { + try { + const parsed = JSON.parse(str); + return Array.isArray(parsed) || (typeof parsed === 'object' && parsed !== null); + } catch (e) { + return false; + } +}; \ No newline at end of file diff --git a/src/utils/tree.ts b/src/utils/tree.ts new file mode 100644 index 0000000000000000000000000000000000000000..a908f5fcb8b839c7298e70fba1968f2ca49bf9dc --- /dev/null +++ b/src/utils/tree.ts @@ -0,0 +1,25 @@ +/** + * 递归访问整个树 + */ +export function visitTree( + tree: any[], + cb: (item: any, parent: any, deep: number) => void, + options?: { + childrenMapName?: string; + }, +) { + options = { + childrenMapName: 'children', + ...options, + }; + const inFn = (data: any[], parent: any, deep: number) => { + for (const item of data) { + cb(item, parent, deep); + const childrenVal = item[options!.childrenMapName!]; + if (childrenVal && childrenVal.length > 0) { + inFn(childrenVal, item, deep + 1); + } + } + }; + inFn(tree, null, 1); +} \ No newline at end of file diff --git a/src/utils/validation.ts b/src/utils/validation.ts new file mode 100644 index 0000000000000000000000000000000000000000..2548dc6a3c2c129cc5b715a90912c6ce48520fda --- /dev/null +++ b/src/utils/validation.ts @@ -0,0 +1,5 @@ +export function vaildAccount(account: string): boolean { + var isEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(account); + var isPhone = /(^1[3|4|5|6|7|8|9]\d{9}$)|(^09\d{8}$)/.test(account); + return isEmail || isPhone; +} \ No newline at end of file