| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 |
- <template>
- <!--时间选择弹窗-->
- <u-popup ref="popup" :show="showPopup">
- <view class="jtime-picker-content">
- <view class="picker-view-content-top">
- <view @click="cancelPickerShow" class="cancel">{{ cancelText }}</view>
- <view class="title" v-if="isShowSeletTimeTitle">{{ seletTimeTitle }}</view>
- <view @click="confirmPickerValue" class="confirm">{{ confirmText }}</view>
- </view>
- <view class="jtime-container">
- <view class="short-select-time" v-if="isShowShortTimeList">
- <view class="time-title">{{ shortTimeTitle }}</view>
- <view class="time-list">
- <view :class="index == currentShortTimeIndex ? 'short-time-active' : ''"
- v-for="(item, index) in shortTimeList" :key="item.key" @click="getShortTime(item, index)">
- {{ item.key }}
- </view>
- </view>
- </view>
- <view class="time-custom">
- <view class="time-tile" v-if="isShowShortTimeList">
- <view>自定义</view>
- <image src="@/uni_modules/jtime-picker-popup/static/delete.png" @click="deleteSelectedTime" />
- </view>
- <view v-if="isShowSelectedTimeEcho" class="time-value">
- <view :class="['time-begin', beginTimeStr ? 'time' : '']">
- <!-- <input :class="['uni-input', beginTimeFlag ? 'begin-focus-style' : '']" :readonly="true" v-model="beginTimeStr" placeholder="开始时间" @click="timeSelect(1)" /> -->
- <view
- :class="['uni-input', beginTimeFlag ? 'begin-focus-style' : '', isDateTypeRange ? '' : 'uni-input-w-full']"
- @click="timeSelect(1)">{{ beginTimeStr ? beginTimeStr : beginTimePlaceHolder }}</view>
- </view>
- <view v-if="isDateTypeRange" class="time-sync">至</view>
- <view v-if="isDateTypeRange" :class="['time-end', endTimeStr ? 'time' : '']">
- <!-- <input :class="['uni-input', endTimeFlag ? 'end-focus-style' : '']" readonly v-model="endTimeStr" placeholder="结束时间" @click="timeSelect(2)" /> -->
- <view :class="['uni-input', endTimeFlag ? 'end-focus-style' : '']" @click="timeSelect(2)">
- {{ endTimeStr ? endTimeStr : endTimePlaceHolder }}
- </view>
- </view>
- </view>
- </view>
- <picker-view v-if="visible" indicator-class="indicatorStyle" indicator-style="height: 40px"
- :value="pickerValue" class="picker-view" @change="pickerDateChange">
- <picker-view-column>
- <view :class="['item', item == year ? 'current-item-year' : '']" v-for="(item,index) in years"
- :key="index">{{item}}年</view>
- </picker-view-column>
- <picker-view-column>
- <view :class="['item', item == month ? 'current-item-month' : '']"
- v-for="(item,index) in months" :key="index">{{item}}月</view>
- </picker-view-column>
- <picker-view-column>
- <view :class="['item', item == day ? 'current-item-day' : '']" v-for="(item,index) in days"
- :key="index">{{item}}日</view>
- </picker-view-column>
- </picker-view>
- </view>
- </view>
- </u-popup>
- <!--end 时间选择弹窗-->
- </template>
- <script>
- import dayjs from 'dayjs';
- export default {
- name: 'JTimePicker',
- props: {
- // 快捷时间列表
- defaultSelect : {
- type : Number,
- default : ()=> null
- },
- shortTimeList: {
- type: Array,
- default: () => {
- return [{
- unit: 'day',
- key: '近半月',
- value: 15
- },
- {
- unit: 'month',
- key: '近三月',
- value: 3
- },
- {
- unit: 'year',
- key: '近一年',
- value: 1
- },
- ]
- }
- },
- // 是否显示快捷时间选择列表
- isShowShortTimeList: {
- type: Boolean,
- default: true
- },
- // 快捷时间标题
- shortTimeTitle: {
- type: String,
- default: '开票时间'
- },
- // 是否显示时间选择标题
- isShowSeletTimeTitle: {
- type: Boolean,
- default: true
- },
- // 标题
- seletTimeTitle: {
- type: String,
- default: '时间选择'
- },
- // 取消按钮文字
- cancelText: {
- type: String,
- default: '取消'
- },
- // 确认按钮文字
- confirmText: {
- type: String,
- default: '确认'
- },
- // 时间选择开始年份
- beginSelectYear: {
- type: Number,
- default: 1930
- },
- // 时间选择结束年份
- endSelectYear: {
- type: Number,
- default: new Date().getFullYear()
- },
- // 时间选择结束月份, -1=>当前月份
- endSelectMonth: {
- type: Number,
- default: -1
- },
- // 时间选择结束天数 -1=> 当天
- endSelectDay: {
- type: Number,
- default: dayjs().daysInMonth()
- },
- beginTimePlaceHolder: {
- type: String,
- default: '开始时间'
- },
- endTimePlaceHolder: {
- type: String,
- default: '结束时间'
- },
- // 选择器是否为时间选范围选择
- isDateTypeRange: {
- type: Boolean,
- default: false
- },
- // 是否回显选择日期
- isShowSelectedTimeEcho: {
- type: Boolean,
- default: true
- },
- // 当前传进来的开始时间
- originBeginTime: {
- type: String,
- default: ''
- },
- // 当前传进来的结束时间
- originEndTime: {
- type: String,
- default: ''
- },
- },
- data() {
- const date = new Date()
- const years = []
- const year = date.getFullYear()
- const months = []
- const month = date.getMonth() + 1
- const days = []
- const day = date.getDate()
- for (let i = this.beginSelectYear; i <= this.endSelectYear; i++) {
- years.push(i)
- }
- for (let i = 1; i <= 12; i++) {
- months.push(String(i).padStart(2, '0'))
- }
- for (let i = 1; i <= 31; i++) {
- days.push(String(i).padStart(2, '0'))
- }
- return {
- years,
- year,
- months,
- month,
- days,
- day,
- visible: true,
- pickerValue: [9999, month - 1, day - 1],
- beginTimeStr: this.originBeginTime,
- endTimeStr: this.originEndTime,
- beginTimeFlag: false,
- endTimeFlag: false,
- currentShortTimeIndex: -1,
- showPopup: false,
- };
- },
- mounted() {
- // 默认展示开始时间
- this.beginTimeFlag = true
- if (this.originBeginTime) {
- this.year = parseInt(this.originBeginTime.split('/')[0])
- this.month = parseInt(this.originBeginTime.split('/')[1])
- this.day = parseInt(this.originBeginTime.split('/')[2])
- this.pickerValue = [this.year - 1930, this.month - 1, this.day - 1]
- };
- },
- methods: {
- handleInit(){
- if(this.defaultSelect){
- const index = this.shortTimeList.findIndex(v=>v.value === this.defaultSelect);
- if(index != -1){
- this.getShortTime(this.shortTimeList[index],index);
- }
- this.confirmPickerValue();
- }
- },
- pickerShow() {
- this.showPopup = true;
- },
- cancelPickerShow() {
- this.showPopup = false;
- },
- updateMonths(selectedYear) {
- const date = new Date();
- const currentYear = date.getFullYear()
- const currentMonth = date.getMonth() + 1
- this.months.length = 0; // 清空原数组
- if (selectedYear === currentYear && currentYear === this.endSelectYear) {
- if (this.endSelectMonth && this.endSelectMonth > 0) {
- for (let i = 1; i <= this.endSelectMonth; i++) {
- this.months.push(String(i).padStart(2, '0'))
- }
- } else {
- // 最多只能选择到当前月份
- for (let i = 1; i <= currentMonth; i++) {
- this.months.push(String(i).padStart(2, '0'))
- }
- }
- } else {
- for (let i = 1; i <= 12; i++) {
- this.months.push(String(i).padStart(2, '0'))
- }
- }
- },
- updateDays(selectedYear, selectedMonth) {
- const date = new Date();
- const currentYear = date.getFullYear()
- const currentMonth = date.getMonth() + 1
- const currentDay = date.getDate()
- this.days.length = 0; // 清空原数组
- const dayCount = this.getDaysInMonth(selectedYear, selectedMonth);
- if (selectedYear === currentYear && selectedMonth === currentMonth && (currentMonth === this
- .endSelectMonth || this.endSelectMonth == -1)) {
- if (this.endSelectDay && this.endSelectDay > -1) {
- for (let i = 1; i <= this.endSelectDay; i++) {
- this.days.push(String(i).padStart(2, '0'));
- }
- } else {
- // 最多选择到今天
- for (let i = 1; i <= currentDay; i++) {
- this.days.push(String(i).padStart(2, '0'));
- }
- }
- } else {
- if (this.endSelectDay > -1 && selectedMonth === this.endSelectMonth) {
- for (let i = 1; i <= Math.min(dayCount, this.endSelectDay); i++) {
- this.days.push(String(i).padStart(2, '0'));
- }
- } else {
- for (let i = 1; i <= dayCount; i++) {
- this.days.push(String(i).padStart(2, '0'));
- }
- }
- }
- },
- getDaysInMonth(year, month) {
- // 二月特殊处理
- if (month == 2) {
- // 闰年规则:能被4整除且不可被100整除,或能被400整除
- return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 ? 29 : 28;
- }
- // 其他月份:4/6/9/11月为30天
- return [4, 6, 9, 11].includes(month) ? 30 : 31;
- },
- pickerDateChange(e) {
- const val = e.detail.value
- this.year = this.years[val[0]]
- this.month = this.months[val[1]]
- this.day = this.days[val[2]]
- this.updateMonths(this.year)
- this.updateDays(this.year, parseFloat(this.month));
- if (this.beginTimeFlag) {
- this.beginTimeStr = `${this.year}/${this.month}/${this.day}`
- } else if (this.endTimeFlag) {
- this.endTimeStr = `${this.year}/${this.month}/${this.day}`
- }
- this.currentShortTimeIndex = -1
- },
- confirmPickerValue() {
- if(this.beginTimeStr || this.endTimeStr){
- if (!this.beginTimeStr) {
- return uni.showToast({
- title: '请选择开始时间',
- icon: 'none'
- })
- } else if (this.isDateTypeRange && !this.endTimeStr) {
- this.endTimeFlag = true
- this.beginTimeFlag = false
- return uni.showToast({
- title: '请选择结束时间',
- icon: 'none'
- })
- } else if (this.isDateTypeRange && dayjs(this.endTimeStr).isBefore(this.beginTimeStr)) {
- return uni.showToast({
- title: '结束时间不能小于开始时间',
- icon: 'none'
- })
- }
- }
- this.$emit('confirm', {
- beginTime: this.beginTimeStr,
- endTime: this.isDateTypeRange ? this.endTimeStr : ''
- })
- this.showPopup = false;
- },
- // 点击开始时间(结束时间)框 => 当前处于开始时间(结束时间),进行时间选择
- timeSelect(type) {
- if (type == 1) {
- this.beginTimeFlag = true
- this.endTimeFlag = false
- } else {
- this.beginTimeFlag = false
- this.endTimeFlag = true
- }
- },
- deleteSelectedTime() {
- this.beginTimeFlag = true
- this.endTimeFlag = false
- this.beginTimeStr = ''
- this.endTimeStr = ''
- },
- // 点击时间快捷方式,获取起始时间、结束时间
- getShortTime(item, index) {
- this.currentShortTimeIndex = index;
- let beginTime = '';
- let endTime = '';
- switch (item.key) {
- case '全部':
- // 特殊处理 实际就是不传值
- this.deleteSelectedTime();
- break;
- case '今天':
- beginTime = dayjs().format('YYYY/MM/DD');
- endTime = beginTime;
- break;
- case '昨天':
- const yesterday = dayjs().subtract(1, 'day').format('YYYY/MM/DD');
- beginTime = yesterday;
- endTime = yesterday;
- break;
- case '本月':
- beginTime = dayjs().startOf('month').format('YYYY/MM/DD');
- endTime = dayjs().endOf('month').format('YYYY/MM/DD');
- break;
- case '上月':
- const firstDayOfLastMonth = dayjs().subtract(1, 'month').startOf('month');
- beginTime = firstDayOfLastMonth.format('YYYY/MM/DD');
- endTime = firstDayOfLastMonth.endOf('month').format('YYYY/MM/DD');
- break;
- default:
- // 处理其他情况(近3天/近7天/近15天)
- const result = this.getDateRange(item.unit, item.value);
- beginTime = result.beginTime;
- endTime = result.endTime;
- }
- if (beginTime && endTime) {
- this.beginTimeStr = beginTime;
- this.endTimeStr = endTime;
- // 仅当不是"全部"时更新选择器位置
- if (item.key !== '全部') {
- const [y, m, d] = beginTime.split('/').map(Number);
- this.year = y;
- this.month = m;
- this.day = d;
- // 更新月份和日期数组
- this.updateMonths(this.year);
- this.updateDays(this.year, parseFloat(this.month));
- // 计算选择器位置
- const yearIndex = this.years.indexOf(y);
- let monthIndex = this.months.indexOf(String(m).padStart(2, '0'));
- let dayIndex = this.days.indexOf(String(d).padStart(2, '0'));
- // 处理索引越界情况
- if (monthIndex === -1) monthIndex = this.months.length - 1;
- if (dayIndex === -1) dayIndex = this.days.length - 1;
- this.pickerValue = [yearIndex, monthIndex, dayIndex];
- }
- }
- this.beginTimeFlag = true;
- this.endTimeFlag = false;
- },
- /**
- * 获取时间区间(格式化 YYYY/MM/DD)
- * @param {string} rangeType - 时间范围类型 ('day' | 'month' | 'year')
- * @param {number} num - 数量(如 15天、3个月、1年)
- * @returns { { beginTime: string, endTime: string } }
- */
- getDateRange(rangeType = 'day', num = 15) {
- // const end = dayjs.format(new Date());
- // const startDate = dayjs.subtract(new Date(), unit == 'day' ? value - 1 : value, unit);
- // const start = dayjs.format(startDate);
- // return { beginTime: start, endTime: end };
- const endTime = dayjs();
- const beginTime = endTime.subtract(rangeType == 'day' ? num - 1 : num, rangeType); // 包含今天,所以减 (num-1)
- return {
- beginTime: beginTime.format('YYYY/MM/DD'),
- endTime: endTime.format('YYYY/MM/DD'),
- };
- }
- }
- }
- </script>
- <style scoped lang="scss">
- ::v-deep uni-picker-view {
- .uni-picker-view-wrapper {
- .indicatorStyle {
- background-color: #F3F3F3;
- &::before {
- display: none;
- }
- &::after {
- display: none;
- }
- }
- .uni-picker-view-mask {
- z-index: 9;
- }
- .uni-picker-view-content {
- z-index: 8;
- }
- uni-picker-view-column:nth-child(1) {
- .indicatorStyle {
- border-top-left-radius: 6px;
- border-bottom-left-radius: 6px;
- }
- }
- uni-picker-view-column:nth-child(3) {
- .indicatorStyle {
- border-top-right-radius: 6px;
- border-bottom-right-radius: 6px;
- }
- }
- }
- }
- .jtime-picker-content {
- width: 100%;
- background-color: #fff;
- .picker-view-content-top {
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 14px;
- padding: 10px 20px;
- cursor: pointer;
- border-bottom: 1px solid #eee;
- .cancel {
- color: #86909C;
- }
- .confirm {
- color: #004677;
- font-weight: 500;
- }
- .title {
- font-size: 18px;
- font-weight: 600;
- }
- }
- .jtime-container {
- padding: 0 16px;
- .short-select-time {
- margin-top: 8px;
- .time-title {
- color: #4E5969;
- font-size: 12px;
- padding-bottom: 16px;
- }
- .time-list {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- font-size: 12px;
- &>view {
- padding: 2px 8px;
- border-radius: 3px;
- border: 1px solid var(--Gray-Gray4, #DCDCDC);
- margin-right: 8px;
- margin-bottom: 10px;
- }
- .short-time-active {
- color: #004677;
- border: 1px solid #004677;
- background: rgba(43, 63, 108, 0.08);
- }
- }
- }
- .time-custom {
- margin: 24px 0 16px 0;
- .time-tile {
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 12px;
- color: #4E5969;
- img,
- image {
- width: 16px;
- height: 16px;
- }
- }
- .time-value {
- margin-top: 16px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- .time-begin,
- .time-end {
- text-align: center;
- flex: 1;
- .uni-input {
- padding: 9px 26px;
- height: 22px;
- line-height: 22px;
- color: #86909C;
- font-size: 16px;
- width: 90px;
- border-bottom: 1px solid var(---color-border-2, #E5E6EB);
- }
- .begin-focus-style,
- .end-focus-style {
- color: #004677 !important;
- border-bottom: 1px solid #004677;
- }
- .uni-input-w-full {
- width: calc(100% - 52px);
- }
- }
- .time-sync {
- color: #4E5969;
- padding: 9px 16px;
- font-size: 14px;
- }
- .time {
- .uni-input {
- color: #1D2129;
- font-weight: 600;
- }
- }
- }
- }
- .picker-view {
- width: 100%;
- height: 200px;
- }
- .item {
- line-height: 40px;
- text-align: center;
- font-size: 16px;
- font-weight: 400;
- color: var(--text-icon-font-gy-260, rgba(0, 0, 0, 0.60));
- }
- .current-item-year,
- .current-item-month,
- .current-item-day {
- font-size: 16px;
- font-weight: 600;
- color: var(--text-icon-font-gy-190, rgba(0, 0, 0, 0.90));
- }
- }
- }
- </style>
|