需求:实现用户以日、月、年为单位来选择时间范围
(描述:如果select选择框选中日,后面的日期选择器就是以日为单位的;select选择框选中月,右侧的日期选择器则是以月为单位的;select选择框选中年,右侧的日期选择器就是以年为单位的)
elementUI提供了现成的以日和月的单位来选择的时间选择器,但是没有按年选择的时间选择器。
效果如下:
按日、月、年选择日期范围
yearPicker完整代码如下:
<template>
<div class="yearPicker" ref="yearPicker" :width="width" @mouseenter="onHover"
@mouseleave="onleave">
<svg-icon icon-class="date-copy" style="fontSize:8px;" class="dateIcon"/>
<input class="_inner" ref="inputLeft" v-model="startShowYear" @focus="onFocus" @blur="onBlur" type="text"
name="yearInput" @keyup="checkStartInput($event)" placeholder="开始年份" readonly="readonly"/>
<span class="toStyle">{{ sp }}</span>
<input class="_inner" ref="inputRight" v-model="endShowYear" @focus="onFocus" @blur="onBlur" type="text"
name="yearInput" @keyup="checkEndInput($event)" placeholder="结束年份" readonly="readonly"/>
<svg-icon
v-show="isHover"
icon-class="clearAll"
style="position: absolute; right: 8px; top: 8px; fontSize: 8px;"
@click="clearDate"
></svg-icon>
<div class="_inner floatPanel" v-if="showPanel">//快捷键
<div style="width:70px;">
<div class="item-left" @click="onChooseYear(1)">最近一年</div>
<div class="item-left" @click="onChooseYear(3)">最近三年</div>
<div class="item-left" @click="onChooseYear(5)">最近五年</div>
</div>
<div class="_inner leftPanel">
<div class="_inner panelHead">
<i class="_inner el-icon-d-arrow-left " @click="onClickLeft"></i>
{{ leftYearList[0] + "-" + leftYearList[9] }}
</div>
<div class="_inner panelContent">
<div :class="{
oneSelected: item === startYear && oneSelected,
startSelected: item === startYear,
endSelected: item === endYear,
betweenSelected: item > startYear && item < endYear,
beyondSelected:item < nowYear - 19 || item > nowYear
}" v-for="item in leftYearList" :key="item">
<a :class="{
cell: true,
_inner: true,
selected: item === startYear || item === endYear,
}" @click="onClickItem(item)" @mouseover="onHoverItem(item)">
{{ item }}
</a>
</div>
</div>
</div>
<div class="_inner rightPanel">
<div class="_inner panelHead">
<i class="_inner el-icon-d-arrow-right" @click="onClickRight"></i>
{{ rightYearList[0] + "-" + rightYearList[9] }}
</div>
<div class="_inner panelContent">
<div :class="{
startSelected: item === startYear,
endSelected: item === endYear,
betweenSelected: item > startYear && item < endYear,
beyondSelected:item < nowYear - 19 || item > nowYear
}" v-for="item in rightYearList" :key="item">
<a :class="{
cell: true,
_inner: true,
selected: item === endYear || item === startYear,
}" @click="onClickItem(item)" @mouseover="onHoverItem(item)">
{{ item }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import moment from "moment";
const SELECT_STATE = {
unselect: 0,
selecting: 1,
selected: 2,
};
export default {
name: "yearPicker",
computed: {
nowYear(){
let date=new Date();
return date.getFullYear();
},
oneSelected() {
return (
this.curState === SELECT_STATE.selecting &&
(this.startYear === this.endYear || this.endYear == null)
);
},
startDate() {
return this.startYear;
},
leftYearList() {
return this.yearList.slice(0, 10);
},
rightYearList() {
return this.yearList.slice(10, 20);
},
startYearFormat() {
if (this._.isNil(this.startYear)) {
return null;
}
return moment(this.startYear).startOf("year").format("yyyy");
},
endYearFormat() {
if (this._.isNil(this.endYear)) {
return null;
}
return moment(this.endYear).endOf("year").format("yyyy");
},
},
props: {
width: {
default: 100,
},
labelWidth: {
default: 35,
},
// labelText: {
// default: "时间标签",
// },
sp: {
default: "至",
},
initYear: {
default: null,
},
},
data() {
return {
itemBg: {},
startShowYear: null,
endShowYear: null,
yearList: [],
showPanel: false,
startYear: null,
endYear: null,
curYear: 0,
curSelectedYear: 0,
curState: SELECT_STATE.unselect,
isHover: false,
};
},
methods: {
onHover() {
this.isHover = true;
},
onleave(){
this.isHover = false;
},
clearDate() {
this.startYear = null;
this.endYear = null;
this.startShowYear = null;
this.endShowYear = null;
this.$emit("updateTimeRange", {
startYear: null,
endYear: null,
});
},
onChooseYear(year) {
let vm = this;
vm.endYear = parseInt(moment().format("yyyy"));
vm.startYear = moment().format("yyyy") - year;
vm.startShowYear = vm.startYear;
vm.endShowYear = vm.endYear;
this.$emit("updateTimeRange", {
startYear: moment(this.startYear + "")
.startOf("year")
.valueOf(),
endYear:
moment(this.endYear + "")
.endOf("year")
.valueOf() ,
});
},
checkStartInput(event) {
if (isNaN(this.startShowYear)) {
this.startShowYear = this.startYear;
} else {
this.startYear = this.startShowYear * 1;
this.changeYear();
}
},
checkEndInput() {
if (isNaN(this.endShowYear)) {
this.endShowYear = this.endYear;
} else {
this.endYear = this.endShowYear * 1;
this.changeYear();
}
},
changeYear() {
if (this.startYear > this.endYear) {
let tmp = this.endYear;
this.endYear = this.startYear;
this.startYear = tmp;
this.startShowYear = this.startYear;
this.endShowYear = this.endYear;
}
if (this.startYear && this.endYear) {
this.$emit("updateTimeRange", {
startYear: moment(this.startYear + "")
.startOf("year")
.valueOf(),
endYear:
moment(this.endYear + "")
.endOf("year")
.valueOf() ,
});
} else {
console.warn("WARN:年份不合法", this.startYear, this.endYear);
}
},
onHoverItem(iYear) {
if (this.curState === SELECT_STATE.selecting) {
let tmpStart = this.curSelectedYear;
this.endYear = Math.max(tmpStart, iYear);
this.startYear = Math.min(tmpStart, iYear);
}
},
onClickItem(iYear) {
if (
this.curState === SELECT_STATE.unselect ||
this.curState === SELECT_STATE.selected
) {
this.startYear = iYear;
this.curSelectedYear = iYear;
this.endYear = iYear;
this.curState = SELECT_STATE.selecting;
} else if (this.curState === SELECT_STATE.selecting) {
this.endShowYear = this.endYear;
this.startShowYear = this.startYear;
this.curState = SELECT_STATE.selected;
this.$emit("updateTimeRange", {
startYear: moment(this.startYear + "")
.startOf("year")
.valueOf(),
endYear:
moment(this.endYear + "")
.endOf("year")
.valueOf() ,
});
setTimeout(() => {
//为动画留的时间,可优化
this.showPanel = false;
}, 300);
}
},
onFocus() {
this.$nextTick(() => {
this.showPanel = true;
});
},
onBlur() {
// this.showPanel = false;
},
updateYearList() {
let iStart = Math.floor(this.curYear / 10) * 10 - 10;
iStart = iStart < 0 ? 0 : iStart;
this.yearList = [];
for (let index = 0; index < 20; index++) {
this.yearList.push(iStart + index);
}
},
closePanel(e) {
if (!this.showPanel) {
return;
}
if (typeof e.target.className !== "string") {
this.$nextTick(() => {
this.showPanel = false;
});
return;
}
if (
e.target.className.indexOf("_inner") === -1 ||
(e.target.name === "yearInput" &&
e.target !== this.$refs.inputLeft &&
e.target !== this.$refs.inputRight)
) {
this.$nextTick(() => {
this.showPanel = false;
});
}
e.stopPropagation();
return false;
},
onClickLeft() {
this.curYear = this.curYear * 1 - 10;
this.updateYearList();
},
onClickRight() {
this.curYear = this.curYear * 1 + 10;
this.updateYearList();
},
//------------------对外接口------------------------
//直接传时间戳
setYear(startYearStamp, endYearStamp) {
if (!isNaN(startYearStamp) && !isNaN(endYearStamp)) {
let startYear = moment(startYearStamp).format("yyyy");
let endYear = moment(endYearStamp).format("yyyy");
this.startYear = startYear * 1;
this.endYear = endYear * 1;
this.endShowYear = endYear * 1;
this.startShowYear = startYear * 1;
}
},
},
created() {
this.curYear = moment().format("yyyy");
this.updateYearList();
},
beforeUnmount() {
document.removeEventListener("click", this.closePanel.bind(this));
},
mounted() {
this.$refs.yearPicker.style = "padding-left:" + this.labelWidth + "px";
document.addEventListener("click", this.closePanel.bind(this));
},
};
</script>
<style lang="scss" scoped>
.yearPicker {
font-size: 14px;
display: flex;
position: relative;
transition: all 0.3s;
width: 275px;
height: 33px;
border: 1px solid #B3B3B3;
input:first-child {
text-align: right;
}
.dateIcon {
position: absolute;
left: 8px;
}
background-color: #fff;
span {
padding: 0 20px;
height: 32px;
line-height: 32px;
}
border: 1px solid #eff1f3;
height: 34px;
line-height: 34px;
border-radius: 4px;
padding: 0 28px 0 8px;
box-sizing: border-box;
.floatPanel {
>div {
width: 50%;
}
padding: 0 16px;
position: absolute;
display: flex;
background-color: #fff;
z-index: 2000;
border-radius: 4px;
width: 650px;
height: 1.3982rem;
top: -1.459rem;
left: -0.4073rem;
box-shadow: 0 2px 12px 0 rgba(195, 195, 195, 10%);
.panelContent {
display: flex;
flex-wrap: wrap;
width: 100%;
height: calc(100% - 70px);
.oneSelected {
border-top-right-radius: 24px;
border-bottom-right-radius: 24px;
}
.startSelected {
background-color: #f6f6f7;
border-top-left-radius: 24px;
border-bottom-left-radius: 24px;
}
.endSelected {
background-color: #f6f6f7;
border-top-right-radius: 24px;
border-bottom-right-radius: 24px;
}
.beyondSelected{
background-color: #F5F7FA;
color:#C2C6CE;
pointer-events: none;
}
.betweenSelected {
background-color: #f6f6f7;
}
>div {
width: 75px;
height: 48px;
line-height: 48px;
margin: 3px 0;
// border-radius: 24px;
text-align: center;
a {
display: inline-block;
width: 60px;
height: 36px;
cursor: pointer;
line-height: 36px;
border-radius: 18px;
}
.selected {
background-color: #3e77fc;
color: #fff;
}
}
}
.panelHead {
position: relative;
height: 46px;
line-height: 46px;
text-align: center;
i {
position: absolute;
cursor: pointer;
&:hover {
color: #3e77fc;
}
}
}
.rightPanel {
padding-left: 8px;
}
.leftPanel .panelHead i {
left: 20px;
// vertical-align: middle;
line-height: 46px;
}
.rightPanel .panelHead i {
right: 20px;
line-height: 46px;
// vertical-align: middle;;
}
}
.floatPanel::before {
content: "";
height: 100%;
position: absolute;
left: 50%;
width: 1px;
border-left: 1px solid #e4e4e4;
}
}
input {
width: 60px;
border: none;
height: 32px;
line-height: 32px;
box-sizing: border-box;
background-color: transparent;
}
input:focus {
outline: none;
background-color: transparent;
}
.yearPicker:hover {
border-color: #3e77fc;
}
.dateIcon {
position: absolute;
right: 16px;
top: 6px;
color: #adb2bc;
}
.item-left {
height: 30px;
line-height: 30px;
cursor: pointer;
margin-top: 10px;
}
</style>
datePicker 完整代码如下:
<template>
<div class="timePicker">
<el-select
v-model="value.dateType"
placeholder="请选择"
ref="dateType"
style="width: 65px; height: 30px"
class="selectType"
:disabled="status"
@change="onChangeTimePicker"
>
<el-option
v-for="item in dateOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
<div
class="block showtime"
v-if="value.dateType === 'day'"
style="margin-left: -1px"
>
<el-date-picker
v-model="dateRange"
type="daterange"
align="right"
unlink-panels
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="pickerDayOptions"
style="width: 300px"
class="datePicker"
:disabled="status"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
>
</el-date-picker>
</div>
<div class="block" v-else-if="value.dateType === 'month'">
<el-date-picker
v-model="dateRange"
type="monthrange"
align="right"
unlink-panels
range-separator="至"
start-placeholder="开始月份"
end-placeholder="结束月份"
:picker-options="pickerMonthOptions"
style="width: 300px"
class="monthPicker"
format="yyyy-MM"
value-format="yyyy-MM"
>
</el-date-picker>
</div>
<div class="block" v-else-if="value.dateType === 'year'">
<yearPicker
style="width: 300px; height: 32px"
ref="statisticPicker"
:initYear="dateRange"
@updateTimeRange="updateStatisticYear"
:width="300"
/>
</div>
</div>
</template>
<script>
import yearPicker from "./yearPicker.vue";
import moment from "moment";
export default {
components: {
yearPicker,
},
props: {
value: {
type: Object,
},
status: {
type: Boolean,
},
},
watch: {
status(newValue, oldValue) {
if (newValue) {
this.dateRange = [];
this.value.dateType = "day";
}
},
dateRange(newValue, oldValue) {
if (newValue) {
this.value.startTime = newValue[0];
this.value.endTime = newValue[1];
} else {
this.value.startTime = "";
this.value.endTime = "";
}
// this.$emit('getDateType',this.value.dateType)
// console.log("+++",this.value.dateType)
},
},
data() {
return {
dateType: "day",
dateOptions: [
{
value: "day",
label: "日",
},
{
value: "month",
label: "月",
},
{
value: "year",
label: "年",
},
],
dateRange: [],
pickerDayMinDate: "",
pickerDayOptions: {
shortcuts: [
{
text: "最近一周",
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(moment(start).subtract(7, "days").valueOf());
picker.$emit("pick", [start, end]);
},
},
{
text: "最近一个月",
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(moment(start).subtract(1, "months").valueOf());
picker.$emit("pick", [start, end]);
},
},
// {
// text: "最近三个月",
// onClick(picker) {
// const end = new Date();
// const start = new Date();
// start.setTime(moment(start).subtract(3, "months").valueOf());
// picker.$emit("pick", [start, end]);
// },
// },
],
onPick: ({ maxDate, minDate }) => {
this.pickerDayMinDate = minDate.getTime();
if (maxDate) {
this.pickerDayMinDate = "";
}
},
disabledDate: (time) => {
if (this.pickerDayMinDate !== "") {
let beforeDate = moment(this.pickerDayMinDate).subtract(59, "days");
let afterDate = moment(this.pickerDayMinDate).add(59, "days");
return (
moment(time).isAfter(afterDate) ||
moment(time).isBefore(beforeDate) ||
time.getTime() > Date.now()
);
}
return time.getTime() > Date.now();
},
},
pickerMonthMinDate: "",
pickerMonthOptions: {
shortcuts: [
{
text: "本月",
onClick(picker) {
picker.$emit("pick", [new Date(), new Date()]);
},
},
{
text: "今年至今",
onClick(picker) {
const end = new Date();
const start = new Date(new Date().getFullYear(), 0);
picker.$emit("pick", [start, end]);
},
},
{
text: "最近六个月",
onClick(picker) {
const end = new Date();
const start = new Date();
start.setMonth(start.getMonth() - 6);
picker.$emit("pick", [start, end]);
},
},
],
onPick: ({ maxDate, minDate }) => {
this.pickerMonthMinDate = minDate.getTime();
if (maxDate) {
this.pickerMonthMinDate = "";
}
},
disabledDate: (time) => {
if (this.pickerMonthMinDate !== "") {
let beforeDate = moment(this.pickerMonthMinDate).subtract(
19,
"months"
);
let afterDate = moment(this.pickerMonthMinDate).add(19, "months");
return (
moment(time).isAfter(afterDate) ||
moment(time).isBefore(beforeDate) ||
time.getTime() > Date.now()
);
}
return time.getTime() > Date.now();
},
},
};
},
methods: {
onChangeTimePicker(dateType){
debugger
this.dateRange = [];
this.value.startTime ='';
this.value.endTime ='';
},
updateStatisticYear(dateRange) {
if(dateRange.startYear){
this.value.startTime = moment(dateRange.startYear).format("yyyy");
this.value.endTime = moment(dateRange.endYear).format("yyyy");}
if(!dateRange.startYear){
this.value.startTime=null;
this.value.endTime=null;
}
},
},
created() {
// 初始化数据
if (this.value) {
this.value.dateType = this.value.dateType ? this.value.dateType : "day";
this.value.startTime = this.value.startTime
? this.value.startTime
: undefined;
this.value.endTime = this.value.endTime ? this.value.endTime : undefined;
if (this.value.startTime && this.value.endTime) {
this.dateRange = [this.value.startTime, this.value.endTime];
} else {
this.dateRange = [];
}
} else {
this.value = {
dateType: "day",
startTime: undefined,
endTime: undefined,
};
this.dateRange = [];
}
},
mounted() {
document
.getElementsByClassName("el-range-input")[1]
.classList.remove("input-margin");
document
.getElementsByClassName("el-range-input")[1]
.classList.add("input-margin1");
},
};
</script>
<style lang="scss" scoped>
.timePicker {
width: 460px;
display: flex;
.selectType {
width: 61px;
height: 31px;
margin-right: 5px;
}
.showtime {
flex: 1;
margin-left: 5px;
.el-date-editor.el-input,
.el-date-editor.el-input__inner {
width: 280px;
}
.input-margin1 {
// .el-range-editor--medium .el-range-input {font-size: 0.07292rem;
// margin-left: 5px;
font-size: 0.07292rem;
margin-left: 5px;
}
}
}
</style>
<style>
.datePicker .el-range-separator {
width: 56px !important;
color: #696b6f;
}
.monthPicker .el-range-separator {
width: 56px !important;
color: #696b6f;
}
.timePicker .el-input__inner {
height: 35px !important;
}
</style>