1. 需求背景与场景分析
在uni-app开发过程中,我们经常需要使用日历组件来实现日期选择功能。官方提供的uni-calendar组件虽然功能完善,但在某些特定场景下需要进一步定制化开发。最近我在一个酒店预订项目中遇到了这样的需求:需要让日历组件默认显示用户上次选择的日期范围,而不是每次都从当前日期开始。
这种需求在各类预订系统、行程规划应用中非常常见。比如:
- 酒店预订:用户上次查询了5月1日-5月3日的房态,下次打开时应该默认显示这个日期范围
- 机票查询:保留用户最近搜索的出发和返回日期
- 报表系统:默认显示上月同期数据对比的时间范围
2. 原组件功能分析
uni-calendar组件默认支持以下特性:
- 单日选择模式(默认)
- 日期范围选择模式(需设置range=true)
- 月份切换功能
- 日期点击事件回调
但缺少一个重要功能:无法在初始化时设置默认选中的日期范围。这导致每次打开日历都需要用户重新选择日期,体验不够友好。
3. 解决方案设计
3.1 核心思路
通过分析组件源码,我们发现可以通过以下方式实现默认日期范围:
- 扩展组件props,接收开始日期(beforeDate)和结束日期(afterDate)参数
- 在组件挂载时(mounted),使用这些参数初始化选中状态
- 修改日期初始化逻辑,避免覆盖我们设置的默认值
3.2 技术选型考量
为什么不直接fork组件进行大改?
- 保持最小修改原则,降低维护成本
- 确保后续可以无缝升级官方组件
- 改动点集中在业务需要的功能上
为什么选择在mounted钩子中初始化?
- 确保DOM已挂载,组件内部日历实例可用
- 早于用户交互执行,视觉上更自然
- 避免在created阶段访问DOM可能引发的问题
4. 具体实现步骤
4.1 组件参数配置
首先在父组件中配置日历组件:
html复制<uni-calendar
:showMonth="false"
:beforeDate="state.beforeDate"
:afterDate="state.afterDate"
:range="true"
@change="changeDay"
/>
对应的script部分:
javascript复制const state = reactive({
beforeDate: '2023-05-01', // 默认开始日期
afterDate: '2023-05-03' // 默认结束日期
})
const changeDay = (e) => {
// 处理日期变化事件
console.log('选择的日期范围:', e)
}
4.2 组件源码修改
找到uni-calendar.vue文件,进行以下关键修改:
- 添加props定义:
javascript复制props: {
beforeDate: {
type: String,
default: ''
},
afterDate: {
type: String,
default: ''
}
},
- 修改mounted钩子:
javascript复制mounted() {
// 初始化默认选中日期
if(this.beforeDate) {
this.cale.setMultiple(this.beforeDate)
this.weeks = this.cale.weeks
}
if(this.afterDate) {
this.cale.setMultiple(this.afterDate)
this.weeks = this.cale.weeks
}
},
- 调整init方法:
javascript复制init(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.calendar = this.cale.getInfo(date)
// 注释掉原来的setMultiple调用
// this.cale.setMultiple(this.nowDate)
},
4.3 日期格式处理
在实际项目中,我们需要注意日期格式的统一。uni-calendar组件内部使用的是YYYY-MM-DD格式,因此:
- 确保传入的beforeDate/afterDate也是相同格式
- 可以使用day.js等库进行日期格式化:
javascript复制import dayjs from 'dayjs'
const state = reactive({
beforeDate: dayjs().format('YYYY-MM-DD'),
afterDate: dayjs().add(2, 'day').format('YYYY-MM-DD')
})
5. 实现效果验证
修改完成后,组件会:
- 初始化时自动选中beforeDate和afterDate指定的日期
- 保持原有的范围选择功能
- 正确触发change事件返回选择结果
可以通过以下方式验证:
- 检查DOM渲染,确认指定日期有选中样式
- 控制台查看change事件返回的数据
- 尝试选择新日期范围,确认交互正常
6. 注意事项与常见问题
6.1 性能优化
当日期范围较大时(如跨年选择),频繁调用setMultiple可能导致性能问题。解决方案:
- 合并设置操作:
javascript复制mounted() {
if(this.beforeDate && this.afterDate) {
this.cale.setMultiple([this.beforeDate, this.afterDate])
this.weeks = this.cale.weeks
}
},
- 使用debounce避免频繁重绘
6.2 边界情况处理
- 结束日期早于开始日期:
javascript复制watchEffect(() => {
if(new Date(this.afterDate) < new Date(this.beforeDate)) {
console.warn('结束日期不能早于开始日期')
this.afterDate = this.beforeDate
}
})
- 日期格式校验:
javascript复制const isValidDate = (dateStr) => {
return /^\d{4}-\d{2}-\d{2}$/.test(dateStr)
}
6.3 样式适配
默认选中样式可能需要调整:
css复制.uni-calendar__day-box .uni-calendar__day-selected {
background-color: #1890ff;
color: #fff;
}
7. 扩展思考
7.1 动态修改默认日期
如果需要动态修改默认日期范围,可以:
- 使用watch监听props变化
- 调用组件内部方法更新状态
javascript复制watch: {
beforeDate(val) {
if(val) {
this.cale.setMultiple(val)
this.weeks = this.cale.weeks
}
},
afterDate(val) {
if(val) {
this.cale.setMultiple(val)
this.weeks = this.cale.weeks
}
}
}
7.2 与后端数据结合
实际项目中,默认日期通常来自:
- 用户上次操作记录(存储在localStorage或后端)
- 业务默认设置(如默认显示最近7天)
- 特殊日期(如节假日、活动期)
可以从后端API获取默认日期:
javascript复制onLoad() {
fetchDefaultDateRange().then(res => {
state.beforeDate = res.startDate
state.afterDate = res.endDate
})
}
8. 替代方案对比
8.1 使用第三方日历组件
优点:
- 功能更丰富
- 可能有现成的默认选中功能
缺点:
- 体积更大
- 风格可能与项目不一致
- 学习成本高
8.2 完全自定义开发
优点:
- 完全可控
- 可以精确满足需求
缺点:
- 开发成本高
- 需要处理各种边界情况
- 维护困难
8.3 本方案优势
- 改动量小,风险可控
- 保持官方组件升级能力
- 专注解决特定问题
- 性能影响最小化
9. 实际项目中的应用技巧
- 日期持久化存储:
javascript复制// 保存用户选择
const saveDateRange = (start, end) => {
localStorage.setItem('lastDateRange', JSON.stringify({start, end}))
}
// 读取
const loadDateRange = () => {
const range = JSON.parse(localStorage.getItem('lastDateRange'))
return range || {start: '', end: ''}
}
- 与页面参数结合:
javascript复制onLoad(query) {
if(query.startDate && query.endDate) {
state.beforeDate = query.startDate
state.afterDate = query.endDate
}
}
- 多语言日期格式处理:
javascript复制const formatLocalizedDate = (dateStr, locale) => {
// 根据locale返回对应格式
}
10. 总结与个人心得
在uni-app项目中使用官方组件时,适度扩展比完全重写往往更高效。这次对uni-calendar的改造让我深刻体会到:
- 阅读源码的重要性:只有了解内部实现,才能精准修改
- 最小化修改原则:尽量不动无关代码,降低维护成本
- 边界情况的考虑:日期处理要格外小心各种异常情况
一个实用的技巧是:在修改第三方组件前,先fork一份到项目目录,这样既不影响node_modules中的原始文件,又方便后续对比更新。同时,务必在代码中添加详细注释,说明修改原因和影响范围。