記錄-Vue移動端日曆設計與實現

来源:https://www.cnblogs.com/smileZAZ/archive/2023/04/26/17356880.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 工作中遇到一個需求是根據日曆查看某一天/某一周/某一月的睡眠報告,但是找了好多日曆組件都不是很符合需求,只好自己手寫一個日曆組件,順便記錄一下。 先看看UI給的設計圖和,需求是有數據的日期做標記,可以查看某一周/某一月的數據,周數據不用自 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

工作中遇到一個需求是根據日曆查看某一天/某一周/某一月的睡眠報告,但是找了好多日曆組件都不是很符合需求,只好自己手寫一個日曆組件,順便記錄一下。

先看看UI給的設計圖和,需求是有數據的日期做標記,可以查看某一周/某一月的數據,周數據不用自定義,就按照日曆上的周數據截取.

實現效果

1.規劃dom部分區塊劃分

2.頁面實現

選擇月份和選擇年份與日期做了條件渲染,切換方式是點擊頂部時間切換選項

<template>
  <div class="calendar">
    <div class="date-top">
      <div class="left" @click="dateOperate('down')">
        <div></div>
      </div>
      <div class="time" @click="selectDate">{{ date.join("/") }}</div>
      <div class="right" @click="dateOperate('up')">
        <div></div>
      </div>
    </div>
    <!-- 日期列表 -->
    <div class="date-list" v-if="show === 'date'">
      <div class="date-content">
        <!-- 日曆頭 -->
        <div v-for="item in header" :key="item">
          {{ item }}
        </div>
        <!-- 日列表 -->
        <div
          v-for="(s, k) in dayList"
          :class="[
            'date-item',
            s.month !== date[1] ? 'other-day' : '',
            s.day === date[2] && s.month === date[1] ? 'today' : '',
          ]"
          :key="s + '-' + k"
          @click="selectDay(s)"
        >
          {{ s.day }}
          <div
            :class="[
              'check',
              haveList.includes(`${s.year}-${s.month}-${s.day}`) ? 'have' : '',
            ]"
          ></div>
        </div>
      </div>
      <!-- 操作欄 -->
      <div class="date-btn">
        <div
          class="btn-item"
          v-for="k in weeks + 1"
          :key="k"
          @click="weekReport(k)"
        >
          {{ k === 1 ? "" : "看周報" }}
        </div>
      </div>
    </div>
    <!-- 月份列表 -->
    <div class="month-list" v-else-if="show === 'month'">
      <div
        :class="date[1] == i ? 'month-item active' : 'month-item'"
        v-for="i in 12"
        :key="i"
        @click="selectMonth(i)"
      >
        {{ i }}月
      </div>
    </div>
    <!-- 年份列表 -->
    <div
      class="year-list"
      v-else
      @touchmove="touchMove"
      @touchstart="touchStart"
    >
      <div
        :class="date[0] === i ? 'month-item active' : 'month-item'"
        v-for="i in yearList"
        :key="i"
        @click="selectYear(i)"
      >
        {{ i }}
      </div>
    </div>
    <!-- 底部操作欄 -->
    <div class="date-bottom">
      <div class="b-left">
        <div class="tab"></div>
        <div class="totip">代表有睡眠報告</div>
      </div>
      <div class="b-right">
        <div class="cancel" @click="cancel">取消</div>
        <div class="m-report" @click="changeReport">看月報</div>
      </div>
    </div>
  </div>
</template>

css部分

<style lang="scss" scoped>
.calendar {
  width: 100%;
  background-color: #fff;

  .date-top {
    width: 100%;
    padding: 20px;
    display: flex;
    justify-content: space-around;
    align-items: center;
    .left,
    .right {
      width: 100px;
      height: 100%;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      div {
        width: 20px;
        height: 20px;
        background-color: #00b7ae;
      }
    }
    .left > div {
      clip-path: polygon(0% 50%, 100% 0%, 100% 100%);
    }

    .right > div {
      clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
    }

    .time {
      font-size: 38px;
      font-weight: 500;
      color: #333333;
    }
  }
  .date-list,
  .year-list,
  .month-list {
    width: 100%;
    padding: 30px;
    height: 540px;
  }
  .month-list,
  .year-list {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-rows: auto;
    .month-item {
      text-align: center;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 30px;
      height: 122px;
    }
    .month-item:active {
      background-color: #eee;
    }
    .active {
      background-color: #dcf4f3;
    }
  }
  .date-list {
    padding-top: 0;
    display: flex;
    .date-content {
      flex: 1;
      height: 100%;
      display: grid;
      grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
      grid-template-rows: auto;
      grid-gap: 20px 20px;
      div {
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        border-radius: 10px;
      }
      .other-day {
        color: rgba($color: #363636, $alpha: 0.6) !important;
      }
      .today {
        background-color: #dcf4f3;
      }
      .date-item {
        font-size: 28px;
        font-weight: 400;
        color: #363636;
        .check {
          width: 10px;
          height: 10px;
          margin-top: 6px;
          border-radius: 50%;
          background-color: #00b7ae;
          opacity: 0;
        }
        .have {
          opacity: 1;
        }
      }
    }
    .date-btn {
      height: 100%;
      width: 80px;
      font-size: 22px;
      color: #4eb9f5;
      display: grid;
      grid-template-columns: 1fr;
      grid-template-rows: auto;
      grid-gap: 20px 20px;
      margin-left: 20px;
      .btn-item {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
      }
    }
  }

  .date-bottom {
    width: calc(100% - 80px);
    display: flex;
    justify-content: space-between;
    align-content: center;
    padding: 20px;
    margin: 0 auto;
    border-top: 1px solid #eee;
    .b-left,
    .b-right {
      display: flex;
      align-items: center;
    }
    .b-left {
      .tab {
        width: 27px;
        height: 26px;
        background: #dcf4f3;
        border-radius: 13px;
      }
      .totip {
        font-size: 24px;
        font-weight: 400;
        color: #363636;
        margin-left: 20px;
      }
    }
    .b-right {
      .cancel {
        font-size: 26px;
        font-weight: 500;
        color: rgba($color: #000000, $alpha: 0.5);
      }
      .m-report {
        width: 195px;
        line-height: 70px;
        color: #fff;
        font-size: 26px;
        background: linear-gradient(196deg, #50dcdc, #18b6b7);
        border-radius: 20px;
        margin-left: 50px;
        text-align: center;
      }
    }
  }
}
</style>

3.接下來是邏輯處理部分

日數據的顯示一共42條數據,先獲取當前月的總天數,將每個月的天數保存在一個數組裡,然後根據傳入的參數返回相應的天數, 因為有閏年的存在,2月會是29天,所以做了閏年的判斷.然後獲取每周的第一天是周幾,使用new Date().getDay()獲取某一天是周幾,返回的是0-7,這裡為了方便使用將日曆表頭用數組保存起來返回的數字剛好是日裡頭對應的下標,然後根據第一天是周幾計算出需要補上個月的幾天數據,通過new Date(y,m,0)可以獲取到上個月最後一天的,然後嚮日數據中添加上個月最後幾天的數據,補充下個月開始的幾天數據,直接使用42減去當月的天數和補充的上個月的天數得到的就是需要補充的下月天數.

月數據的切換顯示,前後翻動切換年數據,每年固定都是12月所以就直接寫固定值12然後v-for遍歷生成dom

年數據的切換顯示,每頁顯示12條數據,保存每頁數據的第一條和最後一條用於前後翻頁計算顯示的數據+12或者-12.

校驗選擇的月份和已選擇的日期是否匹配,因為選擇日期後再切換月份有可能切換到的月份沒有選擇的日期如31日30日29日,所以需要驗證是否正確,若是沒有的話就當前月的最後一天.

手勢操作沒有寫完整,只寫了年份選擇的滑動事件邏輯.

為了方便js部分的代碼每行都有寫詳細的註釋

自定月選擇日期範圍只需要修改日期點擊事件的邏輯,新增一個參數判斷是單日期選擇還是選擇一個日期範圍,在事件處理裡面記錄點擊的兩個日期並計算中間的日期保存返回.

<script>
import { formatTime } from "@/utils/format";
export default {
  name: "calendar",
  props: {
    haveList: {
      type: Array,
      default: [],
    },
  },
  data() {
    return {
      // 切換日期選擇
      show: "date",
      // 日曆頭
      header: ["日", "一", "二", "三", "四", "五", "六"],
      // 選擇日期
      date: [],
      // 年列表
      yearList: [],
      // 天列表
      dayList: [],
      // 定時器
      timer: null,
      // 手勢操作數據
      move: {
        pageX: 0,
        fNum: null,
        lNum: null,
      },
      // 第一天是周幾
      weeks: 0,
    };
  },
  created() {},
  mounted() {
    let time = new Date();
    this.date.push(
      time.getFullYear(),
      formatTime(time.getMonth() + 1),
      formatTime(time.getDate())
    );
    this.countDay();
  },
  methods: {
    // 計算顯示的天數據
    countDay() {
      console.log("chufa");
      let [y, m, d] = this.date;
      // 獲取第一天是周幾
      let week = new Date(`${y}/${m}/1`).getDay(),
        // 獲取當前月的上個月多少天
        lastDays = this.getDays(y, m - 1),
        // 獲取這個月有多少天
        days = this.getDays(y, m);
      // 計算這個月有多少周
      this.weeks = Math.ceil((days - (7 - week)) / 7) + 1;
      // 將當前月份的天數生成數組
      this.dayList = Array.from({ length: this.getDays(y, m) }, (v, k) => {
        return {
          day: formatTime(k + 1),
          month: m,
          year: y,
        };
      });
      // 將本月1日前的數據補齊
      for (let i = lastDays; i > lastDays - week; i--) {
        this.dayList.unshift({
          day: i,
          // 如果當前日期是1月補齊的是去年12月的數據
          month: +m - 1 === 0 ? 12 : formatTime(+m - 1),
          year: +m - 1 === 0 ? y - 1 : y,
        });
      }
      // 計算需要補齊多少天
      let length = this.weeks * 7 - this.dayList.length;
      console.log("length", week, lastDays, days, this.weeks);
      // 將本月最後一天的數據補齊
      for (let i = 1; i <= length; i++) {
        this.dayList.push({
          day: i,
          // 如果當前日期是12月補齊的是明年年1月的數據
          month: +m + 1 > 12 ? 1 : formatTime(+m + 1),
          year: +m + 1 > 12 ? y + 1 : y,
        });
      }

      console.log(this.dayList);
    },
    // 頂部時間點擊事件
    selectDate() {
      let type = {
        month: "year",
        date: "month",
      };
      // 判斷點擊事件選擇月份還是年份
      if (this.show !== "year") {
        this.show = type[this.show];
      }
      // 如果是月份就計算dateList數據
      if (this.show === "month") {
        // 清空每頁顯示的年份數據
        this.yearList.length = 0;
        // 計算頁面顯示的年份數據 每頁顯示12條數據
        for (let i = this.date[0] - 4; i <= this.date[0] + 7; i++) {
          this.yearList.push(i);
        }
      }
    },
    // 屏幕點擊事件
    touchStart(val) {
      // 獲取按下屏幕的x軸坐標
      this.move.pageX = val.touches[0].pageX;
    },
    // 左右滑動切換事件
    touchMove(val) {
      // 獲取按下屏幕移動結束的x軸坐標
      let move = val.touches[0].pageX;
      clearTimeout(this.timer);
      // 判斷往左滑動還是往右滑動
      // 滑動結束x軸坐標減去最初按下坐標為負數就是往左滑動,翻看當前日期以後的年份
      if (move - this.move.pageX < -20) {
        console.log("右滑", this.move.lNum);
        // 定時器防抖
        this.timer = setTimeout(this.changeYear("right"), 100);
      }
      // 滑動結束x軸坐標減去最初按下坐標為正數就是往右滑動,翻看當前日期以前的年份
      if (move - this.move.pageX > 20) {
        // 定時器防抖
        this.timer = setTimeout(this.changeYear("left"), 100);
      }
    },
    // 年份選擇切換
    changeYear(type) {
      // 清空每頁顯示的年份數據
      this.yearList.length = 0;
      if (type === "right") {
        // 計算頁面顯示的年份數據 每頁顯示12條數據
        for (let i = this.move.lNum + 1; i < this.move.lNum + 13; i++) {
          this.yearList.push(i);
        }
      } else {
        for (let i = this.move.fNum - 12; i < this.move.fNum; i++) {
          this.yearList.push(i);
        }
      }
    },
    // 年份點擊事件
    selectYear(val) {
      this.date[0] = val;
      this.show = "month";
    },
    // 月份點擊事件
    selectMonth(val) {
      this.date[1] = val;
      this.show = "date";
      this.countDay();
      this.checkDay();
    },
    // 校驗選擇的月份和已選擇的日期是否匹配
    checkDay() {
      // 獲取選擇的年月有多少天 防止這年不是閏年 就將日期跳轉到28號,或者有的月份沒有31號就跳到30號
      let num = this.getDays(this.date[0], this.date[1]);
      if (num < this.date[2]) {
        this.date.splice(2, 1, num);
      }
    },
    // 日期點擊事件
    selectDay(val) {
      let oVal = this.date[1];
      this.date.splice(1, 2, val.month, val.day);
      if (val.month !== oVal) {
        this.countDay();
      }
      this.$emit("change", this.date.join("-"));
    },
    // 獲取某個月有多少天
    getDays(year, month) {
      // 一年中每個月的天數
      let days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
      // 判斷是不是閏年 2月29天
      if (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)) {
        days[1] = 29;
      }
      return days[month - 1];
    },
    //左右按鈕點擊事件
    dateOperate(type) {
      let [y, m, d] = this.date;
      // 如果是向後翻
      if (type === "up") {
        // 日期向後翻 切換月份
        if (this.show === "date") {
          if (+m === 12) {
            this.date.splice(0, 1, y + 1);
            this.date.splice(1, 1, "01");
          } else {
            this.date.splice(1, 1, formatTime(+m + 1));
          }
          // 月份向後翻 切換年份
        } else if (this.show === "month") {
          this.date.splice(0, 1, y + 1);
          // 年份向後翻 重組數據
        } else {
          this.changeYear("right");
        }

        // 如果是前後翻
      } else {
        // 日期向前翻 切換月份
        if (this.show === "date") {
          if (+m === 1) {
            this.date.splice(0, 1, y - 1);
            this.date.splice(1, 1, 12);
          } else {
            this.date.splice(1, 1, formatTime(+m - 1));
          }
          // 月份向前翻 切換年份
        } else if (this.show === "month") {
          this.date.splice(0, 1, y - 1);
          // 年份向前翻 重組數據
        } else {
          this.changeYear("left");
        }
      }
      this.countDay();
      this.checkDay();
    },
    // 右側按鈕點擊事件
    weekReport(i) {
      if (i === 1) return;
      let arr = [],
        // 選擇一周的數據 開始
        s = 7 * (i - 1) - 7,
        // 結束
        e = 7 * (i - 1);
      // 遍歷日數據 截取選擇的周數據
      for (let k = s; k < e; k++) {
        arr.push(
          `${this.dayList[k].year}-${this.dayList[k].month}-${this.dayList[k].day}`
        );
      }
      this.$emit("weekReport", arr);
    },

    // 看月報事件
    changeReport() {
      let [y, m, d] = this.date;
      this.$emit("changeReport", `${y}-${m}`);
    },
    // 取消事件
    cancel() {
      this.$emit("cancel");
    },
  },
  computed: {},
  watch: {
    yearList(nVal, oVal) {
      // 記錄每一頁顯示的數據第一位和最後一位 用於計算下一頁或者上一頁的數據
      this.move.fNum = nVal[0];
      this.move.lNum = nVal[11];
    },
    deep: true,
    immediate: true,
  },
};
</script>

formatTime是給月份和日期小於10的前面加0的方法

本文轉載於:

https://juejin.cn/post/7218048201981853757

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Oracle的參數sec_case_sensitive_logon是Oracle 11g開始被引入。這個參數主要是為了控制密碼的大小寫敏感問題。 sec_case_sensitive_logon=true表示密碼區分大小寫。 sec_case_sensitive_logon=false表示密碼不區分 ...
  • 表相關 普通表 查詢普通表是否存在可以使用object_id函數,下麵的例子是查詢表“t_test”是否存在之後從而進行其他的DLL操作: if object_id('t_test') is not null begin -- 如果表存在 這段裡面寫相關邏輯 select 1 end 臨時表 臨時表 ...
  • GreatSQL社區原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。 GreatSQL是MySQL的國產分支版本,使用上與MySQL一致。 作者:dan 文章來源:GreatSQL社區原創 由於MySQL 源碼編譯單機耗費的時間過於長,最近MySQL 變成8.0.27 以後編譯時間明顯更耗時 ...
  • jar包下載方式官網地址:MySQL :: Download Connector/J,如果你打不開官網,在下麵我為你準備了直接下載jar包的鏈接地址。 在選擇操作系統時,此處選擇platform independent(獨立於平臺)。 8.0版本的jar包下載地址,點擊直接下載。 https://d ...
  • 一. 問題描述 創建地圖對象,並添加marker標記,對map和marker均添加了點擊事件; <body> <script> function initMap() { // 創建地圖對象 const map = new HWMapJsSDK.HWMap(document.getElementByI ...
  • 前言 Claude 是 Anthropic 開發的人工智慧產品。Anthropic 是由 11 名前 OpenAI 員工於 2022 年創立的人工智慧公司,旨在構建安全、可解釋和有益於人類的人工智慧系統。Claude 是該公司的第一個產品,得到了谷歌 3 億美元的投資。 與 ChatGPT 有什麼區 ...
  • 眾所周知,在一個普通的HTML頁面中,如果要實現一個鏈接點擊後下載圖片,只需要在頁面上放一個<a>標簽,然後將屬性href的值指向圖片的URL或者Base64字元串就可以了。或者按照stackoverflow上提供的方法動態創建<a>標簽來完成圖片的下載動作。不過原理都是相同的。 但是這個方法在VS ...
  • 背景 項目中有一個系統使用的微前端,主站使用是vue2實現的,使用的是vue-router3.x。子應用有使用vue3實現的,使用的為vue-router4.x。 該子應用中的頁面A有通過操作按鈕觸發跳轉到其他子應用頁面B的需求,此時使用的是vue-router4.x的編程式導航API。 當通過點擊 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...