tianjia-dao-rili.vue 8.81 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';

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

// 弹窗显示控制
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;
    }
    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;
};

// 打开弹窗(暴露给父组件)
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;
    }
    const params = {
        id: props.templateId,
        planId: props.planId,
        trainDateList: selectedDates.value
    };
    try {
        await QueryPlanApi.addPlanToCalendar(params);
        uni.showToast({ title: '添加到日历成功', icon: 'success' });
        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>