cxc-szcx-uniapp/uni_modules/cxc-szcx-dateRangeSelect/components/cxc-szcx-dateRangeSelect/cxc-szcx-dateRangeSelect.vue

574 lines
13 KiB
Vue
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.

<template>
<view class="compact-datetime-picker">
<!-- 输入框区域 -->
<view class="input-container">
<view class="compact-input" :class="{ active: activeType === 'start' }" @click="openPicker('start')">
{{ formattedStart || '开始日期' }}
</view>
<text class="separator"></text>
<view class="compact-input" :class="{ active: activeType === 'end' }" @click="openPicker('end')">
{{ formattedEnd || '结束日期' }}
</view>
</view>
<!-- 日期选择弹窗 -->
<uni-popup ref="popup" type="bottom" :is-mask-click="false">
<view class="compact-popup">
<!-- 快捷按钮区域 -->
<view class="quick-actions">
<view class="mode-switch">
<text :class="['mode-btn', { active: isNatural }]" @click="toggleMode(true)">自然周期</text>
<text :class="['mode-btn', { active: !isNatural }]" @click="toggleMode(false)">相对周期</text>
</view>
<!-- 快捷操作 -->
<view class="action-buttons">
<button v-if="isNatural" v-for="btn in quickButtonszr" size="mini" :key="btn.type" class="action-btn" @click="setQuickRange(btn.type)">
{{ btn.label }}
</button>
<button v-else v-for="btn in quickButtonsxd" size="mini" :key="btn.type" class="action-btn" @click="setQuickRange(btn.type)">
{{ btn.label }}
</button>
</view>
</view>
<!-- 日历选择区域 -->
<view class="compact-calendar">
<view class="compact-header">
<text class="nav-arrow" @click="changeMonth(-1)"></text>
<text class="month-title">{{ monthText }}</text>
<text class="nav-arrow" @click="changeMonth(1)"></text>
</view>
<view class="compact-weekdays">
<text v-for="day in weekDays" :key="day" class="weekday">
{{ day }}
</text>
</view>
<view class="compact-days">
<text
v-for="(day, index) in calendarDays"
:key="index"
:class="[
'compact-day',
{
'other-month': !day.isCurrentMonth,
'selected-start': isStart(day.date),
'selected-end': isEnd(day.date),
'in-range': isInRange(day.date),
today: isToday(day.date)
}
]"
@click="handleDayClick(day)"
>
{{ day.day }}
</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="compact-footer">
<text class="footer-btn cancel" @click="closePicker">取消</text>
<text class="footer-btn confirm" @click="confirmSelection">确定</text>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
const MONTH_NAMES = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
export default {
props: {
modelValue: {
type: Array,
default: () => [null, null]
}
},
data() {
return {
isNatural: true,
activeType: 'start',
currentMonth: new Date(),
tempStart: null,
tempEnd: null,
weekDays: ['日', '一', '二', '三', '四', '五', '六'],
quickButtonszr: [
{ label: '本周', type: 'week' },
{ label: '本月', type: 'month' },
{ label: '本季', type: 'quarter' },
{ label: '本年', type: 'year' }
],
quickButtonsxd: [
{ label: '近一周', type: 'week' },
{ label: '近一月', type: 'month' },
{ label: '近一季', type: 'quarter' },
{ label: '近一年', type: 'year' }
]
};
},
filters: {
naturalMonth(date) {
return `${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}`;
}
},
computed: {
formattedStart() {
return this.formatDate(this.modelValue[0]);
},
formattedEnd() {
return this.formatDate(this.modelValue[1]);
},
calendarDays() {
return this.generateCalendar(this.currentMonth);
},
monthText() {
const [year, month] = this.formatDate(this.currentMonth).split('-');
return `${year}${month}`;
}
},
watch: {
modelValue: {
immediate: true,
handler(newVal) {
// console.log(newVal);
this.tempStart = newVal[0];
this.tempEnd = newVal[1];
}
}
},
methods: {
// 打开弹窗并初始化临时值
openPicker(type) {
this.activeType = type;
if (!this.tempStart) this.tempStart = new Date();
if (!this.tempEnd) this.tempEnd = new Date();
this.$refs.popup.open();
},
// 日期点击处理
handleDayClick(day) {
if (!day.isCurrentMonth) return;
const clickedDate = day.date;
if (this.activeType === 'start') {
if (clickedDate > this.tempEnd) {
this.tempStart = clickedDate;
this.tempEnd = clickedDate;
this.activeType = 'end';
} else {
this.tempStart = clickedDate;
}
} else {
if (clickedDate < this.tempStart) {
this.tempStart = clickedDate;
this.tempEnd = clickedDate;
this.activeType = 'start';
} else {
this.tempEnd = clickedDate;
}
}
},
// 确认选择
confirmSelection() {
this.$emit('update:modelValue', [this.tempStart, this.tempEnd]);
this.closePicker();
},
// 关闭弹窗
closePicker() {
this.$refs.popup.close();
this.resetTempDates();
},
// 重置临时日期
resetTempDates() {
this.tempStart = this.modelValue[0];
this.tempEnd = this.modelValue[1];
},
// 生成日历数据
generateCalendar(date) {
const year = date.getFullYear();
const month = date.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const startDay = firstDay.getDay();
const days = [];
// 填充上月日期
for (let i = startDay; i > 0; i--) {
const d = new Date(year, month, -i + 1);
days.push({
day: d.getDate(),
date: d,
isCurrentMonth: false
});
}
// 填充本月日期
for (let i = 1; i <= lastDay.getDate(); i++) {
const d = new Date(year, month, i);
days.push({
day: i,
date: d,
isCurrentMonth: true
});
}
// 修正后的下月日期填充
const totalCells = Math.ceil(days.length / 7) * 7;
let nextMonthDay = 1;
while (days.length < totalCells) {
const d = new Date(year, month + 1, nextMonthDay);
days.push({
day: d.getDate(),
date: d,
isCurrentMonth: false
});
nextMonthDay++;
}
return days;
},
// 日期选择处理
selectDate(date) {
if (!date.isCurrentMonth) return;
const newValue = [...this.modelValue];
const index = this.activeType === 'start' ? 0 : 1;
newValue[index] = date.date;
// 自动调整日期顺序
if (newValue[0] && newValue[1] && newValue[0] > newValue[1]) {
[newValue[0], newValue[1]] = [newValue[1], newValue[0]];
}
this.$emit('update:modelValue', newValue);
},
// 切换月份
changeMonth(offset) {
const newMonth = new Date(this.currentMonth);
newMonth.setMonth(newMonth.getMonth() + offset);
this.currentMonth = newMonth;
// console.log(this.currentMonth);
},
// 判断日期状态
isSelected(date) {
return date === this.modelValue[0] || date === this.modelValue[1];
},
isEnd(date) {
return date === this.modelValue[1];
},
isToday(date) {
return this.formatDate(date) === this.formatDate(new Date());
},
isStart(date) {
return this.tempStart && this.isSameDay(date, this.tempStart);
},
isEnd(date) {
return this.tempEnd && this.isSameDay(date, this.tempEnd);
},
// 改进范围判断
isInRange(date) {
if (!this.tempStart || !this.tempEnd) return false;
return date > this.tempStart && date < this.tempEnd;
},
isSameDay(d1, d2) {
return d1 && d2 && d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
},
// 快捷范围设置
setQuickRange(type) {
const currentDate = new Date(); // 使用当前日期
const range = this.isNatural ? this.calcNaturalRange(type, currentDate) : this.calcRelativeRange(type, currentDate);
this.$emit('update:modelValue', [range.start, range.end]);
this.$refs.popup.close();
},
// 自然周期计算
calcNaturalRange(type, currentDate) {
let start, end;
switch (type) {
case 'week':
start = this.getWeekStart(currentDate);
end = this.getWeekEnd(start);
break;
case 'month':
start = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
end = this.lastDayOfMonth(currentDate);
break;
case 'quarter':
const quarter = Math.floor(currentDate.getMonth() / 3);
start = new Date(currentDate.getFullYear(), quarter * 3, 1);
end = this.lastDayOfMonth(new Date(currentDate.getFullYear(), quarter * 3 + 2, 1));
break;
case 'year':
start = new Date(currentDate.getFullYear(), 0, 1);
end = new Date(currentDate.getFullYear(), 11, 31);
break;
}
return { start, end };
},
// 相对周期计算
calcRelativeRange(type, currentDate) {
let start = new Date(currentDate);
switch (type) {
case 'week':
start.setDate(currentDate.getDate() - 6);
break;
case 'month':
start.setMonth(currentDate.getMonth() - 1);
this.adjustMonthEnd(start, currentDate);
break;
case 'quarter':
start.setMonth(currentDate.getMonth() - 3);
this.adjustMonthEnd(start, currentDate);
break;
case 'year':
start.setFullYear(currentDate.getFullYear() - 1);
this.adjustMonthEnd(start, currentDate);
break;
}
return { start, end: new Date(currentDate) };
},
// 周计算(周一为起点)
getWeekStart(date) {
const day = date.getDay();
const diff = date.getDate() - day + (day === 0 ? -6 : 1);
return new Date(date.setDate(diff));
},
getWeekEnd(startDate) {
const end = new Date(startDate);
end.setDate(startDate.getDate() + 6);
return end;
},
// 月末处理
lastDayOfMonth(date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
},
// 月末日调整
adjustMonthEnd(start, end) {
const lastDay = this.lastDayOfMonth(start).getDate();
if (end.getDate() > lastDay) {
start.setDate(lastDay);
} else {
start.setDate(end.getDate());
}
},
// 闰年判断
isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
},
// 模式切换
toggleMode(isNatural) {
this.isNatural = isNatural;
},
// 日期格式化 monthFormat
formatDate(date) {
if (!date || !(date instanceof Date)) return '';
const pad = (n) => n.toString().padStart(2, '0');
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
}
};
</script>
<style scoped>
.input-container {
display: flex;
align-items: center;
gap: 10px;
}
/* 紧凑型输入框 */
.compact-input {
flex: 1;
padding: 6px 8px;
font-size: 16px;
height: 20px;
line-height: 20px;
border: 1px solid #dcdfe6;
border-radius: 4px;
text-align: center;
}
.compact-input.active {
border-color: #409eff;
background-color: #f5f7ff;
}
/* 紧凑弹窗 */
.compact-popup {
padding: 12px 8px;
background: #fff;
border-radius: 12px 12px 0 0;
}
/* 紧凑头部 */
.compact-header {
display: flex;
align-items: center;
justify-content: center;
margin: 8px 0;
font-size: 14px;
}
.nav-arrow {
font-size: 20px;
padding: 0 12px;
color: #606266;
}
.month-title {
font-weight: 500;
min-width: 120px;
text-align: center;
}
/* 紧凑日期网格 */
.compact-weekdays {
display: grid;
grid-template-columns: repeat(7, 1fr);
margin: 4px 0;
}
.weekday {
text-align: center;
font-size: 20px;
color: #909399;
padding: 4px 0;
}
.compact-days {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
}
/* 修改非本月日期样式 */
.compact-day.other-month {
color: #c0c4cc;
opacity: 0.8;
}
.compact-day {
aspect-ratio: 1;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
border-radius: 3px;
}
/* 日期状态 */
.compact-day.today {
font-weight: bold;
color: #409eff;
background: #e8f3ff;
}
.compact-day.selected-start {
background: #409eff;
color: white;
border-radius: 3px 0 0 3px;
}
.compact-day.selected-end {
background: #409eff;
color: white;
border-radius: 0 3px 3px 0;
}
.compact-day.in-range {
background: #ccd2d8;
}
.quick-actions {
/* padding: 0 10px; */
}
.mode-switch {
display: flex;
margin-bottom: 8px;
border-radius: 8px;
overflow: hidden;
background: #f5f7fa;
}
.mode-btn {
flex: 1;
padding: 6px 8px;
text-align: center;
color: #606266;
transition: all 0.3s;
}
.mode-btn.active {
background: #409eff;
color: #fff;
}
.action-buttons {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
margin-bottom: 5px;
}
.action-btn {
background: #f5f7fa;
color: #606266;
height: 24px;
font-size: 14px;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
text-align: center;
}
/* 底部操作 */
.compact-footer {
display: flex;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #eee;
}
.footer-btn {
flex: 1;
text-align: center;
padding: 10px;
font-size: 14px;
}
.cancel {
color: #606266;
}
.confirm {
color: #409eff;
font-weight: 500;
}
</style>