Files
YGChatCS/components/Calender/README.md
2025-08-02 13:59:29 +08:00

548 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 日历组件 (Calendar Component)
## 功能需求分析
基于设计图片分析,该日历组件是一个酒店预订场景的低价日历弹窗,支持通过日期图标点击触发显示,需要实现以下细分功能:
## 交互使用方式
### 基础用法
通过点击日期图标打开日历组件:
```vue
<template>
<view class="date-picker" @tap="openCalendar">
<view class="date-display">
<text class="date-label">选择日期</text>
<text class="date-value" v-if="selectedDate">{{ selectedDate }}</text>
<text class="date-placeholder" v-else>请点击日期图标选择</text>
</view>
<view class="date-icon">
<uni-icons type="calendar-filled" size="24" color="#1890ff"></uni-icons>
</view>
</view>
<!-- 日历组件 -->
<Calendar
:visible="calendarVisible"
:price-data="priceData"
mode="single"
@close="handleCalendarClose"
@select="handleDateSelect"
/>
</template>
<script setup>
import { ref } from 'vue'
import Calendar from './index.vue'
const calendarVisible = ref(false)
const selectedDate = ref('')
const openCalendar = () => {
calendarVisible.value = true
}
const handleCalendarClose = () => {
calendarVisible.value = false
}
const handleDateSelect = (data) => {
selectedDate.value = data.date
calendarVisible.value = false
}
</script>
```
### 演示文件
- `year-demo.vue` - 全年日历演示(推荐)
- `demo.vue` - 完整的交互演示页面
- `example.vue` - 详细的使用示例,包含单选和范围选择
### 跨年日历
组件支持显示从当前月份到明年同月份的13个月日历用户可以连续选择入住和离店日期
1. 点击第一个日期设置入住日期
2. 点击第二个日期设置离店日期
3. 自动显示"入住"和"离店"标签
4. 支持滚动浏览跨年日期范围
### 1. 弹窗容器结构
#### 1.1 弹窗基础框架
- [ ] 实现遮罩层背景(半透明黑色)
- [ ] 弹窗居中显示,支持垂直居中
- [ ] 弹窗圆角设计建议12px
- [ ] 弹窗阴影效果
- [ ] 弹窗动画效果(淡入淡出)
#### 1.2 弹窗头部区域
- [ ] 标题文字:"低价日历"
- [ ] 副标题文字:"以下价格为单晚参考价"
- [ ] 右上角关闭按钮X图标
- [ ] 关闭按钮点击交互
- [ ] 头部区域背景色和分割线
### 2. 日历主体结构
#### 2.1 周标题行
- [ ] 显示周几标题:一、二、三、四、五、六、日
- [ ] 周标题居中对齐
- [ ] 周标题字体样式(灰色、小字号)
#### 2.2 月份导航
- [ ] 月份标题显示:"2024年5月"、"2024年6月"
- [ ] 月份标题居中显示
- [ ] 月份标题字体加粗
- [ ] 支持上下滑动切换月份(可选)
### 3. 日期网格系统
#### 3.1 日期格子基础
- [ ] 7列网格布局对应周一到周日
- [ ] 每个格子固定宽高比
- [ ] 格子间距设计
- [ ] 格子圆角设计
#### 3.2 日期内容显示
- [ ] 日期数字显示(居中对齐)
- [ ] 价格信息显示¥449格式
- [ ] 日期数字字体大小和颜色
- [ ] 价格字体大小和颜色(较小、灰色)
#### 3.3 日期状态管理
- [ ] 普通可选日期(白色背景)
- [ ] 当前选中日期(蓝色背景,白色文字)
- [ ] 入住日期标记("入住"标签)
- [ ] 离店日期标记("离店"标签)
- [ ] 选择范围内日期(浅蓝色背景)
- [ ] 不可选日期(灰色背景,禁用状态)
### 4. 交互功能实现
#### 4.1 日期选择逻辑
- [ ] 单击日期选择功能
- [ ] 日期范围选择(入住-离店)
- [ ] 选择状态视觉反馈
- [ ] 选择完成后的回调事件
#### 4.2 用户体验优化
- [ ] 点击动画效果
- [ ] 触摸反馈
- [ ] 防止快速重复点击
- [ ] 选择范围的视觉连接线(可选)
### 5. 数据处理功能
#### 5.1 价格数据管理
- [ ] 价格数据结构设计
- [ ] 价格数据绑定到日期
- [ ] 价格格式化显示
- [ ] 无价格数据的处理
#### 5.2 日期计算功能
- [ ] 月份天数计算
- [ ] 月份第一天星期几计算
- [ ] 跨月份日期处理
- [ ] 日期有效性验证
### 6. 响应式适配
#### 6.1 移动端适配
- [ ] 触摸屏操作优化
- [ ] 不同屏幕尺寸适配
- [ ] 横竖屏切换适配
- [ ] 安全区域适配(刘海屏等)
#### 6.2 字体和尺寸适配
- [ ] 字体大小响应式调整
- [ ] 格子尺寸响应式调整
- [ ] 间距响应式调整
## 技术实现细节
### 组件接口设计
#### Props 属性
```javascript
props: {
// 弹窗显示控制
visible: {
type: Boolean,
default: false,
required: true
},
// 价格数据对象
priceData: {
type: Object,
default: () => ({}),
// 格式: { '2024-05-17': 449, '2024-05-18': 399 }
},
// 默认选中日期
defaultValue: {
type: [String, Array],
default: '',
// 单选: '2024-05-17'
// 范围选择: ['2024-05-17', '2024-05-19']
},
// 选择模式
mode: {
type: String,
default: 'single',
validator: (value) => ['single', 'range'].includes(value)
},
// 最小可选日期
minDate: {
type: String,
default: () => new Date().toISOString().split('T')[0]
},
// 最大可选日期
maxDate: {
type: String,
default: ''
},
// 禁用日期数组
disabledDates: {
type: Array,
default: () => []
},
// 自定义标签
customLabels: {
type: Object,
default: () => ({})
// 格式: { '2024-05-17': '入住', '2024-05-19': '离店' }
}
}
```
#### Events 事件
```javascript
// 日期选择事件
this.$emit('select', {
date: '2024-05-17',
price: 449,
mode: 'single'
})
// 范围选择事件
this.$emit('range-select', {
startDate: '2024-05-17',
endDate: '2024-05-19',
startPrice: 449,
endPrice: 399,
totalDays: 2
})
// 弹窗关闭事件
this.$emit('close')
// 月份切换事件
this.$emit('month-change', {
year: 2024,
month: 5,
direction: 'next' // 'prev' | 'next'
})
// 日期点击事件(包含所有点击)
this.$emit('date-click', {
date: '2024-05-17',
price: 449,
disabled: false,
selected: true
})
```
### 核心算法实现
#### 日期计算工具函数
```javascript
// 获取月份天数
getDaysInMonth(year, month) {
return new Date(year, month, 0).getDate()
}
// 获取月份第一天是星期几
getFirstDayOfMonth(year, month) {
return new Date(year, month - 1, 1).getDay()
}
// 生成日期网格数据
generateCalendarGrid(year, month) {
const daysInMonth = this.getDaysInMonth(year, month)
const firstDay = this.getFirstDayOfMonth(year, month)
const grid = []
// 填充空白格子
for (let i = 0; i < firstDay; i++) {
grid.push(null)
}
// 填充日期
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
grid.push({
date: dateStr,
day: day,
price: this.priceData[dateStr] || null,
disabled: this.isDateDisabled(dateStr),
selected: this.isDateSelected(dateStr),
inRange: this.isDateInRange(dateStr),
label: this.customLabels[dateStr] || ''
})
}
return grid
}
```
#### 选择状态管理
```javascript
data() {
return {
selectedDates: [],
currentMonth: new Date().getMonth() + 1,
currentYear: new Date().getFullYear(),
isRangeSelecting: false,
rangeStart: null,
rangeEnd: null
}
},
methods: {
handleDateClick(dateInfo) {
if (dateInfo.disabled) return
if (this.mode === 'single') {
this.selectedDates = [dateInfo.date]
this.$emit('select', dateInfo)
} else if (this.mode === 'range') {
this.handleRangeSelection(dateInfo)
}
},
handleRangeSelection(dateInfo) {
if (!this.rangeStart || (this.rangeStart && this.rangeEnd)) {
// 开始新的范围选择
this.rangeStart = dateInfo.date
this.rangeEnd = null
this.isRangeSelecting = true
} else {
// 完成范围选择
this.rangeEnd = dateInfo.date
this.isRangeSelecting = false
// 确保开始日期小于结束日期
if (new Date(this.rangeStart) > new Date(this.rangeEnd)) {
[this.rangeStart, this.rangeEnd] = [this.rangeEnd, this.rangeStart]
}
this.$emit('range-select', {
startDate: this.rangeStart,
endDate: this.rangeEnd,
startPrice: this.priceData[this.rangeStart],
endPrice: this.priceData[this.rangeEnd],
totalDays: this.calculateDaysBetween(this.rangeStart, this.rangeEnd)
})
}
}
}
```
### 样式设计规范
#### 颜色系统
```scss
// 主色调
$primary-color: #1890ff;
$primary-light: #e6f7ff;
$primary-dark: #0050b3;
// 中性色
$text-primary: #262626;
$text-secondary: #8c8c8c;
$text-disabled: #bfbfbf;
$background-white: #ffffff;
$background-gray: #f5f5f5;
$border-color: #d9d9d9;
// 状态色
$success-color: #52c41a;
$warning-color: #faad14;
$error-color: #ff4d4f;
```
#### 尺寸规范
```scss
// 弹窗尺寸
$modal-width: 350px;
$modal-max-height: 80vh;
$modal-border-radius: 12px;
$modal-padding: 20px;
// 日期格子尺寸
$date-cell-size: 44px;
$date-cell-gap: 2px;
$date-cell-border-radius: 6px;
// 字体大小
$font-size-title: 18px;
$font-size-subtitle: 14px;
$font-size-date: 16px;
$font-size-price: 12px;
$font-size-label: 10px;
```
## 开发实施计划
### 第一阶段基础框架1-2天
- [ ] 创建组件基础结构
- [ ] 实现弹窗容器和遮罩
- [ ] 添加头部区域和关闭功能
- [ ] 建立基础样式系统
### 第二阶段日历核心2-3天
- [ ] 实现日期计算算法
- [ ] 构建日期网格布局
- [ ] 添加周标题和月份显示
- [ ] 实现基础日期显示
### 第三阶段交互功能2-3天
- [ ] 实现日期选择逻辑
- [ ] 添加范围选择功能
- [ ] 实现状态管理和视觉反馈
- [ ] 添加价格数据绑定
### 第四阶段优化完善1-2天
- [ ] 添加动画效果
- [ ] 优化移动端体验
- [ ] 完善边界情况处理
- [ ] 性能优化和测试
## 使用示例
### 基础用法
```vue
<template>
<div>
<button @click="showCalendar = true">选择日期</button>
<Calendar
:visible="showCalendar"
:price-data="priceData"
mode="range"
@range-select="handleRangeSelect"
@close="showCalendar = false"
/>
</div>
</template>
<script>
import Calendar from '@/components/Calendar'
export default {
components: {
Calendar
},
data() {
return {
showCalendar: false,
priceData: {
'2024-05-17': 449,
'2024-05-18': 399,
'2024-05-19': 459,
'2024-05-20': 429
}
}
},
methods: {
handleRangeSelect(range) {
console.log('选择范围:', range)
this.showCalendar = false
// 处理选择结果
this.processBooking(range)
},
processBooking(range) {
// 处理预订逻辑
const { startDate, endDate, totalDays } = range
// ...
}
}
}
</script>
```
### 高级用法
```vue
<template>
<Calendar
:visible="visible"
:price-data="priceData"
:custom-labels="customLabels"
:disabled-dates="disabledDates"
:min-date="minDate"
:max-date="maxDate"
mode="range"
@range-select="handleSelect"
@month-change="handleMonthChange"
@close="handleClose"
/>
</template>
<script>
export default {
data() {
return {
visible: false,
priceData: {
// 动态价格数据
},
customLabels: {
'2024-05-17': '入住',
'2024-05-19': '离店'
},
disabledDates: [
'2024-05-16', // 已满房
'2024-05-25' // 维护日
],
minDate: '2024-05-01',
maxDate: '2024-12-31'
}
},
methods: {
handleMonthChange(monthInfo) {
// 动态加载月份数据
this.loadMonthData(monthInfo.year, monthInfo.month)
},
async loadMonthData(year, month) {
// 从API获取价格数据
const data = await this.fetchPriceData(year, month)
this.priceData = { ...this.priceData, ...data }
}
}
}
</script>
```
## 测试用例
### 单元测试
- [ ] 日期计算函数测试
- [ ] 选择逻辑测试
- [ ] 价格数据绑定测试
- [ ] 边界条件测试
### 集成测试
- [ ] 用户交互流程测试
- [ ] 不同设备适配测试
- [ ] 性能压力测试
### 可访问性测试
- [ ] 键盘导航测试
- [ ] 屏幕阅读器兼容性
- [ ] 色彩对比度检查