基于VUE从零实现一个价格日历(附带讲解,一看就懂)
基于Vue从零实现一个自己的价格日历
-
获取当前时间, methods里面定义方法获取当前传入日期的年月份, 绑定time对象中的年月,



-
实现思路, 每个日期都有一套固定的模板, 共42天如图, 我们只需要拿到当月1号的日期,和拿到当月1号属于一周中的第几天(0-6), 以此推算出当前日历模板42天中每天的日期

代码如图, computed中依赖time中月份或者年份的改变, 计算出每个月的日历模板数据

打印取得的数据结果
-
将计算结果遍历至模板上, 模板代码如下

-
接下来实现日历头部, 样式如下

-
我们需要给每个元素绑定点击事件, 点击一次, data中的time对象年或者月会随之递增或者递减,点击后time更新,computed属性会将visibeDays[]重新计算,


- 现在,一个简单的日历功能已经差不了多少了, 要开始向日历中注入一些自己想要展示的数据, 接收数据

根据业务需求,日历上需要展示剩余库存和价格,小伙伴可根据自己实际业务需求自定义数据
data数据: [{ calendar: "2020-11-24", price: "1111.00",stock: 11109}, {calendar: "2020-11-23", price: "1111.00",stock: 1111}]
field数据: [{'stock': '库存'}, {'price': '价格'}] //要展示的字段和key
- 将数据渲染至对应日期中


6.置灰无效日期和过期日期


- 不属于本月日期,置灰


<!-- 调用方法!
data: 传入的所有数据数据 (用户可点击的所有日期) 数组格式
格式如下
[{ calendar: "2020-11-24", price: "1111.00",stock: 11109},
{calendar: "2020-11-23", price: "1111.00",stock: 1111}]
field: 要在价格日历上展示的字段 如 价格, 库存等... 数组格式
格式如下
[{'stock': '库存'}, {'price': '价格'}]
@formaDate: 用户点击可点击的日期点击回调 回调参数返回用户点击的日期
-->
<template>
<div v-click-outside style="width: 177px; display: inline-block">
<el-input type="text" v-model="formaDate" @focus="focus" placeholder="请选择日期"/>
<!-- <input type="text" v-model="formaDate" @focus="focus" placeholder="请选择日期"/> -->
<div v-show="isVisble" class="pantal">
<div class="triangle"></div>
<div class="header">
<span @click="prevYear"><</span>
<span @click="prevMonth"><<</span>
<span>{{ time.year }}年</span>
<span>{{ time.month + 1 }}月</span>
<span @click="nextMonth">>></span>
<span @click="nextYear" style="text-align: right">></span>
</div>
<div class="content">
<div>
<span v-for="item in weekarr" :key="item">{{ item }}</span>
</div>
<div v-for="i in 6" :key="i">
<span
v-for="j in 7"
:key="j"
class="content-item"
@click="
checkDate(
visibeDays[(i - 1) * 7 + (j - 1)],
isDisabled(visibeDays[(i - 1) * 7 + (j - 1)])
)
"
:class="[
{ checkGray: !notIsThisMonth(visibeDays[(i - 1) * 7 + (j - 1)]) },
{ active: isActive(visibeDays[(i - 1) * 7 + (j - 1)]) },
{ isdisabeld: isDisabled(visibeDays[(i - 1) * 7 + (j - 1)]) },
{ iscancheck: !isDisabled(visibeDays[(i - 1) * 7 + (j - 1)]) },
]"
>
<div class="content-info">
<p style="flex: 1">
{{ visibeDays[(i - 1) * 7 + (j - 1)].getDate() }}
</p>
<p v-for="(ite, index) in field" :key="index">
{{ showText(visibeDays[(i - 1) * 7 + (j - 1)], ite) }}
</p>
</div>
</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "datePickers",
directives: {
// 指令的生命周期
clickOutside: {
// 事件绑定事件
bind(el, bingings, vnode) {
let hander = (e) => {
if (el.contains(e.target)) {
if (!vnode.context.isVisble) {
// vnode.context.focus();
}
} else {
if (vnode.context.isVisble) {
vnode.context.blur();
}
}
};
el.hander = hander;
document.addEventListener("click", hander);
},
unbind(el) {
document.removeEventListener("click", el.hander);
},
},
},
data() {
let { year, month } = this.getYearMonthDay(this.value || new Date());
return {
isVisble: false,
weekarr: ["日", "一", "二", "三", "四", "五", "六"],
value: null,
time: {
year,
month,
},
};
},
methods: {
focus() {
this.isVisble = true;
},
blur() {
this.isVisble = false;
},
getYearMonthDay(date) {
let year = date.getFullYear();
let month = date.getMonth();
let day = date.getDate();
return { year, month, day };
},
getDate(year, month, day) {
return new Date(year, month, day);
},
// 日期模板日期展示不属于这个月
notIsThisMonth(date) {
let { year, month } = this.getYearMonthDay(
this.getDate(this.time.year, this.time.month, 1)
);
let { year: y, month: m } = this.getYearMonthDay(date);
if (year === y && month === m) {
return true;
}
},
isToday(date) {
let { year, month, day } = this.getYearMonthDay(new Date());
let { year: y, month: m, day: d } = this.getYearMonthDay(date);
if (year === y && month === m && day === d) {
return true;
}
},
// 每个日期要展示的自定义字段
showText(date, info) {
let title;
let k;
let item;
for (let key in info) {
title = info[key];
k = key;
}
let { year: y, month: m, day: d } = this.getYearMonthDay(date);
this.data.forEach((element) => {
let splitDateArr = element.calendar.split("-");
if (
Number(splitDateArr[0]) === y &&
Number(splitDateArr[1] - 1) === m &&
Number(splitDateArr[2]) === d
) {
item = element;
}
});
if (item) {
let str = title + ":" + item[k];
return str;
}
},
// 是否禁用
isDisabled(date) {
// let { year, month, day } = utils.getYearMonthDay(new Date())
let { year: y, month: m, day: d } = this.getYearMonthDay(date);
let bool = true;
// 日期小于当前时间 禁用
if(date.getTime() < new Date().getTime() - (1000 * 60 * 60 * 24)){
return bool
}
this.data.forEach((element) => {
let splitDateArr = element.calendar.split("-");
if (
Number(splitDateArr[0]) === y &&
Number(splitDateArr[1] - 1) === m &&
Number(splitDateArr[2]) === d
) {
bool = false;
}
});
return bool;
},
// 用户选中日期
checkDate(date, bool) {
if (bool) return;
this.time = this.getYearMonthDay(date);
let { year, month, day } = this.getYearMonthDay(date);
this.value = date
this.$emit('formaDate', `${year}-${month + 1}-${day}`)
this.isVisble = false;
},
// 点击高亮
isActive(date) {
if(!this.value) return
let { year, month, day } = this.getYearMonthDay(this.value);
let { year: y, month: m, day: d } = this.getYearMonthDay(date);
if (year === y && month === m && day === d) {
return true;
}
},
prevMonth() {
let d = this.getDate(this.time.year, this.time.month, 1);
d.setMonth(d.getMonth() - 1);
this.time = this.getYearMonthDay(d);
},
nextMonth() {
let d = this.getDate(this.time.year, this.time.month, 1);
d.setMonth(d.getMonth() + 1);
this.time = this.getYearMonthDay(d);
},
prevYear() {
let d = this.getDate(this.time.year, this.time.month, 1);
d.setFullYear(d.getFullYear() - 1);
this.time = this.getYearMonthDay(d);
},
nextYear() {
let d = this.getDate(this.time.year, this.time.month, 1);
d.setFullYear(d.getFullYear() + 1);
this.time = this.getYearMonthDay(d);
},
},
// 对象或数组类型必须使用函数
props: {
// value: {
// type: Date,
// default: () => new Date(),
// },
disabledDate: {
type: Array,
default: () => [],
},
data: {
type: Array,
default: () => [],
},
field: {
type: Array,
default: () => [],
},
},
computed: {
formaDate() {
if(!this.value) return
let { year, month, day } = this.getYearMonthDay(this.value);
return `${year}-${month + 1}-${day}`;
},
// 每个月的日期模板
visibeDays() {
let { year, month } = this.getYearMonthDay(
this.getDate(this.time.year, this.time.month, 1)
);
// 拿到当前年月的一号的时间
let startDay = this.getDate(year, month, 1);
// 获取这个月1号是属于这周的第几天 从0-6
let week = startDay.getDay();
// 根据1号的时间 和 属于每一周的天数计算出 当月模板 中 第一个日期
let endDay = startDay - week * 60 * 60 * 24 * 1000;
let arr = [];
// 依次计算当页 42天 每天的时间戳
for (let i = 0; i < 42; i++) {
arr.push(new Date(endDay + i * 60 * 60 * 24 * 1000));
}
return arr;
},
},
};
</script>
<style scoped>
div {
font-size: 16px;
}
.triangle {
width: 0;
height: 0;
overflow: hidden;
border-top: 10px solid transparent;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid #E6E6E6;
position: absolute;
top: -20px;
left: 50px
}
input {
width: 177px;
height: 33px;
border-radius: 3px;
padding-left: 16px;
color: #666;
outline-style: none;
border: 1px solid #ccc;
}
.pantal {
position: absolute;
width: 100%;
background-color: #ffffff;
width: 680px;
box-shadow: 0px 0px 5px #a2a2a2;
z-index: 1000;
margin-top: 16px
}
.header {
/* background: #eee; */
display: flex;
justify-content: space-between;
/* height: 0.3rem; */
}
.header > span {
float: left;
width: 95px;
padding: 0 10px;
font-size: 18px;
box-sizing: border-box;
}
.isdisabeld {
/* background-color: #ccc;
*/
color: rgb(210, 210, 210);
}
.iscancheck {
color: #ff335c;
}
.content {
display: inline-block;
margin: 0 auto;
/* border: 1px solid pink; */
box-sizing: border-box;
}
.content > div {
text-align: center;
}
.isToday {
/* color: #999999; */
}
span {
display: inline-block;
width: 95px;
height: 85px;
line-height: 85px;
vertical-align: middle;
}
.content-item {
/* position: relative; */
height: 85px;
/* line-height: 20px; */
}
.content-info {
/* position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
width: 60px;
height: 60px; */
display: flex;
justify-content: space-between;
flex-direction: column;
}
.content-info p {
width: 95px;
height: 20px;
margin: 0;
font-size: 8px;
line-height: 20px;
}
.checkGray {
color: #666666;
opacity: 0.4;
}
.active {
background-color: rgba(24, 144, 255, 0.3);
color: rgb(24, 144, 255);
/* background-color:#666; */
color: #ff335c;
font-weight: 650;
}
</style>
效果图
小伙伴可以根据自己实际业务需求,进行适当扩展
