怎么用React擼一個(gè)日程組件-創(chuàng)新互聯(lián)

本篇內(nèi)容主要講解“怎么用React擼一個(gè)日程組件”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“怎么用React擼一個(gè)日程組件”吧!

為企業(yè)提供成都網(wǎng)站制作、成都網(wǎng)站建設(shè)、外貿(mào)營(yíng)銷網(wǎng)站建設(shè)、網(wǎng)站優(yōu)化、成都全網(wǎng)營(yíng)銷、競(jìng)價(jià)托管、品牌運(yùn)營(yíng)等營(yíng)銷獲客服務(wù)。成都創(chuàng)新互聯(lián)擁有網(wǎng)絡(luò)營(yíng)銷運(yùn)營(yíng)團(tuán)隊(duì),以豐富的互聯(lián)網(wǎng)營(yíng)銷經(jīng)驗(yàn)助力企業(yè)精準(zhǔn)獲客,真正落地解決中小企業(yè)營(yíng)銷獲客難題,做到“讓獲客更簡(jiǎn)單”。自創(chuàng)立至今,成功用技術(shù)實(shí)力解決了企業(yè)“網(wǎng)站建設(shè)、網(wǎng)絡(luò)品牌塑造、網(wǎng)絡(luò)營(yíng)銷”三大難題,同時(shí)降低了營(yíng)銷成本,提高了有效客戶轉(zhuǎn)化率,獲得了眾多企業(yè)客戶的高度認(rèn)可!

目錄結(jié)構(gòu)

└─Calendar
    │  data.d.ts  類型定義文件
    │  index.tsx  入口文件
    │
    ├─components
    │  ├─CalendatrHeader 頭部容器組件
    │  │  │  index.less
    │  │  │  index.tsx
    │  │  │
    │  │  └─components
    │  │      ├─DailyOptions 頂部切換日期和切換模式狀態(tài)組件
    │  │      │      index.less
    │  │      │      index.tsx
    │  │      │
    │  │      └─WeeklyOptions 周模式日期和星期組件
    │  │              index.less
    │  │              index.tsx
    │  │
    │  ├─Container 容器組件
    │  │      Container.tsx
    │  │      index.less
    │  │
    │  ├─ScheduleCantainer 下半部日程容器
    │  │      index.less
    │  │      index.tsx
    │  │
    │  └─ScheduleItem 灰色部分每一條日程組件
    │          index.less
    │          index.tsx
    │
    └─utils
            index.ts 工具文件

?拆分組件

仔細(xì)看圖, 不難看出, 組件大塊上我拆分成了三個(gè)部分:
Container容器: 該組件是整個(gè)組件的容器, 負(fù)責(zé)UI核心狀態(tài)數(shù)據(jù), 維護(hù)兩個(gè)狀態(tài):

  1. targetDay: 當(dāng)前選中日期時(shí)間戳(為什么選用時(shí)間戳后續(xù)解釋);

  2. switchWeekAndDay: 保存日和周的狀態(tài);

CalendatrHeader頭部容器組件: Container容器的子組件, 該組件負(fù)責(zé)切換日期, 改變組件周和日的狀態(tài); 該組件內(nèi), 包含日歷組件, 星期組件, 日期篩選組件, 日和周切換組件, 今天按鈕組件, 最后還有一個(gè)業(yè)務(wù)組件的容器(businessRender);

ScheduleCantainer日程容器組件: 該組件被25 (因?yàn)槭菑慕裉?點(diǎn)到次日凌晨0點(diǎn)的區(qū)間) 個(gè)scheduleRender組件撐開, 子組件還包括時(shí)間刻度組件;

scheduleRender: 特意說一下這個(gè)組件, 這個(gè)組件接受一個(gè)回調(diào), 回調(diào)會(huì)返回一個(gè)JSX, 這個(gè)JSX就是調(diào)用者傳入的自定義樣式的日程組件(具體內(nèi)容在后文講吧);

這就是大致的組件拆分, 文字表達(dá)確實(shí)欠佳, 可以結(jié)合圖片YY;

接下來就開干吧!!!

代碼實(shí)現(xiàn)

先看一下接受的參數(shù)類型定義:

type dataType = {
  startTime: DOMTimeStamp; // 開始時(shí)間戳
  endTime: DOMTimeStamp; // 結(jié)束時(shí)間戳
  [propsName: string]: any; // 業(yè)務(wù)數(shù)據(jù)
};

type ContainerType = {
  data: dataType[]; // 業(yè)務(wù)數(shù)據(jù)
  initDay?: DOMTimeStamp; // 初始化時(shí)間戳
  onChange?: (params: DOMTimeStamp) => void; // 改變?nèi)掌跁r(shí)的onChange方法
  height?: number; // ScheduleCantainer容器的高度
  scheduleRender?: ({
    data: dataType,
    timestampRange: [DOMTimeStamp, DOMTimeStamp],
  }) => JSX.Element; // 傳入的回調(diào), 會(huì)接收到當(dāng)前這條數(shù)據(jù)的業(yè)務(wù)數(shù)據(jù), 當(dāng)前業(yè)務(wù)數(shù)據(jù)所在的時(shí)間戳范圍;
  businessRender?: ({ timestamp: DOMTimeStamp }) => React.ReactNode; // 傳入的業(yè)務(wù)組件, 查詢前端蔡徐坤那個(gè), 看圖, 想起來了嗎?
  mode?: "day" | "week"; // 初始化展示日和天的模式
};

Container容器組件

代碼:

const Container: React.FC<ContainerType> = ({
  initDay,
  onChange,
  scheduleRender,
  businessRender,
  data,
  height = 560,
  mode = "day",
}) => {
  // 當(dāng)前選擇日期時(shí)間戳
  const [targetDay, setTargetDay] = useState<DOMTimeStamp>(initDay);
  // 切換日和周
  const [switchWeekandDay, setSwitchWeekandDay] = useState<"day" | "week">(mode);

  return (
    <div className={style.Calendar_Container}>
      <CalendatrHeader
        targetDay={targetDay}
        setTargetDay={(timestamp) => {
          onChange(timestamp);
          setTargetDay(timestamp);
        }}
        businessRender={businessRender}
        switchWeekandDay={switchWeekandDay}
        setSwitchWeekandDay={setSwitchWeekandDay}
      />
      <ScheduleCantainer
        height={height}
        data={data}
        targetDay={targetDay}
        scheduleRender={scheduleRender}
      />
    </div>
  );
};

看代碼可以思考一下, 肯定要將全局的狀態(tài)數(shù)據(jù)提升到最高層級(jí)去控制, 也符合React的組件設(shè)計(jì)哲學(xué);

維護(hù)了當(dāng)前時(shí)間戳和日/周的狀態(tài), 所有子組件的狀態(tài)都是根據(jù)targetDay去展示的;

CalendatrHeader頭部容器組件

頭部容器我覺得其他的還好, 由于星期是寫死的(主要是參考了一下蘋果的那個(gè)日程組件, 蘋果的星期就沒換, 所以參考大廠優(yōu)秀的設(shè)計(jì)), 所以比較敲腦殼的就是如何能準(zhǔn)確的展示一周的日期;

其實(shí)展示一周的日期我寫了兩種方式:

第一種是以當(dāng)前的日期的星期為基準(zhǔn), 分別向前和向后去計(jì)算, 最后輸出一個(gè)[29, 30, 31, 1, 2, 3, 4]這樣的List, 如果恰巧今天是1號(hào) 或者 2號(hào), 就去拉去上個(gè)月最后一天的日期往前遞減;

第二種方式就是下面代碼的方式, 也是拿到當(dāng)前日期的星期去定位, 通過時(shí)間戳去動(dòng)態(tài)計(jì)算出來, 只要知道往前減幾天, 往后追加幾天就好了;

其實(shí)兩種方式都可以, 最后我用了第二種, 顯然第二種更加簡(jiǎn)潔;

如下圖:

怎么用React擼一個(gè)日程組件

 當(dāng)前一周就要輸出[12, 13, 14, 15, 16, 17, 18]

下面是上述難點(diǎn)具體實(shí)現(xiàn)的代碼:

const calcWeekDayList: (params: number) => WeekType = (params) => {
    const result = [];
    for (let i = 1; i < weekDay(params); i++) {
      result.unshift(params - 3600 * 1000 * 24 * i);
    }
    for (let i = 0; i < 7 - weekDay(params) + 1; i++) {
      result.push(params + 3600 * 1000 * 24 * i);
    }
    return [...result] as WeekType;
  };

代碼:

const CalendatrHeader: React.FC<CalendatrHeaderType> = ({
  targetDay,
  setTargetDay,
  switchWeekandDay,
  businessRender,
  setSwitchWeekandDay,
}) => {
  // 當(dāng)前一周的日期
  const [dateTextList, setDateTextList] = useState<WeekType | []>([]);
  // 這個(gè)狀態(tài)是在切換周的時(shí)候, 直接增加或者減少一周的時(shí)間戳, 下一周或者上一周的日期就會(huì)被自動(dòng)算出來;
  const [currTime, setCurrTime] = useState<number>(targetDay); 

  useEffect(() => {
    setDateTextList(calcWeekDayList(targetDay));
  }, [targetDay]);

  // 根據(jù)當(dāng)前時(shí)間戳, 計(jì)算之前和之后天數(shù)的日期, 由于星期是固定不變的, 所以只計(jì)算當(dāng)前一周的日期就好了
  const calcWeekDayList: (params: number) => WeekType = (params) => {
    const result = [];
    for (let i = 1; i < weekDay(params); i++) {
      result.unshift(params - 3600 * 1000 * 24 * i);
    }
    for (let i = 0; i < 7 - weekDay(params) + 1; i++) {
      result.push(params + 3600 * 1000 * 24 * i);
    }
    return [...result] as WeekType;
  };

  const onChangeWeek: (type: "prevWeek" | "nextWeek", switchWay: "week" | "day") => void = (
    type,
    switchWay,
  ) => {
    if (switchWay === "week") {
      const calcWeekTime =
        type === "prevWeek" ? currTime - 3600 * 1000 * 24 * 7 : currTime + 3600 * 1000 * 24 * 7;
      setCurrTime(calcWeekTime);
      setDateTextList([...calcWeekDayList(calcWeekTime)]);
    }

    if (switchWay === "day") {
      const calcWeekTime =
        type === "prevWeek" ? targetDay - 3600 * 1000 * 24 : targetDay + 3600 * 1000 * 24;
      setCurrTime(calcWeekTime);
      setTargetDay(calcWeekTime);
    }
  };

  return (
    <div className={style.Calendar_Header}>
      <DailyOptions
        targetDay={targetDay}
        setCurrTime={setCurrTime}
        setTargetDay={setTargetDay}
        dateTextList={dateTextList}
        switchWeekandDay={switchWeekandDay}
        setSwitchWeekandDay={(value) => {
          setSwitchWeekandDay(value);
          if (value === "week") {
            setDateTextList(calcWeekDayList(targetDay));
          }
        }}
        onChangeWeek={(type) => onChangeWeek(type, switchWeekandDay)}
      />
      {switchWeekandDay === "week" && (
        <WeeklyOptions
          targetDay={targetDay}
          setTargetDay={setTargetDay}
          dateTextList={dateTextList}
        />
      )}
      <div className={style.Calendar_Header_businessRender}>
        <div className={style.Calendar_Header_Zone}>GMT+8</div>
        {businessRender({ timestamp: targetDay })}
      </div>
    </div>
  );
};

DailyOptions : 其實(shí)就是頭部切換"一周日期" & "日和周模式" & "今天"的組件的容器;

WeeklyOptions : 這個(gè)是下面展示當(dāng)前一周星期幾和日期的組件, 如果切換為day的話不展示

businessRender: 這個(gè)就是肖戰(zhàn)哥哥那一欄用戶傳入的業(yè)務(wù)組件;

ScheduleCantainer詳細(xì)日程容器

左側(cè)刻度

左側(cè)刻度其實(shí)就是寫死的 從00:00 - 01:00 ---> 23:00 - 00:00, 但是在寫的時(shí)候有一個(gè)小的問題, 就是這個(gè)組件是浮動(dòng)到左側(cè)的, 而且他要隨著右側(cè)條目的滾動(dòng)而滾動(dòng), 其實(shí)一開始我寫到一個(gè)盒子里了, 滾動(dòng)容器整體就一起滾動(dòng)了, 但是遇到了一個(gè)小問題, 由于右側(cè)條目會(huì)變得超寬, 就會(huì)出現(xiàn)橫向滾動(dòng)條, 如果橫滾整個(gè)容器的話, 左側(cè)的時(shí)間刻度就會(huì)被滾動(dòng)出可視區(qū)域.

所以還是絕對(duì)定位之后, 監(jiān)聽右側(cè)日程條目的滾動(dòng)事件, 動(dòng)態(tài)的改變左側(cè)的style的top值, 反向賦值就好了, 由于是向下滾動(dòng)的, 所以左側(cè)的時(shí)間刻度需要向上滾動(dòng), 所以top值取反就會(huì)達(dá)到同步的效果; 真是個(gè)小機(jī)靈鬼吧, 嘿嘿; 這個(gè)代碼就不占用篇幅了, 大家自由發(fā)揮吧, 如果有更好的方式, 歡迎評(píng)論區(qū)留言.

ScheduleItem日程容器條目

先看下這個(gè)組件的代碼:

const ScheduleItem: React.FC<ScheduleItemType> = ({
  timestampRange,
  dataItem,
  scheduleRender,
  width,
  dataItemLength,
}) => {
  // 計(jì)算容器高度
  const calcHeight: (timestampList: [number, number]) => number = (timestampList) =>
    timestampList.length > 1 ? (timestampList[1] - timestampList[0]) / 1000 / 60 / 2 : 30;
  const calcTop: (startTime: number) => number = (startTime) => moment(startTime).minute() / 2;
  // 計(jì)算 ScheduleItem 寬度
  const calcWidth: (w: number, d: number) => string = (w, d) =>
    width === 0 || dataItemLength * width < 347 ? "100%" : `${d * w}px`;

  return (
    <div style={{ position: "relative" }} className={style.Calendar_ScheduleItem_Fath}>
      <div
        className={style.Calendar_ScheduleItem}
        style={{ width: calcWidth(width, dataItemLength) }}
      >
        {dataItem.map((data, index) => {
          return (
            <Fragment key={index}>
              {data.startTime >= timestampRange[0] && data.startTime < timestampRange[1] && (
                <div
                  className={`${style.Calendar_ScheduleItem_container} Calendar_ScheduleItem_container`}
                  style={{
                    height: `${calcHeight([data.startTime, data.endTime]) || 30}px`,
                    top: calcTop(data.startTime),
                  }}
                >
                  {scheduleRender({ data, timestampRange })}
                </div>
              )}
            </Fragment>
          );
        })}
      </div>
    </div>
  );
};

這一部分呢(就是下面灰色一條一條的部分), 為什么要單獨(dú)出一個(gè)組件呢? 可以先思考一下......

好了, 不賣關(guān)子了, 其實(shí)就是為了好定位用戶的日程數(shù)據(jù), 例如今天的10:00 -- 11:00, 定位到哪里的問題.

還記得這個(gè)API嗎?

scheduleRender?: ({
    data: dataType,
    timestampRange: [DOMTimeStamp, DOMTimeStamp],
  }) => JSX.Element;

這個(gè)組件內(nèi)會(huì)有[DOMTimeStamp, DOMTimeStamp] 這樣的一個(gè)參數(shù)(DOMTimeStamp時(shí)間戳的意思), 這兩個(gè)時(shí)間戳其實(shí)就是當(dāng)前時(shí)段的 10:00 -- 11:00 的其實(shí)和截至?xí)r間戳, 由于我們接受的startTime和endTime也是時(shí)間戳, 通過比較大小是否在這個(gè)范圍, 就可以控制展示和隱藏, 這回明白為什么采用時(shí)間戳了吧, 直接比較數(shù)字大小就好了;

我們?cè)僬f一下這個(gè)東東的樣式問題:

其實(shí)這個(gè)東東我我寫死了30px, 原因呢就是因?yàn)橐恍r(shí)是60分鐘, 如果60px的話太高了, 所以寫了30px, 方便定位嘛, 畢竟我懶, 不想太復(fù)雜的計(jì)算;

所以定位計(jì)算也就一行代碼: const calcTop: (startTime: number) => number = (startTime) => moment(startTime).minute() / 2; 高度定位問題結(jié)了! 哈哈~~

接下來呢, 還有一個(gè)問題就是高度問題, 如圖:

怎么用React擼一個(gè)日程組件

高度計(jì)算其實(shí)也不難, 主要根據(jù)當(dāng)前起止時(shí)間的區(qū)間范圍去計(jì)算( 1px 兩分鐘 ), 具體實(shí)現(xiàn)看代碼:

const calcHeight: (timestampList: [number, number]) => number = (timestampList) =>
    timestampList.length > 1 ? (timestampList[1] - timestampList[0]) / 1000 / 60 / 2 : 30;

首先會(huì)判斷入?yún)⒌臅r(shí)間戳是不是只有一個(gè)時(shí)間, 如果只有開始時(shí)間, 沒有結(jié)束時(shí)間, 寫死30px, 如果有起止時(shí)間, 就去轉(zhuǎn)成分鐘動(dòng)態(tài)計(jì)算一下;

最后還有一個(gè)問題, 業(yè)務(wù)數(shù)據(jù)是怎么傳進(jìn)去是如何渲染到組件的:

先看一下我們傳入data字段的JSON:

[
  {
    startTime: 1626057075000, // 開始時(shí)間
    endTime: 1626070875000, // 結(jié)束時(shí)間
    value: "any", // 業(yè)務(wù)數(shù)據(jù)
  },
  {
    startTime: 1626057075000,
    endTime: 1626070875000,
    value: "any",
  },
  {
    startTime: 1626057075000,
    endTime: 1626070875000,
    value: "any",
  },
  {
    startTime: 1626057075000,
    endTime: 1626070875000,
    value: "any",
  },
];

其實(shí)我們?cè)谘h(huán)渲染這個(gè)ScheduleItem組件的時(shí)候, 用那個(gè)寫死的24h的list去循環(huán), 之后, 循環(huán)的時(shí)候, 動(dòng)態(tài)的去業(yè)務(wù)數(shù)據(jù)中去查找符合當(dāng)次循環(huán)的時(shí)間范圍內(nèi)的業(yè)務(wù)數(shù)據(jù), 把這個(gè)數(shù)據(jù)塞到組件內(nèi); 大致代碼如下:

for (let i = 0; i < HoursList.length; i++) {
      resule.push({
        timestampRange: [todayTime + i * 3600 * 1000, todayTime + (i + 1) * 3600 * 1000],
        dataItem: [ // 由于當(dāng)前一個(gè)時(shí)間段, 日程可能沖突, 所以要有一個(gè)list傳入組件
          ...data.filter((item) => {
            return (
              item.startTime >= todayTime + i * 3600 * 1000 &&
              item.startTime < todayTime + (i + 1) * 3600 * 1000
            );
          }),
        ],
      });
    }

到此,相信大家對(duì)“怎么用React擼一個(gè)日程組件”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

新聞標(biāo)題:怎么用React擼一個(gè)日程組件-創(chuàng)新互聯(lián)
當(dāng)前URL:http://bm7419.com/article36/iijsg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、外貿(mào)建站、云服務(wù)器企業(yè)網(wǎng)站制作品牌網(wǎng)站設(shè)計(jì)、Google

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司