tianjia-dao-rili.vue 10 KB
<template>
  <!-- 自定义底部弹窗 -->
  <view v-if="showPopup" class="custom-mask" @click="handleMaskClick">
    <view class="custom-popup" @click.stop>
      <!-- 标题 + 月份导航 -->
      <view class="popup-header">
        <text class="title">训练日历</text>
        <view class="month-nav">
          <u-icon name="arrow-left" size="24" @click="prevMonth"></u-icon>
          <text class="month-text">{{ currentYearMonth }}</text>
          <u-icon name="arrow-right" size="24" @click="nextMonth"></u-icon>
        </view>
      </view>

      <!-- 自定义日历主体 -->
      <view class="calendar-container">
        <!-- 星期行 -->
        <view class="week-row">
          <text v-for="week in weekDays" :key="week" class="week-day">{{ week }}</text>
        </view>
        <!-- 日期格子 -->
        <view class="days-grid">
          <view v-for="(day, idx) in calendarDays" :key="idx" class="day-cell" :class="{
            'other-month': !day.isCurrentMonth,
            'selected': isDateSelected(day.fullDate),
            'disabled': day.fullDate < minDateStr
          }" @click="toggleDate(day)">
            <text class="day-number">{{ day.day }}</text>
          </view>
        </view>
      </view>

      <!-- 底部确认按钮 -->
      <view class="popup-footer">
        <u-button type="primary" size="large" :disabled="selectedDates.length === 0" @click="confirmAdd">
          添加到以上日程
        </u-button>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, computed, watch } from 'vue';
import QueryPlanApi from '@/sheep/api/plan/queryplan';
import dailytemplateApi from '@/sheep/api/Template/Dailytemplate';

const props = defineProps({
  planId: { type: [String, Number], required: false },
  templateId: { type: [String, Number], required: true, default: 0 },
  isMove: {
    type: Boolean,
    default: false
  },
  isCopy: { type: Boolean, default: false },
  dailyTemplateId: { type: Number, default: 0 }
});
const emit = defineEmits(['success', 'clearCopyFlag']);

// 弹窗显示控制
const showPopup = ref(false);

// 当前显示的日期(年、月)
const currentYear = ref(new Date().getFullYear());
const currentMonth = ref(new Date().getMonth() + 1); // 1-12

// 已选择的日期列表(字符串数组,格式 YYYY-MM-DD)
const selectedDates = ref([]);

// 最小可选日期(今天)
const today = new Date();
const minDateStr = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`;

// 星期标题
const weekDays = ['日', '一', '二', '三', '四', '五', '六'];

// 当前年月显示文本
const currentYearMonth = computed(() => `${currentYear.value}年${currentMonth.value.toString().padStart(2, '0')}月`);

// 生成当前月份的日历数据(包含上个月和下个月的占位)
const calendarDays = computed(() => {
  const year = currentYear.value;
  const month = currentMonth.value;
  const firstDayOfMonth = new Date(year, month - 1, 1);
  const startWeekday = firstDayOfMonth.getDay(); // 0=周日
  const daysInMonth = new Date(year, month, 0).getDate();

  // 获取上个月的最后几天
  const prevMonthDate = new Date(year, month - 1, 0);
  const daysInPrevMonth = prevMonthDate.getDate();
  const prevMonthDays = [];
  for (let i = startWeekday - 1; i >= 0; i--) {
    const dayNum = daysInPrevMonth - i;
    const fullDate = `${prevMonthDate.getFullYear()}-${(prevMonthDate.getMonth() + 1).toString().padStart(2, '0')}-${dayNum.toString().padStart(2, '0')}`;
    prevMonthDays.push({
      day: dayNum,
      fullDate: fullDate,
      isCurrentMonth: false,
    });
  }

  // 当月天数
  const currentMonthDays = [];
  for (let i = 1; i <= daysInMonth; i++) {
    const fullDate = `${year}-${month.toString().padStart(2, '0')}-${i.toString().padStart(2, '0')}`;
    currentMonthDays.push({
      day: i,
      fullDate: fullDate,
      isCurrentMonth: true,
    });
  }

  // 下个月补齐(总共42格,6行)
  const totalCells = 42;
  const remaining = totalCells - (prevMonthDays.length + currentMonthDays.length);
  const nextMonthDays = [];
  for (let i = 1; i <= remaining; i++) {
    const nextDate = new Date(year, month, i);
    const fullDate = `${nextDate.getFullYear()}-${(nextDate.getMonth() + 1).toString().padStart(2, '0')}-${nextDate.getDate().toString().padStart(2, '0')}`;
    nextMonthDays.push({
      day: i,
      fullDate: fullDate,
      isCurrentMonth: false,
    });
  }

  return [...prevMonthDays, ...currentMonthDays, ...nextMonthDays];
});

// 检查某天是否被选中
const isDateSelected = (fullDate) => {
  return selectedDates.value.includes(fullDate);
};

// 切换日期选中状态(仅允许选择今天及之后)
const toggleDate = (day) => {
  if (day.fullDate < minDateStr) {
    uni.showToast({ title: '不能选择过去的日期', icon: 'none' });
    return;
  }
  // 移动模式下,只能选择一个日期
  if (props.isMove) {
    selectedDates.value = [day.fullDate]; // 直接覆盖,永远只有1个
    return;
  }
  const index = selectedDates.value.indexOf(day.fullDate);
  if (index === -1) {
    selectedDates.value.push(day.fullDate);
  } else {
    selectedDates.value.splice(index, 1);
  }
};

// 月份切换
const prevMonth = () => {
  if (currentMonth.value === 1) {
    currentYear.value--;
    currentMonth.value = 12;
  } else {
    currentMonth.value--;
  }
};
const nextMonth = () => {
  if (currentMonth.value === 12) {
    currentYear.value++;
    currentMonth.value = 1;
  } else {
    currentMonth.value++;
  }
};

// 关闭弹窗
const handleMaskClick = () => close();
const close = () => {
  showPopup.value = false;
  selectedDates.value = [];
  // 重置到当前月份(可选)
  currentYear.value = new Date().getFullYear();
  currentMonth.value = new Date().getMonth() + 1;
  emit('clearCopyFlag')
};

// 打开弹窗(暴露给父组件)
const open = () => {
  console.log('子组件open方法被调用了');
  showPopup.value = true;
  selectedDates.value = [];
  currentYear.value = new Date().getFullYear();
  currentMonth.value = new Date().getMonth() + 1;
};

// 确认添加
const confirmAdd = async () => {
  if (selectedDates.value.length === 0) {
    uni.showToast({ title: '请选择训练日期', icon: 'none' });
    return;
  }
  try {
    // if (props.isMove) {
    //   const params = {
    //     id: props.templateId,
    //     trainingDate: selectedDates.value[0] || ''
    //   };
    //   // 添加到日历弹窗的参数
    //   console.log('移动到:params', params);
    //   await QueryPlanApi.updateDailyTemplateDate(params);
    //   uni.showToast({ title: '移动成功', icon: 'success' });
    // } else {
    //   const params = {
    //     id: props.templateId,
    //     planId: props.planId,
    //     trainDateList: selectedDates.value
    //   };
    //   // 添加到日历弹窗的参数
    //   console.log('添加到params', params);
    //   await QueryPlanApi.addPlanToCalendar(params);
    //   uni.showToast({ title: '添加到日历成功', icon: 'success' });
    // }
    // ========== 新增复制分支 优先判断 ==========
    console.log('rops.dailyTemplateId=', props.dailyTemplateId);
    if (props.isCopy) {
      // 调用你新增的复制接口
      await dailytemplateApi.copyTempleteDailyTemplate(props.dailyTemplateId, selectedDates.value)
      uni.showToast({ title: '复制成功', icon: 'success' });
      console.log('调用复制接口成功');

    } else if (props.isMove) {
      //  移动到
      const params = {
        id: props.templateId,
        trainingDate: selectedDates.value[0] || ''
      };
      await QueryPlanApi.updateDailyTemplateDate(params);
      uni.showToast({ title: '移动成功', icon: 'success' });
      console.log('移动接口成功');
    } else {
      //添加到日历
      const params = {
        id: props.templateId,
        planId: props.planId,
        trainDateList: selectedDates.value
      };
      await QueryPlanApi.addPlanToCalendar(params);
      uni.showToast({ title: '添加到日历成功', icon: 'success' });
      console.log('添加到接口成功');
    }


    close();
    emit('success');
  } catch (err) {
    uni.showToast({ title: '操作日历失败,请重试', icon: 'none' });
    console.error('操作日历失败:', err);
  }
};

defineExpose({ open, close });
</script>

<style scoped lang="scss">
.custom-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 9999;
  display: flex;
  align-items: flex-end;
}

.custom-popup {
  background: #fff;
  border-radius: 24rpx 24rpx 0 0;
  padding: 30rpx;
  box-sizing: border-box;
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
}

.popup-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30rpx;

  .title {
    font-size: 32rpx;
    font-weight: bold;
    color: #333;
  }

  .month-nav {
    display: flex;
    align-items: center;
    gap: 20rpx;

    .month-text {
      font-size: 28rpx;
      color: #333;
    }
  }
}

.calendar-container {
  margin-bottom: 30rpx;
}

.week-row {
  display: flex;
  justify-content: space-around;
  margin-bottom: 20rpx;

  .week-day {
    width: 14.28%;
    text-align: center;
    font-size: 28rpx;
    color: #999;
  }
}

.days-grid {
  display: flex;
  flex-wrap: wrap;
}

.day-cell {
  width: 14.28%;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8rpx;
  margin-bottom: 8rpx;

  .day-number {
    font-size: 28rpx;
    color: #333;
  }

  &.other-month .day-number {
    color: #ccc;
  }

  &.selected {
    background-color: #ffd700;

    .day-number {
      color: #000;
      font-weight: bold;
    }
  }

  &.disabled {
    opacity: 0.4;
    pointer-events: none;
  }
}

.popup-footer {
  margin-top: 30rpx;
  padding-top: 30rpx;

  .u-button {
    background-color: #ffd700 !important;
    color: #000;
    font-weight: bold;
    border-radius: 40rpx;

    &.u-button--disabled {
      background-color: #eee !important;
      color: #999 !important;
    }
  }
}
</style>