|
@@ -72,12 +72,17 @@
|
|
|
<view class="pick-mask" v-if="monthPickerShow" @click="monthPickerShow = false">
|
|
<view class="pick-mask" v-if="monthPickerShow" @click="monthPickerShow = false">
|
|
|
<view class="pick-sheet" @click.stop>
|
|
<view class="pick-sheet" @click.stop>
|
|
|
<view class="pick-title">选择月份</view>
|
|
<view class="pick-title">选择月份</view>
|
|
|
- <picker-view class="pick-view" :value="monthPickerValue" @change="onMonthPickerChange">
|
|
|
|
|
|
|
+ <picker-view
|
|
|
|
|
+ class="pick-view"
|
|
|
|
|
+ :indicator-style="pickerIndicatorStyle"
|
|
|
|
|
+ :value="monthPickerValue"
|
|
|
|
|
+ @change="onMonthPickerChange"
|
|
|
|
|
+ >
|
|
|
<picker-view-column>
|
|
<picker-view-column>
|
|
|
- <view class="pick-item" v-for="y in yearList" :key="y">{{ y }}年</view>
|
|
|
|
|
|
|
+ <view class="pick-item" :style="pickerItemStyle" v-for="y in yearList" :key="y">{{ y }}年</view>
|
|
|
</picker-view-column>
|
|
</picker-view-column>
|
|
|
<picker-view-column>
|
|
<picker-view-column>
|
|
|
- <view class="pick-item" v-for="m in 12" :key="m">{{ m }}月</view>
|
|
|
|
|
|
|
+ <view class="pick-item" :style="pickerItemStyle" v-for="m in 12" :key="m">{{ m }}月</view>
|
|
|
</picker-view-column>
|
|
</picker-view-column>
|
|
|
</picker-view>
|
|
</picker-view>
|
|
|
<view class="pick-confirm" @click="confirmMonthPicker">确定</view>
|
|
<view class="pick-confirm" @click="confirmMonthPicker">确定</view>
|
|
@@ -95,6 +100,7 @@
|
|
|
>
|
|
>
|
|
|
{{ formatCnDate(tempStart) || '开始日期' }}
|
|
{{ formatCnDate(tempStart) || '开始日期' }}
|
|
|
</view>
|
|
</view>
|
|
|
|
|
+ <text class="range-sep">-</text>
|
|
|
<view
|
|
<view
|
|
|
:class="['preview-box', rangeField === 'end' ? 'on' : '']"
|
|
:class="['preview-box', rangeField === 'end' ? 'on' : '']"
|
|
|
@click="switchRangeField('end')"
|
|
@click="switchRangeField('end')"
|
|
@@ -102,15 +108,21 @@
|
|
|
{{ formatCnDate(tempEnd) || '结束日期' }}
|
|
{{ formatCnDate(tempEnd) || '结束日期' }}
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
- <picker-view class="pick-view" :value="datePickerValue" @change="onDatePickerChange">
|
|
|
|
|
|
|
+ <picker-view
|
|
|
|
|
+ :key="rangePickerKey"
|
|
|
|
|
+ class="pick-view"
|
|
|
|
|
+ :indicator-style="pickerIndicatorStyle"
|
|
|
|
|
+ :value="datePickerValue"
|
|
|
|
|
+ @change="onDatePickerChange"
|
|
|
|
|
+ >
|
|
|
<picker-view-column>
|
|
<picker-view-column>
|
|
|
- <view class="pick-item" v-for="y in yearList" :key="'dy' + y">{{ y }}年</view>
|
|
|
|
|
|
|
+ <view class="pick-item" :style="pickerItemStyle" v-for="y in yearList" :key="'dy' + y">{{ y }}年</view>
|
|
|
</picker-view-column>
|
|
</picker-view-column>
|
|
|
<picker-view-column>
|
|
<picker-view-column>
|
|
|
- <view class="pick-item" v-for="m in 12" :key="'dm' + m">{{ m }}月</view>
|
|
|
|
|
|
|
+ <view class="pick-item" :style="pickerItemStyle" v-for="m in 12" :key="'dm' + m">{{ m }}月</view>
|
|
|
</picker-view-column>
|
|
</picker-view-column>
|
|
|
<picker-view-column>
|
|
<picker-view-column>
|
|
|
- <view class="pick-item" v-for="d in dayList" :key="'dd' + d">{{ d }}日</view>
|
|
|
|
|
|
|
+ <view class="pick-item" :style="pickerItemStyle" v-for="d in dayList" :key="'dd' + d">{{ d }}日</view>
|
|
|
</picker-view-column>
|
|
</picker-view-column>
|
|
|
</picker-view>
|
|
</picker-view>
|
|
|
<view class="pick-confirm" @click="confirmRangePicker">确定</view>
|
|
<view class="pick-confirm" @click="confirmRangePicker">确定</view>
|
|
@@ -134,6 +146,8 @@ export default {
|
|
|
const year = now.getFullYear();
|
|
const year = now.getFullYear();
|
|
|
const yearList = [];
|
|
const yearList = [];
|
|
|
for (let y = year - 2; y <= year + 10; y++) yearList.push(y);
|
|
for (let y = year - 2; y <= year + 10; y++) yearList.push(y);
|
|
|
|
|
+ // picker-view 的 indicator-style 只认 px;与选项行高必须一致,否则会视觉错位选错
|
|
|
|
|
+ const itemPx = uni.upx2px(80);
|
|
|
return {
|
|
return {
|
|
|
fromMulti: false,
|
|
fromMulti: false,
|
|
|
mode: 'fixed',
|
|
mode: 'fixed',
|
|
@@ -149,10 +163,13 @@ export default {
|
|
|
monthPickerValue: [2, now.getMonth()],
|
|
monthPickerValue: [2, now.getMonth()],
|
|
|
rangePickerShow: false,
|
|
rangePickerShow: false,
|
|
|
rangeField: 'start',
|
|
rangeField: 'start',
|
|
|
|
|
+ rangePickerKey: 0,
|
|
|
tempStart: '',
|
|
tempStart: '',
|
|
|
tempEnd: '',
|
|
tempEnd: '',
|
|
|
datePickerValue: [2, now.getMonth(), now.getDate() - 1],
|
|
datePickerValue: [2, now.getMonth(), now.getDate() - 1],
|
|
|
- pickerDay: now.getDate()
|
|
|
|
|
|
|
+ pickerDay: now.getDate(),
|
|
|
|
|
+ pickerIndicatorStyle: `height: ${itemPx}px;`,
|
|
|
|
|
+ pickerItemStyle: `height: ${itemPx}px; line-height: ${itemPx}px;`
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
computed: {
|
|
computed: {
|
|
@@ -293,19 +310,33 @@ export default {
|
|
|
const base = this.normalizeDate(
|
|
const base = this.normalizeDate(
|
|
|
field === 'end' ? this.endDate || this.startDate : this.startDate || this.endDate
|
|
field === 'end' ? this.endDate || this.startDate : this.startDate || this.endDate
|
|
|
);
|
|
);
|
|
|
- this.applyDateToPicker(base);
|
|
|
|
|
- // 首次打开且对应字段为空时,用当前滚轮日期预填
|
|
|
|
|
const now = new Date();
|
|
const now = new Date();
|
|
|
const fallback = this.toYmd(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
|
const fallback = this.toYmd(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
|
|
const seed = base || fallback;
|
|
const seed = base || fallback;
|
|
|
if (field === 'start' && !this.tempStart) this.tempStart = seed;
|
|
if (field === 'start' && !this.tempStart) this.tempStart = seed;
|
|
|
if (field === 'end' && !this.tempEnd) this.tempEnd = seed;
|
|
if (field === 'end' && !this.tempEnd) this.tempEnd = seed;
|
|
|
|
|
+ this.remountRangePicker(
|
|
|
|
|
+ this.normalizeDate(field === 'end' ? this.tempEnd : this.tempStart) || seed
|
|
|
|
|
+ );
|
|
|
this.rangePickerShow = true;
|
|
this.rangePickerShow = true;
|
|
|
},
|
|
},
|
|
|
switchRangeField(field) {
|
|
switchRangeField(field) {
|
|
|
|
|
+ if (this.rangeField === field) return;
|
|
|
|
|
+ // 切换前先把当前滚轮值写回当前字段,避免开始/结束串值
|
|
|
|
|
+ this.syncPickerToField();
|
|
|
this.rangeField = field;
|
|
this.rangeField = field;
|
|
|
const base = this.normalizeDate(field === 'start' ? this.tempStart : this.tempEnd);
|
|
const base = this.normalizeDate(field === 'start' ? this.tempStart : this.tempEnd);
|
|
|
- this.applyDateToPicker(base);
|
|
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const fallback = this.toYmd(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
|
|
|
|
+ const seed = base || fallback;
|
|
|
|
|
+ if (field === 'start' && !this.tempStart) this.tempStart = seed;
|
|
|
|
|
+ if (field === 'end' && !this.tempEnd) this.tempEnd = seed;
|
|
|
|
|
+ this.remountRangePicker(seed);
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 强制重建 picker-view,避免日列天数变化后索引错位 */
|
|
|
|
|
+ remountRangePicker(ymd) {
|
|
|
|
|
+ this.rangePickerKey += 1;
|
|
|
|
|
+ this.applyDateToPicker(ymd);
|
|
|
},
|
|
},
|
|
|
applyDateToPicker(ymd) {
|
|
applyDateToPicker(ymd) {
|
|
|
const now = new Date();
|
|
const now = new Date();
|
|
@@ -322,39 +353,56 @@ export default {
|
|
|
if (yi < 0) yi = 0;
|
|
if (yi < 0) yi = 0;
|
|
|
const total = new Date(y, m, 0).getDate();
|
|
const total = new Date(y, m, 0).getDate();
|
|
|
if (d > total) d = total;
|
|
if (d > total) d = total;
|
|
|
|
|
+ if (d < 1) d = 1;
|
|
|
this.pickerDay = d;
|
|
this.pickerDay = d;
|
|
|
- this.datePickerValue = [yi, m - 1, d - 1];
|
|
|
|
|
|
|
+ const next = [yi, m - 1, d - 1];
|
|
|
|
|
+ this.datePickerValue = next;
|
|
|
this.$nextTick(() => {
|
|
this.$nextTick(() => {
|
|
|
- this.datePickerValue = [yi, m - 1, d - 1];
|
|
|
|
|
|
|
+ this.datePickerValue = next.slice();
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
- onDatePickerChange(e) {
|
|
|
|
|
- const val = e.detail.value || [0, 0, 0];
|
|
|
|
|
- const y = this.yearList[val[0]] || this.year;
|
|
|
|
|
- const m = (val[1] || 0) + 1;
|
|
|
|
|
|
|
+ parsePickerValue(val) {
|
|
|
|
|
+ const raw = Array.isArray(val) ? val : [0, 0, 0];
|
|
|
|
|
+ const yi = Math.max(0, Number(raw[0]) || 0);
|
|
|
|
|
+ const mi = Math.max(0, Number(raw[1]) || 0);
|
|
|
|
|
+ const y = this.yearList[yi] || this.year;
|
|
|
|
|
+ const m = mi + 1;
|
|
|
const total = new Date(y, m, 0).getDate();
|
|
const total = new Date(y, m, 0).getDate();
|
|
|
- let d = (val[2] || 0) + 1;
|
|
|
|
|
|
|
+ let d = Math.max(0, Number(raw[2]) || 0) + 1;
|
|
|
if (d > total) d = total;
|
|
if (d > total) d = total;
|
|
|
- this.pickerDay = d;
|
|
|
|
|
- this.datePickerValue = [val[0], val[1], d - 1];
|
|
|
|
|
|
|
+ if (d < 1) d = 1;
|
|
|
|
|
+ return { yi, mi, y, m, d, total };
|
|
|
|
|
+ },
|
|
|
|
|
+ syncPickerToField() {
|
|
|
|
|
+ const { y, m, d } = this.parsePickerValue(this.datePickerValue);
|
|
|
const ymd = this.toYmd(y, m, d);
|
|
const ymd = this.toYmd(y, m, d);
|
|
|
if (this.rangeField === 'start') this.tempStart = ymd;
|
|
if (this.rangeField === 'start') this.tempStart = ymd;
|
|
|
else this.tempEnd = ymd;
|
|
else this.tempEnd = ymd;
|
|
|
|
|
+ return ymd;
|
|
|
},
|
|
},
|
|
|
- confirmRangePicker() {
|
|
|
|
|
- // 滚轮停住后若未触发 change,用当前索引兜底写入当前字段
|
|
|
|
|
- const [yi, mi, di] = this.datePickerValue;
|
|
|
|
|
- const y = this.yearList[yi] || this.year;
|
|
|
|
|
- const m = (mi || 0) + 1;
|
|
|
|
|
- const total = new Date(y, m, 0).getDate();
|
|
|
|
|
- let d = (di || 0) + 1;
|
|
|
|
|
- if (d > total) d = total;
|
|
|
|
|
- const ymd = this.toYmd(y, m, d);
|
|
|
|
|
- if (this.rangeField === 'start') {
|
|
|
|
|
- if (!this.tempStart) this.tempStart = ymd;
|
|
|
|
|
- } else if (!this.tempEnd) {
|
|
|
|
|
- this.tempEnd = ymd;
|
|
|
|
|
|
|
+ onDatePickerChange(e) {
|
|
|
|
|
+ const prev = this.datePickerValue || [0, 0, 0];
|
|
|
|
|
+ const { yi, mi, y, m, d, total } = this.parsePickerValue(e.detail.value);
|
|
|
|
|
+ const dayIdx = Math.min(d - 1, total - 1);
|
|
|
|
|
+ const next = [yi, mi, dayIdx];
|
|
|
|
|
+ this.pickerDay = dayIdx + 1;
|
|
|
|
|
+ this.datePickerValue = next;
|
|
|
|
|
+
|
|
|
|
|
+ const ymd = this.toYmd(y, m, dayIdx + 1);
|
|
|
|
|
+ if (this.rangeField === 'start') this.tempStart = ymd;
|
|
|
|
|
+ else this.tempEnd = ymd;
|
|
|
|
|
+
|
|
|
|
|
+ // 年/月变化会导致日列长度变化,需重建,否则日列视觉与索引错位
|
|
|
|
|
+ if (prev[0] !== yi || prev[1] !== mi) {
|
|
|
|
|
+ this.rangePickerKey += 1;
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.datePickerValue = next.slice();
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
+ },
|
|
|
|
|
+ confirmRangePicker() {
|
|
|
|
|
+ // 始终以当前滚轮为准写回当前字段,避免未触发 change 时显示与提交不一致
|
|
|
|
|
+ this.syncPickerToField();
|
|
|
|
|
|
|
|
if (!this.tempStart || !this.tempEnd) {
|
|
if (!this.tempStart || !this.tempEnd) {
|
|
|
uni.showToast({ title: '请选择开始和结束日期', icon: 'none' });
|
|
uni.showToast({ title: '请选择开始和结束日期', icon: 'none' });
|
|
@@ -707,11 +755,13 @@ export default {
|
|
|
}
|
|
}
|
|
|
.range-preview {
|
|
.range-preview {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- gap: 20rpx;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
margin-bottom: 12rpx;
|
|
margin-bottom: 12rpx;
|
|
|
}
|
|
}
|
|
|
.preview-box {
|
|
.preview-box {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
height: 72rpx;
|
|
height: 72rpx;
|
|
|
line-height: 70rpx;
|
|
line-height: 70rpx;
|
|
|
text-align: center;
|
|
text-align: center;
|
|
@@ -721,6 +771,10 @@ export default {
|
|
|
border: 2rpx solid #e5e5e5;
|
|
border: 2rpx solid #e5e5e5;
|
|
|
border-radius: 12rpx;
|
|
border-radius: 12rpx;
|
|
|
box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ padding: 0 8rpx;
|
|
|
&.on {
|
|
&.on {
|
|
|
color: #449aff;
|
|
color: #449aff;
|
|
|
border-color: #449aff;
|
|
border-color: #449aff;
|
|
@@ -732,11 +786,11 @@ export default {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
}
|
|
}
|
|
|
.pick-item {
|
|
.pick-item {
|
|
|
- height: 80rpx;
|
|
|
|
|
- line-height: 80rpx;
|
|
|
|
|
|
|
+ /* 具体 height/line-height 由 pickerItemStyle(px)控制,与 indicator 对齐 */
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
font-size: 30rpx;
|
|
font-size: 30rpx;
|
|
|
color: #333;
|
|
color: #333;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
}
|
|
|
.pick-confirm {
|
|
.pick-confirm {
|
|
|
margin-top: 12rpx;
|
|
margin-top: 12rpx;
|