booking.vue 11 KB
<!--
 * @Date: 2024-01-15 13:35:51
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2024-01-18 13:47:50
 * @FilePath: /xysBooking/src/views/booking.vue
 * @Description: 预约页面
 * @Version: 1.0.0
-->
<template>
  <div class="booking-page">
    <div class="calendar">
      <div class="choose-date">
        <div class="title">
          <div class="text">选择参访日期</div>
          <div @click="chooseDate" class="day">{{ currentDateText }} 月</div>
        </div>
        <div class="days-of-week">
          <div v-for="day in daysOfWeek" :key="day" class="item">{{ day }}</div>
        </div>
        <div v-for="(week, index) in weeks" :key="week" class="weeks">
          <div v-for="date in week" :key="date"
            @click="chooseDay(date)"
            :class="['item', checked_day === findDatesInfo(date).date ? 'checked' : '',  findDatesInfo(date).reserve_full ? 'disabled' : '']"
          >
            <div v-if="findDatesInfo(date).date">
              <p class="day-text">{{ findDatesInfo(date).text }}</p>
              <p v-if="!findDatesInfo(date).reserve_full" class="day-price">¥{{ findDatesInfo(date).price }}</p>
              <p v-else class="day-no-booking">已约满</p>
            </div>
          </div>
        </div>
      </div>
      <div v-if="checked_day" class="choose-time">
        <div class="title">
          <div class="text">选择参访时间段</div>
        </div>
        <div class="time-list">
          <div
            @click="chooseTime(item, index)"
            v-for="(item, index) in timePeriod"
            :key="index"
            :class="['time', item.rest_qty === 0 ? 'disabled' : '']"
          >
            <div class="left">
              <van-icon v-if="checked_time !== index" :name="icon_select1" />&nbsp;
              <van-icon v-else :name="icon_select2" />&nbsp;
              {{ item.begin_time }}-{{ item.end_time }}
            </div>
            <div class="right">
              <span v-if="item.rest_qty > 0">余量:{{ item.price }}</span>
              <span v-else-if="item.rest_qty === -1">可约</span>
              <span v-else>已约满</span>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div style="height: 5rem;"></div>
    <div class="next">
      <div @click="nextBtn" class="button" style="background-color: #A67939;">下一步</div>
    </div>

    <van-popup v-model:show="showPicker" position="bottom">
      <van-date-picker
        v-model="currentDate"
        title="选择年月"
        :min-date="minDate"
        :max-date="maxDate"
        :columns-type="columnsType"
        @confirm="onConfirm"
        @cancel="onCancel"
      />
    </van-popup>

    <van-toast v-model:show="show_error" style="">
      <template #message>
        {{ error_message }}
      </template>
    </van-toast>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { showSuccessToast, showFailToast, showToast } from 'vant';
import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
import dayjs from 'dayjs';
import { useGo } from '@/hooks/useGo'
import icon_select1 from '@/assets/images/单选01@2x.png'
import icon_select2 from '@/assets/images/单选02@2x.png'
import { canReserveDateListAPI, canReserveTimeListAPI } from '@/api/index'


const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);

const go = useGo();

const dates_list = ref([]);
const dates = ref([]);

onMounted(async () => {
  const raw_date = new Date();
  const { code, data } = await canReserveDateListAPI({ month: `${raw_date.getFullYear()}-${raw_date.getMonth() + 1}` });
  if (code) {
    // 日期列表
    dates_list.value = data;
    // 今日之前都不可约
    dates_list.value.forEach((date) => {
      if (dayjs(date.month_date).isBefore(dayjs())) {
        date.reserve_full = 1;
      }
    });
    dates.value = dates_list.value.map(item => item.month_date);
  }
})

const findDatesInfo = (date) => {
  const result = dates_list.value.find((item) => item.month_date === date);
  const currentDate = new Date(date);
  return {
    text: currentDate.getDate().toString().padStart(2, '0'),
    date: result?.month_date,
    price: result?.price,
    reserve_full: result?.reserve_full,
  };
};

const daysOfWeek = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];

const weeks = computed(() => {
  const result = [];
  let currentWeek = [];
  let currentDate = new Date(dates.value[0]);

  // 确定第一个日期是星期几
  const firstDayOfWeek = currentDate.getDay() === 0 ? 7 : currentDate.getDay();

  // 添加空白的日期,直到第一个日期的星期一
  for (let i = 1; i < firstDayOfWeek; i++) {
    currentWeek.push('');
  }

  // 添加日期
  for (const date of dates.value) {
    currentDate = new Date(date);
    const dayOfWeek = currentDate.getDay() === 0 ? 7 : currentDate.getDay();

    // 如果当前星期一,开始新的一行
    if (dayOfWeek === 1 && currentWeek.length > 0) {
      result.push(currentWeek);
      currentWeek = [];
    }

    // currentWeek.push(currentDate.getDate()); // 仅将日期部分作为字符串添加到当前星期数组
    currentWeek.push(date); // 仅将日期部分作为字符串添加到当前星期数组
  }

  // 添加最后一行
  if (currentWeek.length > 0) {
    result.push(currentWeek);
  }

  return result;
});

const checked_day = ref('');

const checked_time = ref(-1);
const timePeriod = ref([]);

const chooseTime = (item, index) => { // 选择时间段回调
  if (item.rest_qty > 0 || item.rest_qty === -1) {
    checked_time.value = index;
  }
};

const checked_day_price = ref(0);

const chooseDay = async (date) => { // 点击日期回调
  if (!findDatesInfo(date).reserve_full) { // 有余数可约
    checked_day.value = date;
    checked_day_price.value = findDatesInfo(date).price;
    // 选择日期后,查询时间段信息
    const { code, data } = await canReserveTimeListAPI({ month_date: checked_day.value});
    if (code) {
      // rest_qty >0表示有余量,可约;=0表示没有余量,不可约;<0表示不限,可约;
      timePeriod.value = data;
      checked_time.value = -1;
    }
  }
};

const showPicker = ref(false);
const chooseDate = () => {
  showPicker.value = true;
}

const raw_date = new Date();
const currentDate = ref([raw_date.getFullYear(), raw_date.getMonth()]);
const columnsType = ['year', 'month'];
const minDate = new Date(2024, 0, 1);
const maxDate = new Date(2030, 11, 1);
const currentDateText = ref((raw_date.getMonth() + 1).toString().padStart(2, '0'));

const onConfirm = async ({ selectedValues, selectedOptions }) => { // 选择日期回调
  showPicker.value = false;
  currentDateText.value = selectedValues[1].toString();
  // 清空选择
  checked_day.value = '';
  checked_time.value = -1;
  // 选择日期后,查询月份信息
  const { code, data } = await canReserveDateListAPI({ month: `${selectedValues[0]}-${selectedValues[1]}` });
  if (code) {
    // 日期列表
    dates_list.value = data;
    // 今日之前都不可约
    dates_list.value.forEach((date) => {
      if (dayjs(date.month_date).isBefore(dayjs())) {
        date.reserve_full = 1;
      }
    });
    dates.value = dates_list.value.map(item => item.month_date);
  }
}
const onCancel = () => {
  showPicker.value = false;
}

const show_error = ref(false);
const error_message = ref('');
const nextBtn = () => {
  if (!checked_day.value || checked_time.value === -1) {
    show_error.value = true;
    error_message.value = '请选择日期和时间段';
  } else {
    go('/submit', { date: checked_day.value, time: `${timePeriod.value[checked_time.value]['begin_time']}-${timePeriod.value[checked_time.value]['end_time']}`, price: checked_day_price.value });
  }
}
</script>

<style lang="less" scoped>
.booking-page {
  position: relative;
  .calendar {
    padding: 1rem 0.5rem;
    .choose-date {
      border-radius: 5px;
      background-color: #FFFFFF;
      .title {
        padding: 0.5rem 0.75rem;
        display: flex;
        justify-content: space-between;
        align-items: center;
        .text {
          &::before {
            content: '';
            border: 2px solid #A67939;
            margin-right: 0.5rem;
          }
        }
        .day {
          background-color: #FFFBF3;
          border-radius: 7px;
          border: 1px solid #A67939;
          padding: 0.2rem 0.5rem;
          color: #A67939;
        }
      }
      .days-of-week {
        background-color: #EAEAEA;
        display: flex;
        padding: 0.75em 1%;
        font-size: 0.85rem;
        .item {
          width: 14.5%;
          text-align: center;
        }
      }
      .weeks {
        display: flex;
        padding: 0.5em 1%;
        .item {
          width: 11.5%;
          text-align: center;
          margin: 0 0.3rem;
          padding: 0.5rem 0;
          .day-text {
            color: #1E1E1E;
            font-weight: bold;
            font-size: 1.05rem;
          }
          .day-price {
            color: #A67939;
            font-size: 0.85rem;
          }
          &.checked {
            border: 1px solid #A67939;
            border-radius: 5px;
            background-color: #FFFBF3;
          }
          &.disabled {
            .day-text {
              color: #C7C7C7;
            }
            .day-price {
              color: #C7C7C7;
            }
            .day-no-booking {
              color: #C7C7C7;
              font-size: 0.75rem;
            }
          }
        }
      }
    }
    .choose-time {
      margin-top: 1rem;
      .title {
        padding: 0.5rem 0.75rem;
        display: flex;
        justify-content: space-between;
        align-items: center;
        .text {
          &::before {
            content: '';
            border: 2px solid #A67939;
            margin-right: 0.5rem;
          }
        }
      }
      .time-list {
        .time {
          display: flex;
          align-items: center;
          justify-content: space-between;
          background-color: #FFF;
          border-radius: 5px;
          padding: 0.85rem;
          margin: 1rem 0;
          .left {
            display: flex;
            align-items: center;
            color: #1E1E1E;
          }
          .right {
            color: #A67939;
          }
          &.disabled {
            background-color: #E0E0E0;
            .left {
              color: #C7C7C7;
            }
            .right {
              color: #C7C7C7;
            }
          }
        }
      }
    }
  }
  .next {
    position: fixed;
    bottom: 0;
    height: 5rem;
    width: 100vw;
    display: flex;
    left: 0;
    background-color: #FFF;
    align-items: center;
    justify-content: center;
    box-shadow: 0rem -0.33rem 0.25rem 0rem rgba(0,0,0,0.12);
    .button {
      color: #FFF;
      padding: 0.85rem 9rem;
      border-radius: 8px;
    }
  }
}

:deep(.van-picker__confirm) {
  color: #A67939;
}
</style>