424 lines
11 KiB
Vue
424 lines
11 KiB
Vue
<template>
|
||
<div class="date-picker-core">
|
||
<!-- 日期/周/月 选择区 -->
|
||
<div class="picker-container">
|
||
<!-- 日模式 -->
|
||
<div v-if="mode === 'Daily'" class="daily-picker">
|
||
<HorizontalDateList>
|
||
<button
|
||
class="bt-d"
|
||
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
|
||
class="bt-w"
|
||
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
|
||
class="bt-m"
|
||
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';
|
||
|
||
// 从 sessionStorage 获取缓存数据的辅助函数
|
||
function getCachedData() {
|
||
try {
|
||
const cachedData = sessionStorage.getItem('launches_search_cache');
|
||
return cachedData ? JSON.parse(cachedData) : null;
|
||
} catch (e) {
|
||
// console.error('获取缓存数据失败', e);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
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() {
|
||
const cachedData = getCachedData();
|
||
let initialSelectedDate = this.mode === 'Daily' ? '' : ['', ''];
|
||
let initialSelectedMonth = this.month;
|
||
|
||
// 改进:优先从缓存中获取月份
|
||
if (cachedData && cachedData.currentMonth) {
|
||
initialSelectedMonth = cachedData.currentMonth;
|
||
}
|
||
|
||
// 如果有缓存数据,优先使用缓存数据
|
||
if (cachedData && cachedData.lastSelectedValue) {
|
||
try {
|
||
const lastSelectedValue = JSON.parse(cachedData.lastSelectedValue);
|
||
if (lastSelectedValue) {
|
||
if (this.mode === 'Daily' && typeof lastSelectedValue === 'string') {
|
||
initialSelectedDate = lastSelectedValue;
|
||
} else if (this.mode !== 'Daily' && Array.isArray(lastSelectedValue)) {
|
||
initialSelectedDate = lastSelectedValue;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('解析缓存的lastSelectedValue失败', e);
|
||
}
|
||
}
|
||
|
||
return {
|
||
selectedDate: initialSelectedDate,
|
||
selectedMonth: initialSelectedMonth,
|
||
};
|
||
},
|
||
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() {
|
||
// 先从props获取月份,避免初始为null
|
||
this.selectedMonth = this.month;
|
||
|
||
// 立即尝试获取缓存数据
|
||
this.updateFromCache();
|
||
|
||
// 添加一个小延迟再次尝试,确保父组件的loadSearchCache执行完毕
|
||
setTimeout(() => {
|
||
this.updateFromCache();
|
||
// 强制更新UI,确保选中状态正确显示
|
||
this.$forceUpdate();
|
||
}, 100);
|
||
},
|
||
methods: {
|
||
updateFromCache() {
|
||
const cachedData = getCachedData();
|
||
|
||
if (cachedData) {
|
||
// 优先从缓存获取currentMonth
|
||
if (cachedData.currentMonth) {
|
||
this.selectedMonth = cachedData.currentMonth;
|
||
}
|
||
|
||
// 处理lastSelectedValue
|
||
if (cachedData.lastSelectedValue) {
|
||
try {
|
||
const lastSelectedValue = JSON.parse(cachedData.lastSelectedValue);
|
||
if (lastSelectedValue) {
|
||
if (this.mode === 'Monthly') {
|
||
// 月模式下,确保selectedMonth和lastSelectedValue一致
|
||
let monthToUse = this.selectedMonth;
|
||
// 如果缓存中有currentMonth,优先使用
|
||
if (cachedData.currentMonth) {
|
||
monthToUse = cachedData.currentMonth;
|
||
this.selectedMonth = monthToUse;
|
||
}
|
||
// 生成与selectedMonth对应的日期范围
|
||
const [start, end] = this.getMonthRange(this.year, monthToUse);
|
||
// 使用$nextTick确保DOM更新后再发出事件
|
||
this.$nextTick(() => {
|
||
this.$emit('select', [start, end]);
|
||
this.$emit('month-change', monthToUse);
|
||
});
|
||
} else if ((this.mode === 'Daily' && typeof lastSelectedValue === 'string') ||
|
||
(this.mode !== 'Daily' && Array.isArray(lastSelectedValue))) {
|
||
this.$emit('select', lastSelectedValue);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('使用缓存的lastSelectedValue失败', e);
|
||
}
|
||
}
|
||
}
|
||
},
|
||
// 自动选中当前日期或周
|
||
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;
|
||
// 强制更新UI
|
||
this.$forceUpdate();
|
||
|
||
const [start, end] = this.getMonthRange(this.year, month);
|
||
// 先发出month-change事件,确保父组件的currentMonth先更新
|
||
this.$emit('month-change', month);
|
||
// 然后发出select事件更新lastSelectedValue,包含正确的时间格式
|
||
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);
|
||
const formatDateWithTime = (date, isStart = true) => {
|
||
const year = date.getFullYear();
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const time = isStart ? '00:00:00' : '23:59:59';
|
||
return `${year}-${month}-${day} ${time}`;
|
||
};
|
||
return [formatDateWithTime(start), formatDateWithTime(end, false)];
|
||
|
||
},
|
||
|
||
// 辅助:获取某年的所有完整周区间(假设周从周日开始,到周六结束)
|
||
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>
|
||
.bt-d {
|
||
width: 36px; margin: 0 10px;
|
||
}
|
||
.bt-w {
|
||
margin: 0 30px; width: 105px;
|
||
}
|
||
.bt-m {
|
||
margin: 0 20px; width: 55px;
|
||
}
|
||
.picker-container {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
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>
|