手把手教你实现一个漂亮的周日历

1,124 阅读3分钟

项目背景

在uniapp开发中要求在移动端设计一个以周为单位的日历组件,习惯了用组件的我,突然在uniapp插件中没有找到我的理想型。那只复习一下Date相关的文档,再手撸一个了。

实现思路

要想实现一周日历,首先肯定得获取到当前一周的日期,再与星期做一个一一对应,这样就形成了一个基本日历。将其封装成一个子组件,方便在父组件中调用。

设定一个周星期

默认是从周日到周六,我们采用固定的形式:

['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat']

获取当天是几号

<template>
  <view>
    今天是:{{ dayOfMonth }}号
  </view>
</template>

<script>
export default {
  data() {
    return {
      dayOfMonth: null, // 当天日期
    };
  },
  onLoad() {
    // 获取当前几号
    const today = new Date();
    this.dayOfMonth = today.getDate();
  },
};
</script>

运行结果:今天是22号

获取一周日期

在这个地方,需要注意几点,为了方便渲染,这里将信息进行了一个拼接处理。我们封装了一个getWeekDates()方法,参数就是当前日期startDate。在这其中有一个方法叫toISOString(),这是方法的作用是使用ISO标准,返回Date对象的标准字符串格式,形式如:“2024-11-22T06:42:38.163Z”。

getWeekDates(startDate) {
    const weekDates = [];
    for (let i = 0; i < 7; i++) {
      const date = new Date(startDate);
      date.setDate(startDate.getDate() + i);
      weekDates.push({
        date: date.toISOString().split('T')[0],
        weekday: ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'][date.getDay()],
        selected: this.selectedDate === date.toISOString().split('T')[0],
      });
    }
    return weekDates;
},

初始化当前周

初始化当前周的作用是基于当前日期计算一周内所有日期,并将日期和星期做一个映射处理。

initCurrentWeek() {
    const today = this.today;
    const startOfWeek = new Date(today);
    startOfWeek.setDate(today.getDate() - today.getDay());
    this.currentWeek = this.getWeekDates(startOfWeek);
    this.selectedDate = today.toISOString().split('T')[0];
},

日期选择

默认日期当天高亮,这里主要通过对比周日历中得日期,与系统获取得当前日期是否相同,如果相同就设置一个高亮样式进行标记。手动日期选择,点击某一天,设置高亮。

selectDay(index) {
    this.selectedDate = this.currentWeek[index].date;
    this.currentWeek = this.currentWeek.map((day, i) => ({
      ...day,
      selected: i === index,
    }));
    this.$emit('dateSelected', this.selectedDate);
},

样式设计

主要采用flex布局,让其自适应宽度。

日期组件源码

<template>
  <view class="week-calendar">
    <view class="week-month">
      <view class="month-title">
        <text style="padding-right: 10px;">{{month}}</text>
        <text>{{MONTH_EN[month]}}</text>
      </view>
      <text class="month-tips">Never Put Off Until Tomorrow </text>
    </view>
    <view class="week">
      <block v-for="(day, index) in currentWeek" :key="index">
        <view class="day" @click="selectDay(index)">
          <view class="day-week">{{ day.weekday }}</view>
          <view class="day-day">
            <text class="day-text" :class="{ selected: day.selected }">{{ day.date.split('-')[2] }}</text>
          </view>
          <view class="day-tip">
            <text class="day-tip-color" :class="{'tip-day-text': setDayTip(day.date)}"></text>
          </view>
        </view>
      </block>
    </view>
  </view>
</template>

<script>
  import {
    MONTH_EN
  } from '../../../utils/strategy.js'
  export default {
    props: {
      todo_list: {
        type: Array,
        required: true
      }
    },
    data() {
      return {
        MONTH_EN,
        today: new Date(),
        currentWeek: [],
        selectedDate: new Date().toISOString().split('T')[0],
        month: new Date().toISOString().split('T')[0].split('-')[1]
      }
    },
    created() {
      this.initCurrentWeek();
    },

    methods: {
      // 初始化当前周
      initCurrentWeek() {
        const today = this.today;
        const startOfWeek = new Date(today);
        startOfWeek.setDate(today.getDate() - today.getDay());
        this.currentWeek = this.getWeekDates(startOfWeek);
        this.selectedDate = today.toISOString().split('T')[0];
      },
      // 获取一周日期
      getWeekDates(startDate) {
        const weekDates = [];
        for (let i = 0; i < 7; i++) {
          const date = new Date(startDate);
          date.setDate(startDate.getDate() + i);
          weekDates.push({
            date: date.toISOString().split('T')[0],
            weekday: ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'][date.getDay()],
            selected: this.selectedDate === date.toISOString().split('T')[0],
          });
        }
        return weekDates;
      },
      // 选择某一天
      selectDay(index) {
        this.selectedDate = this.currentWeek[index].date;
        this.currentWeek = this.currentWeek.map((day, i) => ({
          ...day,
          selected: i === index,
        }));
        this.$emit('dateSelected', this.selectedDate);
      },
      setDayTip(day) {
        return this.todo_list.some((item) => {
          return item.remind_time.indexOf(day) != -1
        })
      }
    }
  }
</script>

<style scoped>
  .week-calendar{
    height: 190px;
    width: 100%;
    position: fixed;
    top: 0;
    background: #fff;
    z-index: 10000;
  }
  .week-month {
    padding: 20px;
  }

  .month-title {
    font-size: 28px;
    font-weight: bold;
    margin-bottom: 4px;
  }

  .month-tips {
    color: #868499;
    font-size: 12px;
  }

  .week {
    height: 88px;
    font-size: 14px;
    display: flex;
    justify-content: space-around;
    border: 1px solid #c8c7cc;
    border-radius: 24upx;
    background-color: #fff;
  }

  .day {
    flex: 1;
    text-align: center;
    align-items: center;
  }

  .day-week {
    padding: 10px 5px;
    color: #8990ad;
  }

  .day-text {
    display: inline-block;
    padding: 4px;
  }

  .day-text.selected {
    background-color: #ed471d;
    color: #fff;
    border-radius: 50%;
  }
  .day-tip{
    height: 8px;
  }
  .day-tip-color{
    width: 8px;
    height: 4px;
    display: inline-block;
  }
  .tip-day-text{
    background: #fca00d;
  }
</style>

父组件调用

<template>
  <view class="todo">
    <DateWeek :todo_list="todo_list" @dateSelected="onDateSelected" style="margin-bottom: 20px;"></DateWeek>
  </view>
</template>
<script>
  import DateWeek from './dateweek.vue';
  export default {
    components: {
      DateWeek
    }
  }
</script>

效果图展示

微信截图_20241122152055.png

设置日期标注

眼尖的朋友可能发现了,为啥会有的日期下面会有一个黄色的小标识,比图2024-11-18,2024-11-20。其实它是这样实现的,这和业务功能有关,原理就是提取数据中的日期,再与当前周组件日期进行对比,如果相同,那么就设计一个特殊的css啦。

setDayTip(day) {
   return this.todo_list.some((item) => {
      return item.remind_time.indexOf(day) != -1
   })
}

总结

JavaScriptUniApp中,日期的获取和处理在语法上大致相同,主要是对日期的获取以及转换,特别要注意的一点就是日期的格式最好使用标准的ISO 86001作为日期格式,确保跨平台兼容。