9433cfb9创建于 2025年12月31日历史提交
<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex:1">
  <!-- #endif -->
    <view class="root">
      <view class="date">
        <text class="date-text">{{ current_month }}</text>
      </view>
      <view ref="draw-header" class="calendar-header"></view>
      <view ref="draw-weeks" class="calendar-week" @click="select"></view>
      <view class="btn-group">
        <button size="mini" @click="preDate">上个月</button>
        <button size="mini" @click="gotoToday">回到今天</button>
        <button size="mini" @click="nextDate">下个月</button>
      </view>
      <view>{{ timeData.fullDate }} {{ current_day }}</view>
    </view>
  <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>
<script>
  import { Calendar, DateType } from './index.uts'

  type CoordsType = {
    x : number;
    y : number;
    width : number;
    height : number;
    data : DateType
  }

  export default {
    data() {
      return {
        weeks: [] as Array<Array<DateType>>,
        $coords: [] as Array<CoordsType>,
        $calendar: new Calendar() as Calendar,
        timeData: {
          fullDate: '',
          year: 0,
          month: 0,
          date: 0,
          day: 0,
          lunar: '',
          disabled: false,
          is_today: false
        } as DateType,
        testWidth: 0
      }
    },
    computed: {
      // 获取月份
      current_month() : string {
        const nowDate = this.timeData
        const month = nowDate.month
        return month < 10 ? '0' + month : month.toString()
      },
      current_day() : string {
        const time = this.timeData.data
        if (time == null) {
          return ''
        }
        return time.IMonthCn + time.IDayCn
      }
    },
    created() { },
    onResize() {
      // 屏幕发生变化,初始化日历
      this.init()
    },
    onReady() {
      this.init()

      // 仅自动化测试
      const header = this.$refs['draw-header'] as UniElement
      this.testWidth = header.getBoundingClientRect().width;
    },
    methods: {
      init() {
        const calendar = this.$data['$calendar'] as Calendar
        const refs = this.$refs['draw-weeks'] as UniElement
        let ctx = refs.getDrawableContext()
        if (ctx != null) {
          ctx.reset()
        }

        // this.timeData = calendar.getDateInfo()
        // 检查是否有fulltime ,来确定是否初次渲染
        let fulltime = this.timeData.fullDate
        if (fulltime == '') {
          this.timeData = calendar.getDateInfo()
          fulltime = this.timeData.fullDate
        }

        this.weeks = calendar.getWeeks(fulltime)
        // 绘制日历头部
        this.drawHeader()
        this.drawWeek(this.weeks, fulltime)
      },
      // 触发整个日历的点击事件,需要计算点击位置
      select(event : UniPointerEvent) {
        const refs = this.$refs['draw-weeks'] as UniElement
        const rect = refs.getBoundingClientRect();
        const dom_x = rect.left; // 元素左上角相对于视口的 X 坐标
        const dom_y = rect.top; // 元素左上角相对于视口的 Y 坐标
        // const touch = event.touches[0];
        const clientX = event.clientX; // X 坐标
        const clientY = event.clientY; // Y 坐标
        // 计算点击的相对位置
        const x = clientX - dom_x
        const y = clientY - dom_y

        this.clickGrid(x, y)
      },

      // 点击具体的日历格子
      clickGrid(x : number, y : number) {
        // 小格子数组
        // const gridArray = this.$data.$coords
        const calendar = this.$data['$calendar'] as Calendar
        const gridArray = this.$data['$coords'] as Array<CoordsType>

        // 遍历小格子数组,找到最接近点击坐标的小格子
        for (let i = 0; i < gridArray.length; i++) {
          const grid = gridArray[i]
          // 计算小格子理论上的最大值
          const max_x = grid.x + grid.width
          const max_y = grid.y + grid.height

          const is_x_limit = grid.x < x && x < max_x
          const is_y_limit = grid.y < y && y < max_y

          const is_select = is_x_limit && is_y_limit

          if (is_select) {
            const data = grid.data
            this.timeData = calendar.getDateInfo(data.fullDate)
            this.drawWeek(this.weeks, grid.data.fullDate)
          }
        }
      },
      // 切换上个月
      preDate() {
        const fulldate = this.timeData.fullDate
        const calendar = this.$data['$calendar'] as Calendar
        let time = calendar.getDate(fulldate, -1, 'month')
        const newDate = time.year + '-' + time.month + '-1';
        time = calendar.getDate(newDate)

        this.timeData = calendar.getDateInfo(time.fullDate)
        this.weeks = calendar.getWeeks(time.fullDate)

        // 判断是否回到当前月份
        if (this.isCurrentMonth(time.year, time.month)) {
          this.gotoToday();
        } else {
          // 否则正常绘制日历
          this.drawWeek(this.weeks, time.fullDate)
        }
      },
      // 切换下个他
      nextDate() {
        const fulldate = this.timeData.fullDate
        const calendar = this.$data['$calendar'] as Calendar
        let time = calendar.getDate(fulldate, 1, 'month')

        const newDate = time.year + '-' + time.month + '-1';
        time = calendar.getDate(newDate)

        this.timeData = calendar.getDateInfo(time.fullDate)
        this.weeks = calendar.getWeeks(time.fullDate)

        // 判断是否回到当前月份
        if (this.isCurrentMonth(time.year, time.month)) {
          this.gotoToday();
        } else {
          // 否则正常绘制日历
          this.drawWeek(this.weeks, time.fullDate)
        }
      },
      // 回到今天
      gotoToday() {
        const calendar = this.$data['$calendar'] as Calendar
        const time = calendar.getDate()
        this.timeData = calendar.getDateInfo(time.fullDate)
        this.weeks = calendar.getWeeks(time.fullDate)

        // 重新绘制日历
        this.drawWeek(this.weeks, time.fullDate)
      },
      // 判断是否为当前月份
      isCurrentMonth(year : number, month : number) : boolean {
        const today = new Date();
        return year === today.getFullYear() && month === today.getMonth() + 1;
      },

      // 绘制日历顶部信息
      drawHeader() {
        const refs = this.$refs['draw-header'] as UniElement
        let ctx = refs.getDrawableContext()
        if (ctx == null) return
        // 渲染之前如果有内容,需要清空
        ctx.reset()
        const date_header_map = ['一', '二', '三', '四', '五', '六', '日']

        const width = refs.getBoundingClientRect().width
        const num = date_header_map.length
        const one_width = width / num

        ctx.font = '12'
        ctx.textAlign = 'center'

        for (let i = 0; i < num; i++) {
          let box_left = i * one_width + 2
          let box_width = one_width - 4
          let box_height = 26

          // 文本赋值
          const text = date_header_map[i]
          let text_left = box_width / 2 + box_left
          let text_top = box_height / 2 + 6

          ctx.fillText(text, text_left, text_top)
        }
        ctx.update()
      },

      // 绘制日历主体
      drawWeek(weeks : Array<Array<DateType>>, time : string) {
        const start_time = Date.now()
        const refs = this.$refs['draw-weeks'] as UniElement
        let ctx = refs.getDrawableContext()
        if (ctx == null) return
        const dom = refs.getBoundingClientRect()
        const width = dom.width
        const height = dom.height
        let week_len = weeks.length
        const one_width = width / weeks[0].length
        const one_height = height / week_len

        if (time != '') {
          this.$data['$coords'] = [] as Array<CoordsType>
          ctx.reset()
        }

        ctx.textAlign = 'center'

        for (let week = 0; week < week_len; week++) {

          const week_item = weeks[week]
          for (let day = 0; day < week_item.length; day++) {
            const day_item = week_item[day]
            let day_left = day * one_width + 2
            let day_top = one_height * week + 2
            let day_width = one_width - 4
            let day_height = one_height - 4

            // 文本赋值
            let text = day_item.date.toString()
            let text_left = day * one_width + (one_width / 2)
            let text_top = one_height * week + 25

            ctx.font = '16'

            // 日期是否禁用
            if (day_item.disabled) {
              ctx.fillStyle = '#ccc'
            } else {
              // 是否为今天
              if (day_item.is_today) {
                ctx.fillStyle = 'red'
              } // 是否为选中日期
              else if (time == day_item.fullDate) {
                ctx.fillStyle = 'blue';
              }
              // 默认颜色
              else {
                ctx.fillStyle = '#666';
              }

              // 第一次渲染获取数据
              // if (time == '') {
              // 存储坐标组,用于点击事件
              const coords : CoordsType = {
                x: day_left,
                y: day_top,
                width: day_width,
                height: day_height,
                data: day_item
              }

              // TODO 兼容安卓data内$开头的属性的赋值问题
              let gridArr = this.$data['$coords'] as Array<CoordsType>
              gridArr.push(coords)
              // }
            }

            ctx.fillText(text, text_left, text_top)

            text = day_item.lunar
            let lunar_left = day * one_width + (one_width / 2)
            let lunar_top = one_height * week + 42
            ctx.font = '10'
            ctx.fillText(text, lunar_left, lunar_top)
          }
        }

        ctx.update()
        console.log('diff time', Date.now() - start_time);

      }
    }
  }
</script>

<style>
  .root {
    /* flex: 1; */
    position: relative;
    padding: 15px;
    background-color: #fff;
  }

  .calendar-header {
    height: 30px;
    margin-bottom: 10px;
  }

  .date {
    margin-bottom: 10px;
    margin-left: 10px;
  }

  .date-text {
    font-size: 34px;
    font-weight: bold;
  }

  .calendar-week {
    height: 350px;
    margin: 2px 0;
  }

  .btn-group {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    margin: 20px 0;
  }
</style>