整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          日歷簽到和積分累計的php實現方法

          網站開發過程中我們會經常用到簽到功能來獎勵用戶積分,或者做一些其他活動。這次項目開發過程中做了日歷簽到,因為沒有經驗所有走了很多彎路,再次記錄過程和步驟。

          1.日歷簽到樣式:使用的是calendar日歷插件

          前臺代碼

          <!doctype html>
          <html>
          <head>
          <meta charset="utf-8">
          <title>日歷簽到</title>
          <meta name="keywords" content="日歷簽到"/>
          <meta name="description" content="日歷簽到"/>
          <meta content="telephone=no" name="format-detection" />
          <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" name="viewport" />
          <meta content="yes" name="apple-mobile-web-app-capable" />
          <meta content="black" name="apple-mobile-web-app-status-bar-style" />
          <link href="__TPL__/css/index.css" rel="stylesheet" type="text/css">
          <link rel="stylesheet" type="text/css" href="__TPL__/css/main.css">
          <link rel="stylesheet" type="text/css" href="__TPL__/css/self.css">
          <link rel="stylesheet" type="text/css" href="__TPL__/css/swiper.min.css">
          </head>
          <body style="background:#fff;">
          <div class="warpper">
           <div class="sign">
           <ul>
           <li><span>本月簽到</span><b><?if(is_array($sign)){echo count($sign);}else{echo '0';}?></b></li>
           <li><span>簽到累計</span><b><?php if(is_array($allsign)){echo count($allsign);}else{echo '0';}?></b></li>
           <li><span>累計積分</span><b><?php if(is_array($allsign)){echo count($allsign)*$config['site_praise'];}else{echo '0';}?></b></li>
           <li><span>全部積分</span><b><?php echo ceil($user['integral']);?></b></li>
           </ul>
           </div>
           <input type="hidden" name="" value="__URL__/checksign.html" id="sign">
           <div class="singBox" id="calendar"></div>
           <div class="qdbox"><a href="javascript:;" class="qd_btn" onclick="sign_()" style="background: <?php echo is_sign_now()?'':'rgb(135, 135, 135)'; ?>" ><?php echo is_sign_now()?'簽到':'已簽到'; ?></a></div>
          <script src="__TPL__/js/jquery.min.js"></script> 
          <script src="__TPL__/js/swiper.min.js"></script>
          <script src="__TPL__/js/calendar.js"></script>
          <script src="__TPL__/js/js.js"></script>
          <script type="text/javascript" src="__TPL__/js/layer/2.1/layer.js"></script>
          {include file="footer"}
          <script type="text/javascript">
          $(function(){
           var arr='';
          //var signList=[{"signDay":"09"},{"signDay":"11"}];
           <?php if(is_array($sign)){ foreach($sign as $vo){ ?>
           arr+="{'signDay':'<?php echo $vo['day'];?>'},";
           <?php }?>
           arr = arr.substr(0,arr.length-1);
           arr ="["+arr+"]";
           var signList = eval('(' + arr + ')');
           <?php }else{?>
           var signList=[];
           <?php }?>
           calUtil.init(signList);
          });
          </script>
          <script type="text/javascript">
          function sign_(){
           $.ajax({
           type:'GET',
           url:"__URL__/checksign.html", 
           dataType:'json',
           success:function(res){
           if(res.result==1){
           window.location.href="__URL__/sign.html"
           }else{
           alert(res.msg);
           }
           }
           })
          }
          </script>
          
          

          插件calendar.js 修改如下:

          var calUtil = {
           //當前日歷顯示的年份
           showYear:2015,
           //當前日歷顯示的月份
           showMonth:1,
           //當前日歷顯示的天數
           showDays:1,
           eventName:"load",
           //初始化日歷
           init:function(signList,s=''){
           calUtil.setMonthAndDay();
           if (typeof(s) == 'undefined'){
           }else{
           signList.splice('','',s);
           }
           calUtil.draw(signList);
           calUtil.bindEnvent(signList);
           },
           draw:function(signList){
           //綁定日歷
           //alert(signList.length);
           // console.log(signList);
           if(signList.length > 21){
           //alert(21);
           $("#sign_note").empty();
           $("#sign_note").html('<button class="sign_contener" type="button"><i class="fa fa-calendar-check-o" aria-hidden="true"></i> 已達標,獲取1次抽獎</button>');
           }
           var str = calUtil.drawCal(calUtil.showYear,calUtil.showMonth,signList);
           $("#calendar").html(str);
           //綁定日歷表頭
           var calendarName=calUtil.showYear+"/"+calUtil.showMonth+"";
           $(".calendar_month_span").html(calendarName); 
           },
           //綁定事件
           bindEnvent:function(signList){
           // //綁定上個月事件
           // $(".calendar_month_prev").click(function(){
           // //ajax獲取日歷json數據
           // //var signList=[{"signDay":"10"},{"signDay":"11"},{"signDay":"12"},{"signDay":"13"}];
           // calUtil.eventName="prev";
           // calUtil.init(signList);
           // });
           // //綁定下個月事件
           // $(".calendar_month_next").click(function(){
           // //ajax獲取日歷json數據
           // //var signList=[{"signDay":"10"},{"signDay":"11"},{"signDay":"12"},{"signDay":"13"}];
           // calUtil.eventName="next";
           // calUtil.init(signList);
           // });
           $(".calendar_record").click(function(){
           //ajax獲取日歷json數據
           // console(typeof(signList)+"yxy");
           //var signList=[{"signDay":"10"},{"signDay":"11"},{"signDay":"12"},{"signDay":"13"}];
           //var tmp = {"signDay":$(this).html()};
           //if (typeof(signList) == 'undefined'){
           //不做處理
           //}else{
           // signList.splice('','',tmp);
           // console.log(signList);
           // calUtil.init(signList);
           // }
           //alert($(this).html());
           var tmp = {"signDay":$(this).html()};
           console.log(tmp.signDay)
           // if(tmp.signDay==11){
           //執行簽到
           $.ajax({
           type:'POST',
           url:"checksign.html", 
           data:{day:tmp.signDay},
           dataType:'json',
           success:function(res){
           // if(res.result==1){
           // calUtil.init(signList,tmp);
           // }else{
           alert(res.msg);
           location.reload(true);
           // }
           }
           })
           // }else{
           // alert("請簽到當天日期")
           // }
           });
           },
           //獲取當前選擇的年月
           setMonthAndDay:function(){
           switch(calUtil.eventName)
           {
           case "load":
           var current = new Date();
           calUtil.showYear=current.getFullYear();
           calUtil.showMonth=current.getMonth() + 1;
           break;
           case "prev":
           var nowMonth=$(".calendar_month_span").html().split("年")[1].split("月")[0];
           calUtil.showMonth=parseInt(nowMonth)-1;
           if(calUtil.showMonth==0)
           {
           calUtil.showMonth=12;
           calUtil.showYear-=1;
           }
           break;
           case "next":
           var nowMonth=$(".calendar_month_span").html().split("年")[1].split("月")[0];
           calUtil.showMonth=parseInt(nowMonth)+1;
           if(calUtil.showMonth==13)
           {
           calUtil.showMonth=1;
           calUtil.showYear+=1;
           }
           break;
           }
           },
           getDaysInmonth : function(iMonth, iYear){
           var dPrevDate = new Date(iYear, iMonth, 0);
           return dPrevDate.getDate();
           },
           bulidCal : function(iYear, iMonth) {
           var aMonth = new Array();
           aMonth[0] = new Array(7);
           aMonth[1] = new Array(7);
           aMonth[2] = new Array(7);
           aMonth[3] = new Array(7);
           aMonth[4] = new Array(7);
           aMonth[5] = new Array(7);
           aMonth[6] = new Array(7);
           var dCalDate = new Date(iYear, iMonth - 1, 1);
           var iDayOfFirst = dCalDate.getDay();
           var iDaysInMonth = calUtil.getDaysInmonth(iMonth, iYear);
           var iVarDate = 1;
           var d, w;
           aMonth[0][0] = "日";
           aMonth[0][1] = "一";
           aMonth[0][2] = "二";
           aMonth[0][3] = "三";
           aMonth[0][4] = "四";
           aMonth[0][5] = "五";
           aMonth[0][6] = "六";
           for (d = iDayOfFirst; d < 7; d++) {
           aMonth[1][d] = iVarDate;
           iVarDate++;
           }
           for (w = 2; w < 7; w++) {
           for (d = 0; d < 7; d++) {
           if (iVarDate <= iDaysInMonth) {
           aMonth[w][d] = iVarDate;
           iVarDate++;
           }
           }
           }
           return aMonth;
           },
           ifHasSigned : function(signList,day){
           var signed = false;
           $.each(signList,function(index,item){
           if(item.signDay == day) {
           signed = true;
           return false;
           }
           });
           return signed ;
           },
           drawCal : function(iYear, iMonth ,signList) {
           var myMonth = calUtil.bulidCal(iYear, iMonth);
           var htmls = new Array();
           htmls.push("<div class='sign_main' id='sign_layer'>");
           htmls.push("<div class='sign_succ_calendar_title'>");
           //htmls.push("<div class='calendar_month_next'>下月</div>");
           //htmls.push("<div class='calendar_month_prev'>上月</div>");
           htmls.push("<div class='calendar_month_span'></div>");
           htmls.push("</div>");
           htmls.push("<div class='sign_equal' id='sign_cal'>");
           htmls.push("<div class='sign_row'>");
           htmls.push("<div class='th_1 bold'>" + myMonth[0][0] + "</div>");
           htmls.push("<div class='th_2 bold'>" + myMonth[0][1] + "</div>");
           htmls.push("<div class='th_3 bold'>" + myMonth[0][2] + "</div>");
           htmls.push("<div class='th_4 bold'>" + myMonth[0][3] + "</div>");
           htmls.push("<div class='th_5 bold'>" + myMonth[0][4] + "</div>");
           htmls.push("<div class='th_6 bold'>" + myMonth[0][5] + "</div>");
           htmls.push("<div class='th_7 bold'>" + myMonth[0][6] + "</div>");
           htmls.push("</div>");
           var d, w;
           for (w = 1; w < 6; w++) {
           htmls.push("<div class='sign_row'>");
           for (d = 0; d < 7; d++) {
           var ifHasSigned = calUtil.ifHasSigned(signList,myMonth[w][d]);
           console.log("001:"+ifHasSigned);
           if(ifHasSigned && typeof(myMonth[w][d]) != 'undefined'){
           htmls.push("<div class='td_"+d+" on'>" + (!isNaN(myMonth[w][d]) ? myMonth[w][d] : " ") + "</div>");
           } else {
           htmls.push("<div class='td_"+d+" calendar_record'>" + (!isNaN(myMonth[w][d]) ? myMonth[w][d] : " ") + "</div>");
           }
           }
           htmls.push("</div>");
           }
           htmls.push("</div>");
           htmls.push("</div>");
           htmls.push("</div>");
           return htmls.join('');
           }
          };
          

          PHP代碼的實現

          我們的投資過程中,每天復盤、查資料都需要看很多網站。為了方便大家,今天就把我平時常看的優質網站整理了一下,分享給大家,希望對大家有幫助,覺得好的話可以分享或者收藏起來,以備不時之需!這些都是經過人工篩選,絕對干貨哦!

          一、重要數據

          資金流向 http://data.eastmoney.com/zjlx/

          投資者數量 http://www.chinaclear.cn/zdjs/xmzkb/center_mzkb.shtml

          龍虎榜 http://data.eastmoney.com/stock/lhb.html

          新股日歷 http://data.eastmoney.com/xg/xg/calendar.html

          股權質押 http://data.eastmoney.com/gpzy/

          滬港通 http://data.eastmoney.com/hsgt/index.html

          業績預告 http://data.eastmoney.com/bbsj/yjyg.html

          基金數據 http://fund.eastmoney.com/data/

          經濟數據 http://data.eastmoney.com/cjsj/cpi.html

          行情中心 http://quote.eastmoney.com/center/

          股指期貨 http://futures.10jqka.com.cn/

          新三板 http://xinsanban.10jqka.com.cn/

          IPO信息 http://www.cninfo.com.cn/eipo/index.jsp

          美股咨詢 http://stock.10jqka.com.cn/usstock/

          私募信息 https://www.simuwang.com/

          二、常用軟件

          同花順 http://www.10jqka.com.cn/

          東方財富網 https://www.eastmoney.com/

          大智慧 http://www.dzh.com.cn/

          富途牛牛 https://www.futunn.com/?lang=zh-CN

          老虎證券 https://www.itiger.com/

          三、優質網站

          華爾街見聞 https://wallstreetcn.com/

          雪球 https://xueqiu.com/

          英為財經 https://cn.investing.com/

          財聯社 https://www.cls.cn/

          四、研報

          發現研報 https://www.fxbaogao.com/

          蘿卜投研 https://robo.datayes.com/

          東方財富研報 https://data.eastmoney.com/report/

          報告查一查 http://report.seedsufe.com/#/index

          烏拉邦研報搜索引擎 https://www.ulapia.com/

          行行查 https://www.hanghangcha.com/

          樂晴智庫 http://www.767stock.com/

          五、量化交易

          Bigquant https://bigquant.com/

          掘金https://www.myquant.cn/

          優礦https://uqer.datayes.com/

          萬礦http://windquant.com/

          聚寬https://www.joinquant.com

          米筐http://www.ricequant.com


          看完文章的朋友歡迎點下關注轉發,方便及時收到最新潛力優質股分析!投資路上,深度研究筆記,與你一路同行!

          漲停|翻倍|白馬股|龍頭|價值投資|牛股|行業分析|估值|自選股|分紅|高增長|業績|財務|暴漲|風口|

          機構|散戶|牛散|莊家|成長股|績優股|藍籌|外資|基金|QFII|融資融券|醫藥|白酒|股票|貴州茅臺|

          賺錢|分紅|換手|漲幅|收益|量比|市盈率|市凈率|凈資產|市值|估值|證金|美元|人民幣|馬云|成長性|

          消費股|安全墊|利潤率|題材股|

          上周接到一個需求,開發一個工作日歷組件。找了一圈沒找到合適的,索性自己寫一個。

          下面給大家分享一下組件中使用到的一些日期API和后面實現的框選元素功能。

          效果展示

          demo體驗地址:dbfu.github.io/work-calend…

          開始之前

          lunar-typescript

          介紹組件之前先給大家介紹一個庫lunar-typescript。

          lunar是一個支持陽歷、陰歷、佛歷和道歷的日歷工具庫,它開源免費,有多種開發語言的版本,不依賴第三方,支持陽歷、陰歷、佛歷、道歷、儒略日的相互轉換,還支持星座、干支、生肖、節氣、節日、彭祖百忌、每日宜忌、吉神宜趨、兇煞宜忌、吉神方位、沖煞、納音、星宿、八字、五行、十神、建除十二值星、青龍名堂等十二神、黃道日及吉兇等。僅供參考,切勿迷信。

          這個庫封裝了很多常用的api,并且使用起來也比較簡單。

          本文用到了上面庫的獲取農歷和節氣方法。

          復習Date Api

          new Date

          可以使用new Date()傳年月日三個參數來構造日期,這里注意一下月是從零開始的。

          獲取星期幾

          可以使用getDay方法獲取,注意一下,獲取的值是從0開始的,0表示星期日。

          獲取上個月最后一天

          基于上面api,如果第三個參數傳0,就表示上個月最后一天,-1,是上個月倒數第二天,以此類推。(PS:這個方法還是我有次面試,面試官告訴我的。)

          獲取某個月有多少天

          想獲取某個月有多少天,只需要獲取當月最后天的日期,而當月最后一天,可以用上面new Date第三個參數傳零的方式獲取。

          假設我想獲取2023年12月有多少天,按照下面方式就可以獲取到。

          日期加減

          假設我現在想實現在某個日期上加一天,可以像下面這樣實現。

          這樣實現有個不好的地方,改變了原來的date,如果不想改變date,可以這樣做。

          比較兩個日期

          在寫這個例子的時候,我發現一個很神奇的事情,先看例子。

          大于等于結果是true,小于等于結果也是true,正常來說肯定是等于的,但是等于返回的是false,是不是很神奇。

          其實原理很簡單,用等于號去比較的時候,會直接比較兩個對象的引用,因為是分別new的,所以兩個引用肯定不相等,返回false。

          用大于等于去比較的時候,會默認使用date的valueOf方法返回值去比較,而valueOf返回值也就是時間戳,他們時間戳是一樣的,所以返回true。

          說到這里,給大家分享一個經典面試題。

          console.log(a == 1 && a == 2 && a == 3),希望打印出true。

          原理和上面類似,感興趣的可以挑戰一下。

          這里推薦大家比較兩個日期使用getTime方法獲取時間戳,然后再去比較。

          實戰

          數據結構

          開發之前先把數據結構定一下,一個正確的數據結構會讓程序開發變得簡單。

          根據上面效果圖,可以把數據結構定義成這樣:

          
          /**
           * 日期信息
           */
          export interface DateInfo {
            /**
             * 年
             */
            year: number;
            /**
             * 月
             */
            month: number;
            /**
             * 日
             */
            day: number;
            /**
             * 日期
             */
            date: Date;
            /**
             * 農歷日
             */
            cnDay: string;
            /**
             * 農歷月
             */
            cnMonth: string;
            /**
             * 農歷年
             */
            cnYear: string;
            /**
             * 節氣
             */
            jieQi: string;
            /**
             * 是否當前月
             */
            isCurMonth?: boolean;
            /**
             * 星期幾
             */
            week: number;
            /**
             * 節日名稱
             */
            festivalName: string;
          }
          
          /**
           * 月份的所有周 
           */
          export interface MonthWeek {
            /**
             * 月
             */
            month: number;
            /**
             * 按周分組的日期,7天一組
             */
            weeks: DateInfo[][];
          }
          


          通過算法生成數據結構

          現在數據結構定義好了,下面該通過算法生成上面數據結構了。

          封裝獲取日期信息方法

          /**
           * 獲取給定日期的信息。
           * @param date - 要獲取信息的日期。
           * @param isCurMonth - 可選參數,指示日期是否在當前月份。
           * @returns 包含有關日期的各種信息的對象。
           */
          export const getDateInfo = (date: Date, isCurMonth?: boolean): DateInfo => {
            // 從給定日期創建 農歷 對象
            const lunar = Lunar.fromDate(date);
          
            // 獲取 Lunar 對象中的農歷日、月和年
            const cnDay = lunar.getDayInChinese();
            const cnMonth = lunar.getMonthInChinese();
            const cnYear = lunar.getYearInChinese();
          
            // 獲取農歷節日
            const festivals = lunar.getFestivals();
          
            // 獲取 Lunar 對象中的節氣
            const jieQi = lunar.getJieQi();
          
            // 從日期對象中獲取年、月和日
            const year = date.getFullYear();
            const month = date.getMonth();
            const day = date.getDate();
          
            // 創建包含日期信息的對象
            return {
              year,
              month,
              day,
              date,
              cnDay,
              cnMonth,
              cnYear,
              jieQi,
              isCurMonth,
              week: date.getDay(),
              festivalName: festivals?.[0] || festivalMap[`${month + 1}-${day}`],
            };
          };
          


          上面使用了lunar-typescript庫,獲取了一些農歷信息,節氣和農歷節日。方法第二個參數isCurMonth是用來標記是否是當月的,因為很多月的第一周或最后一周都會補一些其他月日期。

          把月日期按照每周7天格式化

          思路是先獲取給定月的第一天是星期幾,如果前面有空白,用上個月日期填充,然后遍歷當月日期,把當月日期填充到數組中,如果后面有空白,用下個月日期填充。

          /**
           * 返回給定年份和月份的周數組。
           * 每個周是一個天數數組。
           *
           * @param year - 年份。
           * @param month - 月份 (0-11)。
           * @param weekStartDay - 一周的起始日 (0-6) (0: 星期天, 6: 星期六)。
           * @returns 給定月份的周數組。
           */
          const getMonthWeeks = (year: number, month: number, weekStartDay: number) => {
            // 獲取給定月份的第一天
            const start = new Date(year, month, 1);
          
            // 這里為了支持周一或周日在第一天的情況,封裝了獲取星期幾的方法
            const day = getDay(start, weekStartDay);
          
            const days = [];
          
            // 獲取給定月份的前面的空白天數,假如某個月第一天是星期3,并且周日開始,那么這個月前面的空白天數就是3
            // 如果是周一開始,那么這個月前面的空白天數就是2
            // 用上個月日期替換空白天數
            for (let i = 0; i < day; i += 1) {
              days.push(getDateInfo(new Date(year, month, -day + i + 1)));
            }
          
            // 獲取給定月份的天數
            const monthDay = new Date(year, month + 1, 0).getDate();
          
            // 把當月日期放入數組
            for (let i = 1; i <= monthDay; i += 1) {
              days.push(getDateInfo(new Date(year, month, i), true));
            }
          
            // 獲取給定月份的最后一天
            const endDate = new Date(year, month + 1, 0);
            // 獲取最后一天是星期幾
            const endDay = getDay(endDate, weekStartDay);
          
            // 和前面一樣,如果有空白位置就用下個月日期補充上
            for (let i = endDay; i <= 5; i += 1) {
              days.push(getDateInfo(new Date(year, month + 1, i - endDay + 1)));
            }
          
            // 按周排列
            const weeks: DateInfo[][] = [];
            for (let i = 0; i < days.length; i += 1) {
              if (i % 7 === 0) {
                weeks.push(days.slice(i, i + 7));
              }
            }
          
            // 默認每個月都有6個周,如果沒有的話就用下個月日期補充。
            while (weeks.length < 6) {
              const endDate = weeks[weeks.length - 1][6];
              weeks.push(
                Array.from({length: 7}).map((_, i) => {
                  const newDate = new Date(endDate.date);
                  newDate.setDate(newDate.getDate() + i + 1)
                  return getDateInfo(newDate);
                })
              );
            }
            return weeks;
          };
          


          getDay方法實現

          function getDay(date: Date, weekStartDay: number) {
            // 獲取給定日期是星期幾
            const day = date.getDay();
            // 根據給定的周開始日,計算出星期幾在第一天的偏移量
            if (weekStartDay === 1) {
              if (day === 0) {
                return 6;
              } else {
                return day - 1;
              }
            }
            return day;
          }
          


          獲取一年的月周數據

          /**
           * 獲取年份的所有周,按月排列
           * @param year 年
           * @param weekStartDay 周開始日 0為周日 1為周一
           * @returns
           */
          export const getYearWeeks = (year: number, weekStartDay = 0): MonthWeek[] => {
            const weeks = [];
            for (let i = 0; i <= 11; i += 1) {
              weeks.push({month: i, weeks: getMonthWeeks(year, i, weekStartDay)});
            }
            return weeks;
          };
          


          頁面

          頁面布局使用了grid和table,使用grid布局讓一行顯示4個,并且會自動換行。日期顯示使用了table布局。

          如果想學習grid布局,推薦這篇文章。

          工作日歷日期分為三種類型,工作日、休息日、節假日。在渲染單元格根據不同的日期類型,渲染不同背景顏色用于區分。

          維護日期類型

          背景

          雖然節假日信息可以從網上公共api獲取到,但是我們的業務希望可以自己調整日期類型,這個簡單給單元格加一個點擊事件,點擊后彈出一個框去維護當前日期類型,但是業務希望能支持框選多個日期,然后一起調整,這個就稍微麻煩一點,下面給大家分享一下我的做法。

          實現思路

          實現框選框

          定義一個fixed布局的div,設置背景色和邊框顏色,背景色稍微有點透明。監聽全局點擊事件,記錄初始位置,然后監聽鼠標移動事件,拿當前位置減去初始位置就是寬度和高度了,初始位置就是div的left和top。

          獲取框選框內符合條件的dom元素

          當框選框位置改變的時候,獲取所有符合條件的dom元素,然后通過坐標位置判斷dom元素是否和框選框相交,如果相交,說明被框選了,把當前dom返回出去。

          判斷兩個矩形是否相交

          interface Rect {
            x: number;
            y: number;
            width: number;
            height: number;
          }
          
          export function isRectangleIntersect(rect1: Rect, rect2: Rect) {
            // 獲取矩形1的左上角和右下角坐標
            const x1 = rect1.x;
            const y1 = rect1.y;
            const x2 = rect1.x + rect1.width;
            const y2 = rect1.y + rect1.height;
          
            // 獲取矩形2的左上角和右下角坐標
            const x3 = rect2.x;
            const y3 = rect2.y;
            const x4 = rect2.x + rect2.width;
            const y4 = rect2.y + rect2.height;
          
            // 如果 `rect1` 的左上角在 `rect2` 的右下方(即 `x1 < x4` 和 `y1 < y4`),并且 `rect1` 的右下角在 `rect2` 的左上方(即 `x2 > x3` 和 `y2 > y3`),那么這意味著兩個矩形相交,函數返回 `true`。
            // 否則,函數返回 `false`,表示兩個矩形不相交。
            if (x1 < x4 && x2 > x3 && y1 < y4 && y2 > y3) {
              return true;
            } else {
              return false;
            }
          }
          


          具體實現

          框選框組件實現

          import { useEffect, useRef, useState } from 'react';
          
          import { createPortal } from 'react-dom';
          import { isRectangleIntersect } from './utils';
          
          interface Props {
            selectors: string;
            sourceClassName: string;
            onSelectChange?: (selectDoms: Element[]) => void;
            onSelectEnd?: () => void;
            style?: React.CSSProperties,
          }
          
          function BoxSelect({
            selectors,
            sourceClassName,
            onSelectChange,
            style,
            onSelectEnd,
          }: Props) {
          
            const [position, setPosition] = useState({ top: 0, left: 0, width: 0, height: 0 });
          
            const isPress = useRef(false);
          
            const startPos = useRef<any>();
          
            useEffect(() => {
              // 滾動的時候,框選框位置不變,但是元素位置會變,所以需要重新計算
              function scroll() {
                if (!isPress.current) return;
                setPosition(prev => ({ ...prev }));
              }
          
              // 鼠標按下,開始框選
              function sourceMouseDown(e: any) {
                isPress.current = true;
                startPos.current = { top: e.clientY, left: e.clientX };
                setPosition({ top: e.clientY, left: e.clientX, width: 1, height: 1 })
                // 解決誤選擇文本情況
                window.getSelection()?.removeAllRanges();
              }
              // 鼠標移動,移動框選
              function mousemove(e: MouseEvent) {
                if (!isPress.current) return;
          
                let left = startPos.current.left;
                let top = startPos.current.top;
                const width = Math.abs(e.clientX - startPos.current.left);
                const height = Math.abs(e.clientY - startPos.current.top);
          
                // 當后面位置小于前面位置的時候,需要把框的坐標設置為后面的位置
                if (e.clientX < startPos.current.left) {
                  left = e.clientX;
                }
          
                if (e.clientY < startPos.current.top) {
                  top = e.clientY;
                }
          
                setPosition({ top, left, width, height })
              }
          
              // 鼠標抬起
              function mouseup() {
          
                if(!isPress.current) return;
          
                startPos.current = null;
                isPress.current = false;
                // 為了重新渲染一下
                setPosition(prev => ({ ...prev }));
          
                onSelectEnd && onSelectEnd();
              }
          
              const sourceDom = document.querySelector(`.${sourceClassName}`);
          
              if (sourceDom) {
                sourceDom.addEventListener('mousedown', sourceMouseDown);
              }
          
              document.addEventListener('scroll', scroll);
              document.addEventListener('mousemove', mousemove);
              document.addEventListener('mouseup', mouseup);
          
              return () => {
                document.removeEventListener('scroll', scroll);
                document.removeEventListener('mousemove', mousemove);
                document.removeEventListener('mouseup', mouseup);
          
                if (sourceDom) {
                  sourceDom.removeEventListener('mousedown', sourceMouseDown);
                }
              }
            }, [])
          
            useEffect(() => {
              const selectDoms: Element[] = [];
              const boxes = document.querySelectorAll(selectors);
              (boxes || []).forEach((box) => {
                // 判斷是否在框選區域
                if (isRectangleIntersect({
                  x: position.left,
                  y: position.top,
                  width: position.width,
                  height: position.height,
                },
                  box.getBoundingClientRect()
                )) {
                  selectDoms.push(box);
                }
              });
              onSelectChange && onSelectChange(selectDoms);
            }, [position]);
          
          
            return createPortal((
              isPress.current && (
                <div
                  className='fixed bg-[rgba(0,0,0,0.2)]'
                  style={{
                    border: '1px solid #666',
                    ...style,
                    ...position,
                  }}
                />)
            ), document.body)
          }
          
          
          export default BoxSelect;
          


          使用框選框組件,并在框選結束后,給框選日期設置類型

          import { Modal, Radio } from 'antd';
          import { useEffect, useMemo, useRef, useState } from 'react';
          import BoxSelect from './box-select';
          import WorkCalendar from './work-calendar';
          
          import './App.css';
          
          function App() {
          
            const [selectDates, setSelectDates] = useState<string[]>([]);
            const [open, setOpen] = useState(false);
            const [dateType, setDateType] = useState<number | null>();
            const [dates, setDates] = useState<any>({});
          
            const selectDatesRef = useRef<string[]>([]);
          
            const workDays = useMemo(() => {
              return Object.keys(dates).filter(date => dates[date] === 1)
            }, [dates])
          
            const restDays = useMemo(() => {
              return Object.keys(dates).filter(date => dates[date] === 2)
            }, [dates]);
          
            const holidayDays = useMemo(() => {
              return Object.keys(dates).filter(date => dates[date] === 3)
            }, [dates]);
          
            useEffect(() => {
              selectDatesRef.current = selectDates;
            }, [selectDates]);
          
            return (
              <div>
                <WorkCalendar
                  defaultWeekStartDay={0}
                  workDays={workDays}
                  holidayDays={holidayDays}
                  restDays={restDays}
                  selectDates={selectDates}
                  year={new Date().getFullYear()}
                />
                <BoxSelect
                  // 可框選區域
                  sourceClassName='work-calendar'
                  // 可框選元素的dom選擇器,
                  selectors='td.date[data-date]'
                  // 框選元素改變時的回調,可以拿到框選中元素
                  onSelectChange={(selectDoms) => {
                    // 內部給td元素上設置了data-date屬性,這樣就可以從dom元素上拿到日期
                    setSelectDates(selectDoms.map(dom => dom.getAttribute('data-date') as string))
                  }}
                  // 框選結束事件
                  onSelectEnd={() => {
                    // 如果有框選就彈出設置彈框
                    if (selectDatesRef.current.length) {
                      setOpen(true)
                    }
                  }}
                />
                <Modal
                  title="設置日期類型"
                  open={open}
                  onCancel={() => {
                    setOpen(false);
                    setSelectDates([]);
                    setDateType(null);
                  }}
                  onOk={() => {
                    setOpen(false);
                    selectDatesRef.current.forEach(date => {
                      setDates((prev: any) => ({
                        ...prev,
                        [date]: dateType,
                      }))
                    })
                    setSelectDates([]);
                    setDateType(null);
                  }}
                >
                  <Radio.Group
                    options={[
                      { label: '工作日', value: 1 },
                      { label: '休息日', value: 2 },
                      { label: '節假日', value: 3 },
                    ]}
                    value={dateType}
                    onChange={e => setDateType(e.target.value)}
                  />
                </Modal>
              </div>
            )
          }
          
          export default App
          
          


          工作日歷改造

          給td的class里加了個date,并且給元素上加了個data-date屬性

          如果被框選,改變一下背景色

          效果展示

          小結

          本來想給mousemove加節流函數,防止觸發太頻繁影響性能,后面發現不加節流很流暢,加了節流后因為延遲,反而不流暢了,后面如果有性能問題,再優化吧。


          作者:前端小付
          鏈接:https://juejin.cn/post/7308948738659155983


          主站蜘蛛池模板: 波多野结衣AV一区二区三区中文| 精品人妻一区二区三区四区| 国产大秀视频一区二区三区| 天天看高清无码一区二区三区| 精品午夜福利无人区乱码一区| 少妇精品久久久一区二区三区| 国产成人精品一区二区三区免费| 99精品国产一区二区三区不卡| 精品三级AV无码一区| 亚洲一区二区三区高清不卡| 国产精品无码一区二区三区毛片| 四虎在线观看一区二区| eeuss鲁片一区二区三区| 国产一区二区三区在线电影 | 国产午夜精品一区二区三区小说 | 国产精品香蕉在线一区| 无码中文字幕人妻在线一区二区三区| 国产欧美色一区二区三区| 日韩伦理一区二区| 日韩精品无码一区二区三区免费| 无码精品不卡一区二区三区| 国产精品乱码一区二区三区| 午夜影院一区二区| 国产丝袜一区二区三区在线观看| 人妻少妇AV无码一区二区| 亚洲中文字幕一区精品自拍 | 伊人久久一区二区三区无码| 一区二区三区伦理高清| 欲色aV无码一区二区人妻| 久久国产一区二区| 国产福利一区二区在线视频 | 99精品一区二区免费视频| 国产在线观看一区二区三区四区 | 亚洲av无码一区二区三区网站 | 久久中文字幕无码一区二区| 亚洲熟女一区二区三区| 国产精品一区二区综合| 国产精品视频免费一区二区| 亚洲一区二区三区久久| 免费日本一区二区| 日本一区二区不卡在线|