仿照支付宝时间查询,月份及区间时间组件;
vue setup语法;使用uniapp内置组件picker-view滚动选择器;弹窗使用uview-plus弹出层;搭配moment时间格式转换。
返回参数可根据项目要求调整;月份选择返回参数,当前选中月份第一天和最后一天时间字符串;
效果图如下:
完整代码如下:
<template>
<view>
<u-popup :show="timePopShow" mode="bottom" @close="close" @open="open" :closeOnClickOverlay="true">
<view class="popup-container">
<u-tabs :list="tabList" @click="clickTab" class="tabs" :scrollable="false"></u-tabs>
<!-- 月份选择内容 -->
<view class="content" v-if="currentTab === 'tab1'">
<picker-view indicator-style="height: 100rpx;" :value="dateValue" @change="pickerChange"
class="picker-view">
<picker-view-column>
<view class="item" v-for="(item,index) in years" :key="index">{{item}}年</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item,index) in months" :key="index">{{item}}月</view>
</picker-view-column>
</picker-view>
</view>
<!-- 自定义时间内容 -->
<view class="date-range-container" v-if="currentTab === 'tab2'">
<view class="date-input" :class="{ selected: currentSelect === 'start' }"
@click="selectDate('start')">
<text>{{ startDateText }}</text>
</view>
<text class="date-separator">至</text>
<view class="date-input" :class="{ selected: currentSelect === 'end' }" @click="selectDate('end')">
<text>{{ endDateText }}</text>
</view>
</view>
<view class="content-range" v-if="currentTab === 'tab2'">
<picker-view :value="pickerValue" indicator-style="height: 100rpx;" class="picker-view"
@change="pickerChange">
<picker-view-column>
<view v-for="(year, index) in years" :key="index" class="item">{{ year }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(month, index) in months" :key="index" class="item">{{ month }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(day, index) in days" :key="index" class="item">{{ day }}</view>
</picker-view-column>
</picker-view>
</view>
</view>
<!-- 确认按钮 -->
<view class="header-buttons">
<button @click="confirm" class="button button--confirm">确认</button>
</view>
</u-popup>
</view>
</template>
<script setup>
import moment from 'moment'
import {
ref,
defineEmits,
watch
} from 'vue';
// 注册组件暴露方法
const emits = defineEmits(['emitsClose', 'emitsGetMonthDays', 'emitsCompareDates'])
// 组件传参
const props = defineProps({
// 弹窗开启
timePopShow: {
type: Boolean,
default: false
},
})
const currentTab = ref('tab1') //选项卡控制变量
const tabList = ref([{
name: '月份选择',
value: 'tab1'
},
{
name: '自定义时间',
value: 'tab2'
}
]) //选项卡内容
const currentDate = new Date(); //当前时间
const currentYear = currentDate.getFullYear(); //当前年
const currentMonth = currentDate.getMonth(); //当前月
const years = Array.from({
length: 180
}, (v, i) => 2020 + i) // 生成2020到2200年
const months = Array.from({
length: 12
}, (v, i) => i + 1) // 生成1到12月
const days = Array.from({
length: 31
}, (v, i) => i + 1) // 生成1到31日
const dateValue = ref([currentYear - 2020, currentMonth]) // 默认选中当前年份和月份
const currentSelect = ref('start') // 用于标记当前选择的是开始日期还是结束日期
const startDateText = ref(moment().format('YYYY-MM-DD')) // 开始时间
const endDateText = ref(moment().add(1, 'days').format('YYYY-MM-DD')) //截至时间
const pickerValue = ref([currentYear - 2020, currentMonth, currentDate.getDate() - 1]) // 当前选择器的值
const startDateValue = ref([currentYear - 2020, currentMonth, currentDate.getDate() - 1]) // 默认选中当前日期
const endDateValue = ref([currentYear - 2020, currentMonth, currentDate.getDate()]) // 默认选中截止日期
// 选项卡切换点击
const clickTab = (item) => {
currentTab.value = item.value;
}
// 组件开启
const open = () => {
currentTab.value = 'tab1';
}
// 组件关闭
const close = () => {
selectDate(currentSelect.value)
emits('emitsClose', false)
}
// 时间选择监听
const pickerChange = (e) => {
const value = e.detail.value;
if (currentTab.value === 'tab1') {
// 当 currentTab 为 'tab1' 时,处理月份选择
dateValue.value = value; // 更新 dateValue
} else if (currentTab.value === 'tab2') {
// 当 currentTab 为 'tab2' 时,处理自定义时间
if (currentSelect.value === 'start') {
startDateValue.value = value;
pickerValue.value = value
startDateText.value =
`${years[value[0]]}-${months[value[1]].toString().padStart(2, '0')}-${days[value[2]].toString().padStart(2, '0')}`;
} else if (currentSelect.value === 'end') {
endDateValue.value = value;
pickerValue.value = value
endDateText.value =
`${years[value[0]]}-${months[value[1]].toString().padStart(2, '0')}-${days[value[2]].toString().padStart(2, '0')}`;
}
}
}
// 开始/截止时间点击选择
const selectDate = (type) => {
currentSelect.value = type;
// 恢复上一次选中时间
if (type === 'start') {
pickerValue.value = [...startDateValue.value];
} else if (type === 'end') {
pickerValue.value = [...endDateValue.value];
}
}
// 确认按钮
const confirm = () => {
if (currentTab.value === 'tab1') {
const year = years[dateValue.value[0]];
const month = months[dateValue.value[1]];
emits('emitsGetMonthDays', getMonthDays(year, month), year + '-' + month)
} else if (currentTab.value === 'tab2') {
const startYear = years[startDateValue.value[0]];
const startMonth = months[startDateValue.value[1]];
const startDay = days[startDateValue.value[2]];
const endYear = years[endDateValue.value[0]];
const endMonth = months[endDateValue.value[1]];
const endDay = days[endDateValue.value[2]];
if (compareDates(startYear, startMonth, startDay, endYear, endMonth, endDay)) {
emits('emitsCompareDates', compareDates(startYear, startMonth, startDay, endYear, endMonth, endDay))
}
}
}
// 月份数据处理(返回每个月开始/截止时间)
const getMonthDays = (year, month) => {
const firstDayOfMonth = moment([year, month - 1, 1]);
const lastDayOfMonth = firstDayOfMonth.clone().endOf('month');
return {
startTime: firstDayOfMonth.format('YYYY-MM-DD'),
endTime: lastDayOfMonth.format('YYYY-MM-DD'),
}
}
// 处理区间时间数据
const compareDates = (startYear, startMonth, startDay, endYear, endMonth, endDay) => {
// 比较两个日期
let startTime = `${startYear}-${startMonth}-${startDay}`;
let endTime = `${endYear}-${endMonth}-${endDay}`;
// 如果开始时间和结束时间相同,则提示用户
if (startYear === endYear && startMonth === endMonth && startDay === endDay) {
return null
uni.$u.toast('开始时间不能与截止时间一致');
}
// 比较日期并交换如果必要
return swapIfGreater(startTime, endTime);
}
// 时间比对参数调换
const swapIfGreater = (startTime, endTime) => {
const startTimestamp = moment(startTime).valueOf();
const endTimestamp = moment(endTime).valueOf();
if (startTimestamp > endTimestamp) {
return {
startTime: endTime,
endTime: startTime
};
}
return {
startTime,
endTime
};
}
</script>
<style lang="less" scoped>
.popup-container {
position: relative;
width: 100vw;
height: 760rpx;
padding: 40rpx 40rpx;
box-sizing: border-box;
background-color: #fff;
.tabs {
width: 100%;
margin-bottom: 40rpx;
}
.content {
margin-top: 40rpx;
display: flex;
justify-content: center;
align-items: center;
height: 560rpx;
}
.date-range-container {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 40rpx;
margin-top: 40rpx;
.date-separator {
margin: 0 20rpx;
}
.date-input {
border-bottom: 4rpx solid #ccc;
padding: 10rpx 20rpx;
margin: 0 20rpx;
cursor: pointer;
text-align: center;
width: 50%;
/* 固定宽度确保开始时间和结束时间一致 */
height: 60rpx;
/* 设置高度以确保一致 */
line-height: 60rpx;
/* 设置行高以居中内容 */
}
.date-input.selected {
border-bottom: 4rpx solid #007bff;
color: #007bff;
}
.content-range {
margin-top: 40rpx;
display: flex;
justify-content: center;
align-items: center;
height: 400rpx;
}
}
}
.picker-view {
width: 100%;
height: 400rpx;
margin-top: 20rpx;
}
.header-buttons {
display: flex;
justify-content: space-between;
padding: 0 40rpx;
box-sizing: border-box;
margin-bottom: 20rpx;
.button--confirm {
text-align: center;
background-color: #007bff;
color: #fff;
width: 100%;
font-size: 32rpx;
font-weight: 400;
line-height: 88rpx; /* 150% */
}
}
.item {
line-height: 100rpx;
text-align: center;
}
</style>