客户原始需求:用户需要知道在选中的月份中,哪些日期是有客户预约的,并且显示当天预约人数,点击有预约的日期则进入预约信息详细页,详细页内可以新建预约;点击没有预约的日期则直接进入新建预约页面。
因为项目用的是vant的小程序ui组件,所以刚开始想的是用vant的日历组件来实现此需求。经过几番尝试后,始终实现不了 formatter 的动态数据渲染,最后放弃了(如有大佬已实现此功能,麻烦留言分享下你的解决方案,谢谢),然后自己写了一个日历组件。
2. 点击单个日期,返回具体年月日,并且被点击日期变色
3. 根据用户数据,动态渲染日历标注。传入的日期变更,或者是用户数据变更,都会重新渲染日历及标注。上面效果图下方的【改日期】和 【改数据】就是用来测试这个功能的。
- <view class="calendar-box">
- <view class="head-box">
- <view class="title-box">{{title}}</view>
- <view class="week-box">
- <view class="week-item">日</view>
- <view class="week-item">一</view>
- <view class="week-item">二</view>
- <view class="week-item">三</view>
- <view class="week-item">四</view>
- <view class="week-item">五</view>
- <view class="week-item">六</view>
- </view>
- </view>
- <view class="date-box">
- <view
- bindtap="clickItem"
- data-date="{{day.date}}"
- class="date-item {{currentDate == day.date ? 'active' : ''}}"
- wx:for="{{daysArray}}" wx:for-item="day"
- wx:key="index">
- <view class="top-text" wx:if="{{day.topText}}">{{day.topText}}</view>
- {{day.date}}
- <view class="bottom-text" wx:if="{{day.bottomText}}">{{day.bottomText}}</view>
- </view>
- </view>
- </view>
- // components/calendar/calendar.js
- Component({
- /**
- * 组件的属性列表
- */
- properties: {
- defaultDate: {
- type: String,
- observer () {
- this.getCurrentDaysAndWeekStart();
- this.renderDate();
- this.triggerEvent('formatter', this.data.daysArray);
- }
- },
- isDataChange: {
- type: Boolean,
- value: false,
- observer () {
- this.triggerEvent('formatter', this.data.daysArray);
- }
- },
- daysData: {
- type: Array,
- observer (newVal) {
- if (newVal.length > 0) {
- this.setData({ daysArray: newVal });
- }
- }
- }
- },
- /**
- * 组件的初始数据
- */
- data: {
- Y: '', // 年
- M: '', // 月
- D: '', // 日
- W: '', // 星期
- firstDayWeek: '', // 当前月第一天星期几
- lastDayWeek: '', // 当前月最后一天星期几
- daysCount: 0, // 总天数
- daysArray: [], // 日历中天数数组
- title: '',
- currentDate: '0'
- },
- /**
- * 组件的方法列表
- */
- methods: {
- // 获取当前月的天数,以及当前月第一天是星期几,最后一天是星期几
- getCurrentDaysAndWeekStart () {
- let dateString = this.properties.defaultDate;
- let today = new Date();
- if (dateString) {
- today = new Date(dateString);
- }
- let Y = today.getFullYear();
- let M = today.getMonth() + 1;
- let D = today.getDate();
- let daysCount = new Date(Y, M, 0).getDate(); // 当前月最后一天日期,即当前月的天数
- let firstDayWeek = new Date(Y, M - 1, 1).getDay(); // 第一天星期几
- let lastDayWeek = new Date(Y, M, 0).getDay(); // 最后一天星期几
- this.setData({
- Y: Y,
- M: M,
- D: D,
- firstDayWeek: firstDayWeek,
- lastDayWeek: lastDayWeek,
- daysCount: daysCount,
- title: `${Y}年${M}月`
- });
- },
- // 根据总天数和第一天星期几,最后一天星期几,渲染日历天数数组
- renderDate () {
- let firstDayWeek = this.data.firstDayWeek;
- let lastDayWeek = this.data.lastDayWeek;
- let daysCount = this.data.daysCount;
- let days = []; // 当前月总天数数组
- for (let i = 1; i <= daysCount; i++) {
- days.push({
- date: i.toString(),
- topText: '',
- bottomText: ''
- });
- }
-
- // 补全前面 (因为一周七天,如果第一天是星期三,则需要把星期一和星期二数据补全)
- for (let i = 0; i < firstDayWeek; i++) {
- days.unshift({
- date: '',
- topText: '',
- bottomText: ''
- });
- }
-
- // 补全后面 (因为一周七天,如果最后一天是星期五,则需要把星期六和星期天数据补全)
- for (let i = lastDayWeek; i <= 7; i++) {
- days.push({
- date: '',
- topText: '',
- bottomText: ''
- });
- }
- this.setData({ daysArray: days });
- },
- // 点击单个日期事件
- clickItem (event) {
- let date = event.currentTarget.dataset.date;
- if (date) {
- this.setData({ currentDate: date });
- this.triggerEvent('select', `${this.data.Y}-${this.data.M}-${date}`);
- }
- }
- }
- })
- /* components/calendar/calendar.wxss */
- .calendar-box {
- background-color: #ffffff;
- padding: 10rpx;
- color: #323233;
- }
- .head-box {
- box-shadow: 0 2px 10px rgb(125 126 128 / 16%);
- }
- .title-box {
- padding: 20rpx 0 40rpx 0;
- text-align: center;
- font-size: 14px;
- }
- .week-box {
- display: flex;
- justify-content: space-between;
- font-size: 12px;
- padding-bottom: 20rpx;
- }
- .week-item {
- width: 100%;
- text-align: center;
- }
- .date-box {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- }
- .date-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 125rpx;
- width: 14.285%;
- border-radius: 10rpx;
- font-size: 12px;
- }
- .top-text, .bottom-text {
- font-size: 8px;
- color: red;
- }
- .active {
- background-color: pink;
- }