269 lines
6.2 KiB
Vue
269 lines
6.2 KiB
Vue
<template>
|
||
<view class="demo-container">
|
||
<view class="demo-header">
|
||
<text class="demo-title">跨年日历演示</text>
|
||
<text class="demo-subtitle">支持从当前月份到明年同月份的日期连续选择</text>
|
||
</view>
|
||
|
||
<view class="demo-content">
|
||
<!-- 触发按钮 -->
|
||
<view class="trigger-section">
|
||
<view class="date-input" @tap="openCalendar">
|
||
<view class="date-icon">📅</view>
|
||
<view class="date-text">
|
||
<text v-if="!selectedRange.start && !selectedRange.end" class="placeholder">
|
||
选择入住和离店日期
|
||
</text>
|
||
<text v-else class="selected-text">
|
||
{{ formatDateRange() }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 选择结果显示 -->
|
||
<view class="result-section" v-if="selectedRange.start || selectedRange.end">
|
||
<view class="result-title">选择结果:</view>
|
||
<view class="result-item" v-if="selectedRange.start">
|
||
<text class="result-label">入住日期:</text>
|
||
<text class="result-value">{{ formatDate(selectedRange.start) }}</text>
|
||
</view>
|
||
<view class="result-item" v-if="selectedRange.end">
|
||
<text class="result-label">离店日期:</text>
|
||
<text class="result-value">{{ formatDate(selectedRange.end) }}</text>
|
||
</view>
|
||
<view class="result-item" v-if="selectedRange.start && selectedRange.end">
|
||
<text class="result-label">住宿天数:</text>
|
||
<text class="result-value">{{ calculateDays() }}晚</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 日历组件 -->
|
||
<Calender
|
||
:visible="calendarVisible"
|
||
mode="range"
|
||
:price-data="priceData"
|
||
:min-date="minDate"
|
||
@close="handleCalendarClose"
|
||
@select="handleDateSelect"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
import Calender from './index.vue'
|
||
|
||
// 响应式数据
|
||
const calendarVisible = ref(false)
|
||
const selectedRange = ref({
|
||
start: '',
|
||
end: ''
|
||
})
|
||
|
||
// 最小日期(今天)
|
||
const minDate = new Date().toISOString().split('T')[0]
|
||
|
||
// 动态生成价格数据(从当前月份到明年同月份)
|
||
const generatePriceData = () => {
|
||
const priceData = {};
|
||
const now = new Date();
|
||
const currentYear = now.getFullYear();
|
||
const currentMonth = now.getMonth() + 1;
|
||
|
||
// 生成13个月的示例价格数据
|
||
for (let i = 0; i < 13; i++) {
|
||
const month = ((currentMonth - 1 + i) % 12) + 1;
|
||
const year = currentYear + Math.floor((currentMonth - 1 + i) / 12);
|
||
|
||
// 为每个月生成几个示例价格
|
||
const sampleDates = [1, 15, 25]; // 每月的1号、15号、25号
|
||
sampleDates.forEach(day => {
|
||
if (day <= new Date(year, month, 0).getDate()) { // 确保日期存在
|
||
const dateKey = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
|
||
// 生成随机价格(299-1599之间)
|
||
priceData[dateKey] = Math.floor(Math.random() * 1300) + 299;
|
||
}
|
||
});
|
||
}
|
||
|
||
return priceData;
|
||
};
|
||
|
||
const priceData = reactive(generatePriceData());
|
||
|
||
// 打开日历
|
||
const openCalendar = () => {
|
||
calendarVisible.value = true
|
||
}
|
||
|
||
// 关闭日历
|
||
const handleCalendarClose = () => {
|
||
calendarVisible.value = false
|
||
}
|
||
|
||
// 处理日期选择
|
||
const handleDateSelect = (dates) => {
|
||
if (dates.length >= 2) {
|
||
selectedRange.value = {
|
||
start: dates[0],
|
||
end: dates[1]
|
||
}
|
||
} else if (dates.length === 1) {
|
||
selectedRange.value = {
|
||
start: dates[0],
|
||
end: ''
|
||
}
|
||
} else {
|
||
selectedRange.value = {
|
||
start: '',
|
||
end: ''
|
||
}
|
||
}
|
||
}
|
||
|
||
// 格式化日期
|
||
const formatDate = (dateStr) => {
|
||
if (!dateStr) return ''
|
||
const date = new Date(dateStr)
|
||
const month = date.getMonth() + 1
|
||
const day = date.getDate()
|
||
const weekDays = ['日', '一', '二', '三', '四', '五', '六']
|
||
const weekDay = weekDays[date.getDay()]
|
||
return `${month}月${day}日 周${weekDay}`
|
||
}
|
||
|
||
// 格式化日期范围
|
||
const formatDateRange = () => {
|
||
if (selectedRange.value.start && selectedRange.value.end) {
|
||
return `${formatDate(selectedRange.value.start)} - ${formatDate(selectedRange.value.end)}`
|
||
} else if (selectedRange.value.start) {
|
||
return `${formatDate(selectedRange.value.start)} - 选择离店日期`
|
||
}
|
||
return ''
|
||
}
|
||
|
||
// 计算住宿天数
|
||
const calculateDays = () => {
|
||
if (!selectedRange.value.start || !selectedRange.value.end) return 0
|
||
const start = new Date(selectedRange.value.start)
|
||
const end = new Date(selectedRange.value.end)
|
||
const diffTime = Math.abs(end - start)
|
||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||
return diffDays
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.demo-container {
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.demo-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
|
||
.demo-title {
|
||
display: block;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.demo-subtitle {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
}
|
||
|
||
.demo-content {
|
||
max-width: 400px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.trigger-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.date-input {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px;
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
|
||
&:hover {
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
&:active {
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.date-icon {
|
||
font-size: 24px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.date-text {
|
||
flex: 1;
|
||
|
||
.placeholder {
|
||
color: #999;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.selected-text {
|
||
color: #333;
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.result-section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 16px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.result-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.result-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
|
||
.result-label {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.result-value {
|
||
color: #333;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
</style> |