319 lines
8.0 KiB
Vue
319 lines
8.0 KiB
Vue
<template>
|
||
<div class="date-picker-core">
|
||
<!-- 日期/周/月 选择区 -->
|
||
<div class="picker-container">
|
||
<!-- 日模式 -->
|
||
<div v-if="mode === 'Daily'" class="daily-picker">
|
||
<HorizontalDateList>
|
||
<button
|
||
style="width: 36px"
|
||
v-for="day in dailyDays"
|
||
:key="day.dateStr"
|
||
:class="{ 'selected': day.dateStr === selectedDate }"
|
||
@click="handleSelect(day.dateStr)"
|
||
>{{ day.day }}</button>
|
||
</HorizontalDateList>
|
||
</div>
|
||
|
||
<!-- 周模式 -->
|
||
<div v-else-if="mode === 'Weekly'" class="weekly-picker">
|
||
<HorizontalDateList>
|
||
<button
|
||
style="margin: 0 30px; width: 105px"
|
||
v-for="week in weeklyRanges"
|
||
:key="week.start"
|
||
:class="{ 'selected': week.start === selectedDate[0] && week.end === selectedDate[1] }"
|
||
@click="handleSelect([week.start, week.end])"
|
||
>{{ week.label }}</button>
|
||
</HorizontalDateList>
|
||
</div>
|
||
|
||
<!-- 月模式 -->
|
||
<div v-else class="monthly-picker">
|
||
<HorizontalDateList>
|
||
<button
|
||
style="margin: 0 9px; width: 55px"
|
||
v-for="(monthAbbr, index) in monthAbbrs"
|
||
:key="index"
|
||
:class="{ 'selected': (index + 1) === selectedMonth }"
|
||
@click="handleMonthSelect(index + 1)"
|
||
>{{ monthAbbr }}</button>
|
||
</HorizontalDateList>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import HorizontalDateList from '@/components/HorizontalDateList.vue';
|
||
|
||
export default {
|
||
name: 'OptionDates',
|
||
components: {
|
||
HorizontalDateList
|
||
},
|
||
props: {
|
||
// 当前年份,由外部传入
|
||
year: {
|
||
type: Number,
|
||
required: true
|
||
},
|
||
// 当前模式,由外部传入 (Daily, Weekly, Monthly)
|
||
mode: {
|
||
type: String,
|
||
required: true,
|
||
validator: (val) => ['Daily', 'Weekly', 'Monthly'].includes(val)
|
||
},
|
||
// 日模式下的当前月份,由外部传入
|
||
month: {
|
||
type: Number,
|
||
default: () => new Date().getMonth() + 1,
|
||
validator: (val) => val >= 1 && val <= 12
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
selectedDate: this.mode === 'Daily' ? '' : ['', ''], // 选中的日期/日期范围
|
||
selectedMonth: this.month // 月模式下选中的月份
|
||
};
|
||
},
|
||
computed: {
|
||
// 日模式:生成"当前年月"的所有日期
|
||
dailyDays() {
|
||
const daysInMonth = this.getDaysInMonth(this.year, this.month);
|
||
return Array.from({ length: daysInMonth }, (_, i) => {
|
||
const day = i + 1;
|
||
const date = new Date(this.year, this.month - 1, day);
|
||
return {
|
||
day,
|
||
dateStr: this.formatDate(date)
|
||
};
|
||
});
|
||
},
|
||
|
||
// 周模式:生成"当前年份"的所有完整周区间
|
||
weeklyRanges() {
|
||
return this.getWeekRanges(this.year);
|
||
},
|
||
|
||
// 月模式:12个月份的英文简写
|
||
monthAbbrs() {
|
||
return ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
}
|
||
},
|
||
created() {
|
||
// 组件创建时自动选中当前日期或周
|
||
this.autoSelectCurrentDate();
|
||
},
|
||
methods: {
|
||
// 自动选中当前日期或周
|
||
autoSelectCurrentDate(shouldEmit = true) {
|
||
const now = new Date();
|
||
const currentYear = now.getFullYear();
|
||
const currentMonth = now.getMonth() + 1;
|
||
const currentDateStr = this.formatDate(now);
|
||
|
||
// 只有当年份和月份匹配当前日期时才自动选中
|
||
if (this.year === currentYear) {
|
||
if (this.mode === 'Daily' && this.month === currentMonth) {
|
||
this.selectedDate = currentDateStr;
|
||
if (shouldEmit) {
|
||
this.$emit('select', currentDateStr);
|
||
}
|
||
} else if (this.mode === 'Weekly') {
|
||
// 找到当前日期所在的周
|
||
const currentWeek = this.getWeekForDate(now);
|
||
if (currentWeek) {
|
||
this.selectedDate = [currentWeek.start, currentWeek.end];
|
||
if (shouldEmit) {
|
||
this.$emit('select', [currentWeek.start, currentWeek.end]);
|
||
}
|
||
}
|
||
} else if (this.mode === 'Monthly') {
|
||
// 月模式下自动选中当前月并导出日期范围
|
||
this.selectedMonth = currentMonth;
|
||
const [start, end] = this.getMonthRange(this.year, currentMonth);
|
||
if (shouldEmit) {
|
||
this.$emit('select', [start, end]);
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
// 获取指定日期所在的周
|
||
getWeekForDate(date) {
|
||
const weeks = this.getWeekRanges(this.year);
|
||
const dateStr = this.formatDate(date);
|
||
|
||
for (const week of weeks) {
|
||
// 检查日期是否在该周范围内
|
||
if (dateStr >= week.start && dateStr <= week.end) {
|
||
return week;
|
||
}
|
||
}
|
||
return null;
|
||
},
|
||
|
||
// 处理日/周模式的选择
|
||
handleSelect(value) {
|
||
this.selectedDate = value;
|
||
this.$emit('select', value);
|
||
},
|
||
|
||
// 处理月模式的选择
|
||
handleMonthSelect(month) {
|
||
this.selectedMonth = month;
|
||
const [start, end] = this.getMonthRange(this.year, month);
|
||
this.$emit('select', [start, end]);
|
||
},
|
||
|
||
// 辅助:获取某月的天数(month:1-12)
|
||
getDaysInMonth(year, month) {
|
||
return new Date(year, month, 0).getDate();
|
||
},
|
||
|
||
// 辅助:格式化日期为 yyyy-mm-dd
|
||
formatDate(date) {
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
return `${year}-${month}-${day}`;
|
||
},
|
||
|
||
// 辅助:获取某月的首尾日期(month:1-12)
|
||
getMonthRange(year, month) {
|
||
const start = new Date(year, month - 1, 1);
|
||
const end = new Date(year, month, 0);
|
||
return [this.formatDate(start), this.formatDate(end)];
|
||
},
|
||
|
||
// 辅助:获取某年的所有完整周区间(假设周从周日开始,到周六结束)
|
||
getWeekRanges(year) {
|
||
const ranges = [];
|
||
let currentDate = new Date(year, 0, 1); // 当年1月1日
|
||
|
||
// 找到第一个周日(作为周起始)
|
||
while (currentDate.getDay() !== 0) {
|
||
currentDate.setDate(currentDate.getDate() + 1);
|
||
}
|
||
|
||
// 生成所有"结束日期在当年"的完整周
|
||
while (currentDate.getFullYear() === year) {
|
||
const start = new Date(currentDate);
|
||
const end = new Date(currentDate);
|
||
end.setDate(end.getDate() + 6); // 周日到周六共7天
|
||
|
||
if (end.getFullYear() === year) { // 确保周结束在当年
|
||
ranges.push({
|
||
label: `${this.monthAbbrs[start.getMonth()]} ${start.getDate()}-${end.getDate()}`,
|
||
start: this.formatDate(start),
|
||
end: this.formatDate(end)
|
||
});
|
||
}
|
||
|
||
currentDate.setDate(currentDate.getDate() + 7); // 下一周
|
||
}
|
||
return ranges;
|
||
},
|
||
// 重置选择状态
|
||
resetSelection() {
|
||
if (this.mode === 'Daily') {
|
||
this.selectedDate = '';
|
||
} else if (this.mode === 'Weekly') {
|
||
this.selectedDate = ['', ''];
|
||
} else {
|
||
this.selectedMonth = this.month;
|
||
}
|
||
}
|
||
},
|
||
watch: {
|
||
// 监听年份变化,重置选择
|
||
year() {
|
||
this.resetSelection();
|
||
this.autoSelectCurrentDate();
|
||
},
|
||
mode: {
|
||
handler(newMode, oldMode) {
|
||
if (newMode !== oldMode) {
|
||
this.resetSelection();
|
||
// 切换模式时不立即触发 emit,让组件自行处理
|
||
this.autoSelectCurrentDate(false);
|
||
// 通知子组件重新计算滚动状态
|
||
this.$nextTick(() => {
|
||
this.$children.forEach(child => {
|
||
if (child.$options.name === 'HorizontalDateList') {
|
||
child.updateScrollState();
|
||
}
|
||
});
|
||
});
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
month() {
|
||
if (this.mode === 'Daily') {
|
||
this.selectedDate = '';
|
||
}
|
||
this.autoSelectCurrentDate();
|
||
}
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.date-picker-core {
|
||
padding: 10px;
|
||
}
|
||
|
||
.picker-container {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
padding: 12px;
|
||
border-radius: 4px;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
background-color: #ffffff;
|
||
}
|
||
|
||
.daily-picker,
|
||
.weekly-picker,
|
||
.monthly-picker {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
width: 100%;
|
||
}
|
||
|
||
.daily-picker button,
|
||
.weekly-picker button,
|
||
.monthly-picker button {
|
||
height: 36px;
|
||
text-align: center;
|
||
border: 1px solid #E2E8F0;
|
||
border-radius: 12px;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-size: 18px;
|
||
line-height: 22px;
|
||
color: #506179;
|
||
&:active {
|
||
opacity: 0.8;
|
||
}
|
||
}
|
||
|
||
.daily-picker button:hover,
|
||
.weekly-picker button:hover,
|
||
.monthly-picker button:hover {
|
||
border-color: #9ca3af;
|
||
}
|
||
|
||
.daily-picker button.selected,
|
||
.weekly-picker button.selected,
|
||
.monthly-picker button.selected {
|
||
color: #fff;
|
||
background: linear-gradient( 47deg, #2563EB 4%, #7B61FF 73%);
|
||
}
|
||
</style>
|