Requirement Analysis
In certain mobile application scenarios, displaying a full monthlly calendar view consumes excessive vertical space. A weekly strip offers a more compact alternative while maintaining necessary date selection functionality. The following implementation outlines a reusable Vue compnoent designed to display a seven-day window with navigation capabilities.
Component Implemantation
Create a new file named WeeklyCalendar.vue. This component utilizes the Vue 3 Composition API for state management and lifecycle handling. It includes a header for navigation, a row for weekday labels, and a row for selectable dates.
<template>
<div class="weekly-calendar">
<header class="calendar-nav">
<span class="date-label">{{ displayYear }}/{{ displayMonth }}</span>
<div class="controls">
<svg class="icon-btn" @click="navigatePrevious" viewBox="0 0 24 24">
<path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
<svg class="icon-btn" @click="navigateNext" viewBox="0 0 24 24">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
</div>
</header>
<div class="calendar-body">
<div class="weekdays">
<span v-for="(day, idx) in weekDayLabels" :key="idx" class="weekday-label">
{{ day }}
</span>
</div>
<div class="dates">
<span
v-for="(item, idx) in currentWeekDates"
:key="idx"
class="date-item"
:class="{ active: selectedTimestamp === item.timestamp, isToday: todayTimestamp === item.timestamp }"
@click="handleDateSelection(item)"
>
{{ todayTimestamp === item.timestamp ? 'Today' : item.dayNumber }}
</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
const WEEK_DAY_LABELS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const state = reactive({
displayYear: '',
displayMonth: '',
todayTimestamp: 0,
selectedTimestamp: 0,
weekDayLabels: WEEK_DAY_LABELS,
currentWeekDates: []
});
const { displayYear, displayMonth, todayTimestamp, selectedTimestamp, weekDayLabels, currentWeekDates } = state;
const emit = defineEmits(['date-change']);
onMounted(() => {
initializeCalendar();
});
const initializeCalendar = () => {
const now = new Date();
const nowTime = now.getTime();
updateDateDisplay(nowTime);
state.todayTimestamp = nowTime;
state.selectedTimestamp = nowTime;
// Calculate start of week (Monday)
const dayOfWeek = now.getDay();
const diffToMonday = now.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1);
const mondayTime = new Date(now.setDate(diffToMonday)).getTime();
generateWeekDays(mondayTime);
// Trigger initial selection
handleDateSelection({
timestamp: state.todayTimestamp,
dayNumber: new Date(state.todayTimestamp).getDate()
});
};
const updateDateDisplay = (time) => {
const date = new Date(time);
state.displayYear = date.getFullYear();
const month = date.getMonth() + 1;
state.displayMonth = month < 10 ? `0${month}` : `${month}`;
};
const generateWeekDays = (startTimestamp) => {
updateDateDisplay(startTimestamp);
const weekData = [];
for (let i = 0; i < 7; i++) {
const currentDayTime = startTimestamp + (i * 24 * 60 * 60 * 1000);
const dateObj = new Date(currentDayTime);
weekData.push({
timestamp: currentDayTime,
dayNumber: dateObj.getDate()
});
}
state.currentWeekDates = weekData;
};
const navigatePrevious = () => {
if (state.currentWeekDates.length > 0) {
const prevStart = state.currentWeekDates[0].timestamp - (7 * 24 * 60 * 60 * 1000);
generateWeekDays(prevStart);
}
};
const navigateNext = () => {
if (state.currentWeekDates.length > 0) {
const nextStart = state.currentWeekDates[0].timestamp + (7 * 24 * 60 * 60 * 1000);
generateWeekDays(nextStart);
}
};
const handleDateSelection = (item) => {
updateDateDisplay(item.timestamp);
state.selectedTimestamp = item.timestamp;
const day = item.dayNumber < 10 ? `0${item.dayNumber}` : item.dayNumber;
const formattedDate = `${state.displayYear}-${state.displayMonth}-${day}`;
emit('date-change', formattedDate);
};
</script>
<style scoped lang="less">
.weekly-calendar {
font-size: 14px;
background: #fff;
color: #666;
padding: 12px;
border-radius: 8px;
.calendar-nav {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
color: #333;
font-weight: 600;
font-size: 16px;
.controls {
display: flex;
gap: 12px;
.icon-btn {
width: 20px;
height: 20px;
cursor: pointer;
&:active {
opacity: 0.6;
}
}
}
}
.calendar-body {
.weekdays, .dates {
display: flex;
justify-content: space-between;
text-align: center;
span {
flex: 1;
}
}
.weekdays {
color: #333;
font-size: 15px;
margin-bottom: 8px;
}
.dates {
.date-item {
color: #999;
padding: 8px 0;
cursor: pointer;
transition: color 0.2s;
&.isToday {
color: #333;
font-weight: 600;
}
&.active {
color: #e74c3c;
font-weight: bold;
background: rgba(231, 76, 60, 0.1);
border-radius: 4px;
}
}
}
}
}
</style>
Integration Guide
Import the component into the parent view and listen for the date-change event to retrieve the selected date string.
<template>
<div class="app-container">
<WeeklyCalendar @date-change="processSelectedDate" />
</div>
</template>
<script setup>
import WeeklyCalendar from './components/WeeklyCalendar.vue';
const processSelectedDate = (dateString) => {
console.log('Selected Date:', dateString);
// Proceed with API calls or logic using dateString (YYYY-MM-DD)
};
</script>