Files

233 lines
5.3 KiB
Vue
Raw Permalink 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 v-if="visible" class="picker-overlay" @click.stop="close">
<view class="picker-container" @click.stop>
<view class="picker-header">
<text class="cancel" @click="close">取消</text>
<text class="title">请选择日期</text>
<text class="confirm" @click="confirm">确定</text>
</view>
<view class="picker-columns">
<!-- -->
<scroll-view
class="column"
scroll-y
:scroll-top="scrollPos[0]"
@scroll="onScroll(0,$event)"
>
<view
v-for="(y,i) in years"
:key="i"
class="item"
:class="{active:selected[0]===i}"
@click="select(0,i)"
>
{{y}}
</view>
</scroll-view>
<!-- -->
<scroll-view
class="column"
scroll-y
:scroll-top="scrollPos[1]"
@scroll="onScroll(1,$event)"
>
<view
v-for="(m,i) in months"
:key="i"
class="item"
:class="{active:selected[1]===i}"
@click="select(1,i)"
>
{{m}}
</view>
</scroll-view>
<!-- -->
<scroll-view
class="column"
scroll-y
:scroll-top="scrollPos[2]"
@scroll="onScroll(2,$event)"
>
<view
v-for="(d,i) in days"
:key="i"
class="item"
:class="{active:selected[2]===i}"
@click="select(2,i)"
>
{{d}}
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
export default {
props: { value: String },
data() {
return {
visible: false,
years: [],
months: Array.from({ length: 12 }, (_, i) => i + 1),
days: [],
selected: [0, 0, 0],
scrollPos: [0, 0, 0], // 小程序 scroll-view 只能用 scroll-top 来滚动
itemH: 40,
scrollTimer: null,
};
},
methods: {
open(val) {
this.visible = true;
const currentYear = new Date().getFullYear();
this.years = Array.from({ length: currentYear - 1980 + 1 }, (_, i) => 1980 + i);
const date = val ? new Date(val) : new Date();
const y = date.getFullYear();
const m = date.getMonth() + 1;
const d = date.getDate();
this.selected = [
this.years.indexOf(y),
m - 1,
0 // 先占位,待 days 生成后再修正
];
this.updateDays(() => {
this.selected[2] = Math.min(d - 1, this.days.length - 1);
this.updateScrollPos();
});
},
close() {
this.visible = false;
},
confirm() {
const y = this.years[this.selected[0]];
const m = this.selected[1] + 1;
const d = this.selected[2] + 1;
const dateStr = `${y}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
this.$emit("input", dateStr);
this.$emit("confirm", dateStr);
this.close();
},
// 点击选择项
select(col, idx) {
this.selected.splice(col, 1, idx);
if (col === 0 || col === 1) {
this.updateDays(() => {
if (this.selected[2] >= this.days.length) {
this.selected[2] = this.days.length - 1;
}
this.updateScrollPos();
});
} else {
this.updateScrollPos();
}
},
// 滚动事件处理(兼容小程序)
onScroll(col, e) {
if (!e.detail) return;
const top = e.detail.scrollTop;
if (this.scrollTimer) clearTimeout(this.scrollTimer);
this.scrollTimer = setTimeout(() => {
const idx = Math.round(top / this.itemH);
this.selected.splice(col, 1, idx);
if (col === 0 || col === 1) {
this.updateDays(() => {
if (this.selected[2] >= this.days.length) {
this.selected[2] = this.days.length - 1;
}
this.updateScrollPos();
});
} else {
this.updateScrollPos();
}
}, 120);
},
// 更新 scroll-top使选中项居中
updateScrollPos() {
this.scrollPos = [
this.selected[0] * this.itemH,
this.selected[1] * this.itemH,
this.selected[2] * this.itemH,
];
},
updateDays(callback) {
const year = this.years[this.selected[0]];
const month = this.selected[1] + 1;
const max = new Date(year, month, 0).getDate();
this.days = Array.from({ length: max }, (_, i) => i + 1);
this.$nextTick(() => {
if (callback) callback();
});
}
}
};
</script>
<style scoped>
.picker-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.4);
display: flex;
justify-content: center;
align-items: flex-end;
z-index: 999;
}
.picker-container {
background: #fff;
width: 100%;
border-radius: 16px 16px 0 0;
}
.picker-header {
display: flex;
justify-content: space-between;
padding: 12px 20px;
font-size: 16px;
}
.cancel { color: #999; }
.confirm { color:#EA4D3E; }
.title { font-weight:bold; }
.picker-columns {
display: flex;
height: 200px;
}
.column {
flex: 1;
height: 100%;
}
.item {
height: 40px;
line-height: 40px;
text-align: center;
color:#666;
}
.item.active {
color:#EA4D3E;
font-weight:bold;
font-size:18px;
}
</style>