time-select.vue 4.65 KB
<template>
  <view class="time-select">
    <view class="tab-container">
      <view class="tab-item" :class="{ active: currentDateType === 'week' }" @click="switchDateType('week')">

      </view>
      <view class="tab-item" :class="{ active: currentDateType === 'month' }" @click="switchDateType('month')">

      </view>
    </view>

    <view class="date-stepper">
      <view class="arrow-icon" @click="handlePrev">
        <up-icon name="arrow-left" color="#333" size="12" bold></up-icon>
      </view>
      <view class="date-display"> {{ dateRangeText }} </view>
      <!-- 修改: 根据 isNextDisabled 判断是否隐藏下一个按钮 -->
      <view class="arrow-icon" @click="handleNext" v-if="!isNextDisabled">
        <up-icon name="arrow-right" color="#333" size="12" bold></up-icon>
      </view>
      <view v-else class="occupy"></view>
    </view>
  </view>
</template>

<script setup>
import dayjs from 'dayjs';
import { ref, computed, watch } from 'vue';

const emit = defineEmits(['dateTypeChange', 'dateRangeChange']);

// 当前日期类型与基准日期
const currentDateType = ref('week');
const baseDate = ref(dayjs());

// ==================== 公共日期范围计算 ====================

/** 根据当前类型计算周期起止日期(dayjs 实例) */
const periodRange = computed(() => {
  const d = baseDate.value;
  if (currentDateType.value === 'week') {
    const dayOfWeek = d.day(); // 0=周日, 1=周一, ...
    const start = d.subtract(dayOfWeek === 0 ? 6 : dayOfWeek - 1, 'day');
    const end = start.add(6, 'day');
    return { start, end };
  }
  // month
  return { start: d.startOf('month'), end: d.endOf('month') };
});

// ==================== 计算属性 ====================

/** 对外暴露的日期范围(YYYY-MM-DD) */
const currentDateRange = computed(() => ({
  start: periodRange.value.start.format('YYYY-MM-DD'),
  end: periodRange.value.end.format('YYYY-MM-DD'),
}));

/** 显示的日期范围文本 "YYYY/MM/DD - YYYY/MM/DD" */
const dateRangeText = computed(() => {
  const { start, end } = periodRange.value;
  return `${start.format('YYYY/MM/DD')} - ${end.format('YYYY/MM/DD')}`;
});

/** 下一周期是否超过今天(超出则隐藏前进按钮) */
const isNextDisabled = computed(() => {
  const nextBase = currentDateType.value === 'week'
    ? baseDate.value.add(7, 'day')
    : baseDate.value.add(1, 'month');
  return nextBase.startOf('day').isAfter(dayjs().startOf('day'));
});

// ==================== 事件处理 ====================

const switchDateType = (type) => {
  currentDateType.value = type;
  emit('dateTypeChange', type);
};

const handlePrev = () => {
  baseDate.value = currentDateType.value === 'week'
    ? baseDate.value.subtract(7, 'day')
    : baseDate.value.subtract(1, 'month');
};

const handleNext = () => {
  if (isNextDisabled.value) return;
  baseDate.value = currentDateType.value === 'week'
    ? baseDate.value.add(7, 'day')
    : baseDate.value.add(1, 'month');
};

// ==================== 双向绑定 ====================

watch(
  [baseDate, currentDateType],
  () => {
    emit('dateRangeChange', {
      startDate: currentDateRange.value.start,
      endDate: currentDateRange.value.end,
    });
  },
  { immediate: true },
);
</script>

<style scoped lang="scss">
.time-select {
  flex-shrink: 0;
  /* 修改: 防止顶部选择器被压缩 */
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;

  // 周月切换 Tab
  .tab-container {
    width: 100%;
    height: 72rpx;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8rpx;
    background-color: #eee;
    border-radius: 12rpx;
    padding: 6rpx;
    box-sizing: border-box;

    .tab-item {
      display: flex;
      align-items: center;
      justify-content: center;
      color: #8c8c8c;
      font-size: 28rpx;
      transition: all 0.2s ease;

      &.active {
        background-color: #ffffff;
        color: #1a1a1a;
        font-weight: 600;
        border-radius: 8rpx;
        box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
      }
    }
  }

  // 日期翻页器
  .date-stepper {
    margin-top: 32rpx;
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;

    .date-display {
      margin: 0 40rpx;
      font-size: 28rpx;
      font-weight: 500;
      color: #333;
      font-variant-numeric: tabular-nums; // 防止数字切换时抖动
    }

    .arrow-icon {
      width: 56rpx;
      height: 56rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      background-color: #fff;
      border-radius: 50%;
      transition: background-color 0.2s;
    }
  }

  .occupy {
    width: 56rpx;
    height: 56rpx;
  }
}
</style>