index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /**
  2. * 防抖
  3. * debounce
  4. * @param {Function} fn
  5. * @param {Number} delay
  6. * @returns {Function}
  7. * @example
  8. * const fn = () => console.log('hello world');
  9. * const debounceFn = debounce(fn, 1000);
  10. */
  11. export const debounce = (fn, delay = 10) => {
  12. let timer = null;
  13. return function () {
  14. if (timer) {
  15. clearTimeout(timer);
  16. }
  17. timer = setTimeout(function () {
  18. //模拟触发change事件
  19. // Simulate triggering change event
  20. fn.apply(this, arguments);
  21. // 清空计时器
  22. // Clear timer
  23. timer = null;
  24. }, delay);
  25. };
  26. };
  27. /**
  28. * 节流
  29. * throttle
  30. * @param {Function} fn
  31. * @param {Number} delay
  32. * @returns {Function}
  33. */
  34. export const throttle = (fn, delay = 50) => {
  35. let timer = null;
  36. return function () {
  37. if (timer) {
  38. return;
  39. }
  40. let context = this;
  41. let args = arguments;
  42. timer = setTimeout(function () {
  43. fn.apply(context, args);
  44. timer = null;
  45. }, delay);
  46. };
  47. };
  48. /**
  49. * 节流
  50. * throttle With requestAnimationFrame
  51. * @param {Function} fn
  52. * @param {Number} delay
  53. * @returns {Function}
  54. */
  55. export const throttleWithRAF = (fn, delay = 16) => {
  56. let timeoutId = null;
  57. let rafId = null;
  58. let lastExec = 0;
  59. const throttledFn = function (...args) {
  60. const now = performance.now();
  61. const remainingTime = delay - (now - lastExec);
  62. if (rafId === null && timeoutId === null) {
  63. // 如果没有rafId和timeoutId,说明没有正在进行的动画帧或定时器
  64. // If there is no rafId and timeoutId, it means there is no animation frame or timer in progress.
  65. rafId = requestAnimationFrame(() => {
  66. // 在动画帧开始时执行函数
  67. // Executes a function at the beginning of an animation frame
  68. fn.apply(this, args);
  69. lastExec = performance.now();
  70. rafId = null;
  71. });
  72. } else if (remainingTime <= 0) {
  73. // 如果超过了延迟时间,取消当前的定时器,然后在下一个动画帧执行
  74. // If the delay time is exceeded, cancel the current timer and execute it in the next animation frame.
  75. clearTimeout(timeoutId);
  76. timeoutId = null;
  77. rafId = requestAnimationFrame(() => {
  78. fn.apply(this, args);
  79. lastExec = performance.now();
  80. rafId = null;
  81. });
  82. } else {
  83. // 如果还有剩余时间,设置定时器
  84. // If there is time left, set the timer
  85. clearTimeout(timeoutId);
  86. timeoutId = setTimeout(() => {
  87. rafId = requestAnimationFrame(() => {
  88. fn.apply(this, args);
  89. lastExec = performance.now();
  90. rafId = null;
  91. });
  92. }, remainingTime);
  93. }
  94. };
  95. throttledFn.clear = () => {
  96. if (timeoutId !== null) {
  97. clearTimeout(timeoutId);
  98. timeoutId = null;
  99. }
  100. if (rafId !== null) {
  101. cancelAnimationFrame(rafId);
  102. rafId = null;
  103. }
  104. };
  105. return throttledFn;
  106. };
  107. /**
  108. * 将数字按照步长进行四舍五入
  109. * Round the number according to the step length
  110. * @param {Number} num
  111. * @param {Number} step
  112. * @returns {Number}
  113. * @example
  114. * stepNumberFun(18, 5) // 20
  115. */
  116. export const stepNumberFun = (num, step = 1) => {
  117. //将step乘10,转换为整数,避免小数计算精度问题
  118. // Multiply step by 10, convert to integer, avoid decimal calculation precision problem
  119. const stepNum = step * 10;
  120. return (Math.round((num * 10) / stepNum) * stepNum) / 10;
  121. };
  122. /**
  123. * 传入Dom和一段字符串,返回占页面的宽度
  124. * Pass in Dom and a string, return the width occupied by the page
  125. * @param {HTMLElement} dom
  126. * @param {String} str
  127. * @returns {Number}
  128. * @example
  129. * getDomWidth(document.body, 'hello world') // 100
  130. */
  131. export const getDomWidth = (dom, str) => {
  132. let span = document.createElement('span');
  133. span.innerHTML = str;
  134. span.style.visibility = 'hidden';
  135. dom.appendChild(span);
  136. let width = span.getBoundingClientRect().width;
  137. dom.removeChild(span);
  138. return width;
  139. };
  140. /**
  141. * 根据年份和月份获取对应的天数
  142. * Get the number of days corresponding to the year and month according to the year and month
  143. * @param {*} year
  144. * @param {*} month
  145. * @returns {Number}
  146. * @example
  147. * getDayNum(2020, '02') // 29
  148. */
  149. export const getDayNum = (year, month) => {
  150. if (month === '02') {
  151. return isLeapYear(year) ? 29 : 28;
  152. } else if (month === '04' || month === '06' || month === '09' || month === '11') {
  153. return 30;
  154. } else {
  155. return 31;
  156. }
  157. };
  158. /**
  159. * 是否是闰年
  160. * Is it a leap year
  161. * @param {*} year
  162. * @returns {Boolean}
  163. * @example
  164. * isLeapYear(2020) // true
  165. */
  166. export const isLeapYear = year => {
  167. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  168. };
  169. /**
  170. * 返回当前日期所处周的开始与结束日期组成的数组,日期格式为 YYYYMMDD
  171. * return an array of start and end date of current week, date format is YYYYMMDD
  172. * @returns {Array}
  173. * @example
  174. * getWeekRange() // ['20200518', '20200524']
  175. */
  176. export const getWeekRange = () => {
  177. const now = new Date();
  178. const year = now.getFullYear();
  179. const month = now.getMonth();
  180. const day = now.getDate();
  181. const week = now.getDay();
  182. const start = new Date(year, month, day - week + 1);
  183. const end = new Date(year, month, day + (7 - week));
  184. return [start, end].map(date => {
  185. const year = date.getFullYear();
  186. const month = date.getMonth() + 1;
  187. const day = date.getDate();
  188. return `${year}${month < 10 ? `0${month}` : month}${day < 10 ? `0${day}` : day}`;
  189. });
  190. };
  191. /**
  192. * 返回当前日期所处月的开始与结束日期组成的数组,日期格式为 YYYYMMDD
  193. * return an array of start and end date of current month, date format is YYYYMMDD
  194. * @returns {Array}
  195. * @example
  196. * getMonthRange() // ['20200501', '20200531']
  197. */
  198. export const getMonthRange = () => {
  199. const now = new Date();
  200. const year = now.getFullYear();
  201. const month = now.getMonth();
  202. const start = new Date(year, month, 1);
  203. const end = new Date(year, month + 1, 0);
  204. return [start, end].map(date => {
  205. const year = date.getFullYear();
  206. const month = date.getMonth() + 1;
  207. const day = date.getDate();
  208. return `${year}${month < 10 ? `0${month}` : month}${day < 10 ? `0${day}` : day}`;
  209. });
  210. };
  211. /**
  212. * 传入年月数据 YYYYMM,返回开始日是周几
  213. * Pass in year and month data YYYYMM, return the start day of the week
  214. * @param {String} yearMonth
  215. * @returns {Number}
  216. * @example
  217. * getStartDay('202005') // 6
  218. */
  219. export const getStartDay = yearMonth => {
  220. const year = yearMonth.slice(0, 4);
  221. const month = yearMonth.slice(4);
  222. return new Date(`${year}-${month}-01`).getDay();
  223. };
  224. /**
  225. * 传入正数或者负数 n,返回当前月往前或者往后的 n 个月的年月数据,格式为 YYYYMM,不足两位的月份前面补 0
  226. * Pass in a positive or negative number n, return the year and month data of the current month forward or backward n months, the format is YYYYMM, and the month less than two digits is filled with 0
  227. * @param {Number} n 正整数或者负整数
  228. * @returns {String} 指定月份的年月数据 YYYYMM
  229. * @example
  230. * getYearMonth(1) // '202006'
  231. */
  232. export const getNowBeforeOrAfterMonth = n => {
  233. const now = new Date();
  234. const year = now.getFullYear();
  235. const month = now.getMonth() + 1;
  236. const newMonth = month + n;
  237. if (newMonth > 12) {
  238. return `${year + 1}${newMonth - 12 < 10 ? `0${newMonth - 12}` : newMonth - 12}`;
  239. } else if (newMonth < 1) {
  240. return `${year - 1}${newMonth + 12 < 10 ? `0${newMonth + 12}` : newMonth + 12}`;
  241. } else {
  242. return `${year}${newMonth < 10 ? `0${newMonth}` : newMonth}`;
  243. }
  244. };
  245. /**
  246. * 传入年月数据 YYYYMM,根据当月 1 日的周几,返回当月的日历数据数组。如果 startSunday 为 true 从周日开始,否则从周一开始,1 日之前的数据为空字符串,之后的数据为空字符串并去除
  247. * Pass in year and month data YYYYMM, return the calendar data array according to the week of the first day of the month. If startSunday is true, start from Sunday, otherwise start from Monday. The data before 1 day is an empty string, and the data after 1 day is an empty string and is removed
  248. * @param {String} yearMonth
  249. * @param {Boolean} startSunday
  250. * @returns {Array}
  251. * @example
  252. * getCalendarData('202005') // ['','','','','','','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25','26','27','28','29','30','31','']
  253. */
  254. export const getCalendarData = (yearMonth, startSunday) => {
  255. const year = yearMonth.slice(0, 4);
  256. const month = yearMonth.slice(4);
  257. const startDay = getStartDay(yearMonth);
  258. const dayNum = getDayNum(year, month);
  259. const data = [];
  260. for (let i = 0; i < (startDay > 0 ? startDay - (startSunday ? 0 : 1) : 7 - (startSunday ? 0 : 1)); i++) {
  261. data.push({ day: '' });
  262. }
  263. for (let i = 1; i <= dayNum; i++) {
  264. // @ts-ignore
  265. data.push({ day: i.toString().padStart(2, '0') });
  266. }
  267. for (let i = 0; i < 42 - startDay - (startSunday ? 0 : 1) - dayNum; i++) {
  268. data.push({ day: '' });
  269. }
  270. // 去除结尾的所有空字符
  271. // Remove all empty characters at the end
  272. while (data[data.length - 1].day === '') {
  273. data.pop();
  274. }
  275. // 如果 day 是 00 或 0 的替换为空字符
  276. // If day is 00, replace it with an empty character
  277. data.forEach(item => {
  278. if (item.day === '00' || item.day === '0') {
  279. item.day = '';
  280. }
  281. });
  282. // 循环 data,如果 day 不为 '',则计算出 day 是周几,放在 week
  283. // Loop data, if day is not '', then calculate that day is what day of the week, put it in week
  284. data.forEach(item => {
  285. if (item.day !== '') {
  286. // @ts-ignore
  287. item.week = new Date(`${year}-${month}-${item.day}`).getDay();
  288. }
  289. });
  290. // 循环 data,如果当天是月末,endDay 为 true,否则为 false
  291. // Loop data, if today is the end of the month, endDay is true, otherwise it is false
  292. data.forEach((item, index) => {
  293. if (item.day !== '') {
  294. // @ts-ignore
  295. item.endDay = index === data.length - 1;
  296. // @ts-ignore
  297. item.startDay = item.day === '01';
  298. }
  299. });
  300. // 根据从周一开始还是周末开始,循环 data,如果 day 不为 '',判断每天是 周第一天 还是 周最后一天,分别放在 weekStartDay 和 weekEndDay
  301. // According to whether it starts from Monday or Sunday, loop data, if day is not '', judge whether each day is the first day of the week or the last day of the week, and put it in weekStartDay and weekEndDay respectively
  302. if (!startSunday) {
  303. data.forEach(item => {
  304. if (item.day !== '') {
  305. // @ts-ignore
  306. item.weekStartDay = item.week === 1;
  307. // @ts-ignore
  308. item.weekEndDay = item.week === 0;
  309. }
  310. });
  311. } else {
  312. data.forEach(item => {
  313. if (item.day !== '') {
  314. // @ts-ignore
  315. item.weekStartDay = item.week === 0;
  316. // @ts-ignore
  317. item.weekEndDay = item.week === 6;
  318. }
  319. });
  320. }
  321. // 根据每一天的 startDay和weekStartDay,endDay和weekEndDay,只要有一项是 true,就是月第一天或周第一天,放在 start 和 end 中
  322. // According to the startDay and weekStartDay of each day, endDay and weekEndDay, as long as one item is true, it is the first day of the month or the first day of the week, put it in start and end
  323. data.forEach(item => {
  324. if (item.day !== '') {
  325. // @ts-ignore
  326. item.start = item.startDay || item.weekStartDay;
  327. // @ts-ignore
  328. item.end = item.endDay || item.weekEndDay;
  329. }
  330. });
  331. return data;
  332. };
  333. /**
  334. * 传入格式为 YYYYMM 的开始与结束月份字符串,返回这两个月份之间的所有月份数据数组,格式为 YYYYMM,不足两位的月份前面补 0
  335. * Pass in the start and end month strings in the format of YYYYMM, return the data array of all months between these two months, the format is YYYYMM, and the month less than two digits is filled with 0
  336. * @param {String} startMonthStr
  337. * @param {String} endMonthStr
  338. * @returns {Array}
  339. * @example
  340. * getMonthRange('202005', '202007') // ['202005', '202006', '202007']
  341. */
  342. export const getMonthListRange = (startMonthStr, endMonthStr) => {
  343. const startYear = startMonthStr.slice(0, 4);
  344. const startMonth = startMonthStr.slice(4);
  345. const endYear = endMonthStr.slice(0, 4);
  346. const endMonth = endMonthStr.slice(4);
  347. const data = [];
  348. if (startYear === endYear) {
  349. for (let i = Number(startMonth); i <= Number(endMonth); i++) {
  350. data.push(`${startYear}${i < 10 ? `0${i}` : i}`);
  351. }
  352. } else {
  353. for (let i = Number(startMonth); i <= 12; i++) {
  354. data.push(`${startYear}${i < 10 ? `0${i}` : i}`);
  355. }
  356. for (let i = 1; i <= Number(endMonth); i++) {
  357. data.push(`${endYear}${i < 10 ? `0${i}` : i}`);
  358. }
  359. }
  360. return data;
  361. };
  362. /**
  363. * 传入开始与结束日期 YYYYMMDD,需要剔除的日期组成的数组 disabledDates。返回中间的所有日期数组,包含开始与结束日期,月与日不足两位的前面补 0,格式为 YYYYMMDD
  364. * Pass in the start and end date YYYYMMDD, the array of disabled dates to be removed. Return the array of all dates in the middle, including the start and end dates, and the month and day less than two digits are filled with 0, in the format of YYYYMMDD
  365. * @param {String} startDate
  366. * @param {String} endDate
  367. * @param {Array} disabledDates
  368. * @returns {Array}
  369. * @example
  370. * getDateRange('20200501', '20200503') // ['20200501', '20200502', '20200503']
  371. */
  372. export const getDateRange = (startDate, endDate, disabledDates) => {
  373. // 将 startDate 与 endDate 转化为 YYYY-MM-DD格式
  374. // Convert startDate and endDate to YYYY-MM-DD format
  375. const startYear = startDate.slice(0, 4);
  376. const startMonth = startDate.slice(4, 6);
  377. const startDay = startDate.slice(6);
  378. const endYear = endDate.slice(0, 4);
  379. const endMonth = endDate.slice(4, 6);
  380. const endDay = endDate.slice(6);
  381. const startTimeStr = new Date(`${startYear}-${startMonth}-${startDay}`).getTime();
  382. const endTimeStr = new Date(`${endYear}-${endMonth}-${endDay}`).getTime();
  383. const startTime = new Date(startTimeStr).getTime();
  384. const endTime = new Date(endTimeStr).getTime();
  385. const data = [];
  386. for (let i = startTime; i <= endTime; i += 24 * 60 * 60 * 1000) {
  387. const date = new Date(i);
  388. const year = date.getFullYear();
  389. const month = date.getMonth() + 1;
  390. const day = date.getDate();
  391. data.push(`${year}${month < 10 ? `0${month}` : month}${day < 10 ? `0${day}` : day}`);
  392. }
  393. // 剔除 disabledDates 中的日期
  394. // Remove dates in disabledDates
  395. if (disabledDates && disabledDates.length) {
  396. disabledDates.forEach(item => {
  397. const index = data.indexOf(item);
  398. if (index > -1) {
  399. data.splice(index, 1);
  400. }
  401. });
  402. }
  403. return data;
  404. };
  405. /**
  406. * 传入 startSunday,返回当天所处周的所有日期数组,日期格式为 YYYYMMDD,月与日不足两位的前面补 0
  407. * Pass in startSunday, return the array of all dates in the week of the current day, the date format is YYYYMMDD, and the month and day less than two digits are filled with 0
  408. * @param {Boolean} startSunday
  409. * @returns {Array}
  410. * @example
  411. * getWeekRange(true) // ['20200531', '20200601', '20200602', '20200603', '20200604', '20200605', '20200606']
  412. */
  413. export const getCurrentWeek = (startSunday = false) => {
  414. const now = new Date();
  415. const year = now.getFullYear();
  416. const month = now.getMonth() + 1;
  417. const date = now.getDate();
  418. const day = now.getDay();
  419. const weekDates = [];
  420. if (startSunday) {
  421. for (let i = 0; i < 7; i++) {
  422. const newDate = new Date(year, month - 1, date - day + i);
  423. const newYear = newDate.getFullYear();
  424. const newMonth = newDate.getMonth() + 1;
  425. const newDateNum = newDate.getDate();
  426. // @ts-ignore
  427. weekDates.push(`${newYear}${newMonth.toString().padStart(2, '0')}${newDateNum.toString().padStart(2, '0')}`);
  428. }
  429. } else {
  430. for (let i = 1; i < 8; i++) {
  431. const newDate = new Date(year, month - 1, date - day + i);
  432. const newYear = newDate.getFullYear();
  433. const newMonth = newDate.getMonth() + 1;
  434. const newDateNum = newDate.getDate();
  435. // @ts-ignore
  436. weekDates.push(`${newYear}${newMonth.toString().padStart(2, '0')}${newDateNum.toString().padStart(2, '0')}`);
  437. }
  438. }
  439. return weekDates;
  440. };
  441. /**
  442. * 返回当天所处月份的所有日期数组,日期格式为 YYYYMMDD,月与日不足两位的前面补 0
  443. * Return the array of all dates in the month of the current day, the date format is YYYYMMDD, and the month and day less than two digits are filled with 0
  444. * @returns {Array}
  445. */
  446. export const getCurrentMonth = () => {
  447. const now = new Date();
  448. const year = now.getFullYear();
  449. const month = now.getMonth() + 1;
  450. const days = new Date(year, month, 0).getDate();
  451. const dates = [];
  452. for (let i = 1; i <= days; i++) {
  453. // @ts-ignore
  454. dates.push(`${year}${month.toString().padStart(2, '0')}${i.toString().padStart(2, '0')}`);
  455. }
  456. return dates;
  457. };
  458. /**
  459. * 返回当天所处季度的所有日期数组,日期格式为 YYYYMMDD,月与日不足两位的前面补 0
  460. * Pass in the date, return the array of all dates in the quarter of the current day, the date format is YYYYMMDD, and the month and day less than two digits are filled with 0
  461. * @returns {Array}
  462. */
  463. export const getCurrentQuarter = () => {
  464. const now = new Date();
  465. const year = now.getFullYear();
  466. const month = now.getMonth() + 1;
  467. const quarter = Math.floor((month - 1) / 3) + 1;
  468. const startMonth = (quarter - 1) * 3 + 1;
  469. const endMonth = quarter * 3;
  470. const days = [];
  471. for (let i = startMonth; i <= endMonth; i++) {
  472. const daysInMonth = new Date(year, i, 0).getDate();
  473. for (let j = 1; j <= daysInMonth; j++) {
  474. // @ts-ignore
  475. days.push(`${year}${i.toString().padStart(2, '0')}${j.toString().padStart(2, '0')}`);
  476. }
  477. }
  478. return days;
  479. };
  480. /**
  481. * 传入正数或负数天数 n 和是否从今天开始计算 today,返回对应的前后日期组成的数组,日期格式为 YYYYMMDD,月与日不足两位的前面补 0
  482. * Pass in a positive or negative number of days n, whether to include today hasToday, return the array of dates before and after the current day, the date format is YYYYMMDD, and the month and day less than two digits are filled with 0
  483. * @param {Number} n
  484. * @param {Boolean} today
  485. * @returns {Array}
  486. */
  487. export const getDaysRangeWithToday = (n = 0, today = true) => {
  488. const now = new Date();
  489. let fakeToday = now;
  490. if (today) {
  491. fakeToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  492. } else {
  493. if (n < 0) {
  494. fakeToday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
  495. } else if (n > 0) {
  496. fakeToday = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
  497. } else {
  498. fakeToday = now;
  499. }
  500. }
  501. const dates = [];
  502. for (let i = 0; i < Math.abs(n); i++) {
  503. const date = new Date(fakeToday.getTime() + (n > 0 ? i : -i) * 24 * 60 * 60 * 1000);
  504. // @ts-ignore
  505. dates.push(`${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`);
  506. }
  507. // 对日期排序
  508. // Sort dates
  509. dates.sort((a, b) => Number(a) - Number(b));
  510. return dates;
  511. };