日期计划
-
计算年月日
年月日的计算,有两种模式,一种是只计算当月日期,另一种则是将整年的日期都计算出来。在本篇文章里我想着重记录第一种写法。
image.png
我们先来个看图说话,这个二月份有28天,1号是星期五。那是不是说,我们只要从周五开始,按顺序渲染出28个'main__block'就好了呢?其实就是这样,关键是怎么把我们的1号定位到周五,只要这个能够准确定位到,我们的日历自然就出来了。
// 定义每个月的天数,如果是闰年第二月改为29天
// year=2019;month=1(js--month=0~11)
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
daysInMonth[1] = 29;
}
// 获得指定年月的1号是星期几
let targetDay = new Date(year, month, 1).getDay();
// 将要在calendar__main中渲染的列表
let total_calendar_list = [];
let preNum = targetDay;
// 首先先说一下,我们的日期是(日--六)这个顺序也就是(0--6)
// 有了上述的前提我们可以认为targetDay为多少,我们就只需要在total_calendar_list的数组中push几个content为''的obj作为占位
if (targetDay > 0) {
for (let i = 0; i < preNum; i++) {
let obj = {
type: "pre",
content: ""
};
total_calendar_list.push(obj);
}
}
这样一来,1号的位置自然而然就到了我们需要的星期五了,接下来就只需要按顺序渲染就ok啦。下面是剩下日期数组填充,填充完毕之后return出来供我们view层使用。
for (let i = 0; i < daysInMonth[month]; i++) {
let obj = {
type: "normal",
content: i + 1
};
total_calendar_list.push(obj);
}
nextNum = 6 - new Date(year, month+1, 0).getDay()
// 与上面的type=pre同理
for (let i = 0; i < nextNum; i++) {
let obj = {
type: "next",
content: ""
};
total_calendar_list.push(obj);
}
return total_calendar_list;
- 开发日历相关功能
如何选择上一个月或下一个月?
data() {
return {
// ...
selectedYear: new Date().getFullYear(),
selectedMonth: new Date().getMonth(),
selectedDate: new Date().getDate()
};
}
handlePreMonth() {
if (this.selectedMonth === 0) {
this.selectedYear = this.selectedYear - 1
this.selectedMonth = 11
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth - 1
this.selectedDate = 1
}
}
handleNextMonth() {
if (this.selectedMonth === 11) {
this.selectedYear = this.selectedYear + 1
this.selectedMonth = 0
this.selectedDate = 1
} else {
this.selectedMonth = this.selectedMonth + 1
this.selectedDate = 1
}
}
就是这么简单,需要注意的点是跨年的时间转换,我们需要在变更月份的同时把年份也改变,这样才能渲染出正确的日期。
也许大家会有疑问,怎么变更了月份或年份之后不需要重新计算一次日期呢?其实是有计算的,不知大家是否还记得,vue可是数据驱动变更的,我们只需要关注数据的变更即可,其他东西vue都会帮我们解决。
- 如果选中某一天
handleDayClick(item) {
if (item.type === 'normal') {
// do anything...
this.selectedDate = Number(item.content)
}
}
在渲染列表的时候我就给每一个block绑定了click事件,这样做的好处就是调用十分方便,点击每一个block的时候,可以获取该block的内容然后do anything you like
当然我们也可以给外层的父级元素绑定事件监听,通过事件流来解决每个block的点击事件,这里看个人习惯~毕竟元素数量不是特别多
日历完成之后再进行相关的数据操作,以下是实现gif图的完整页面代码
<template>
<div class="mywk__calendar">
<div class="calendar">
<div class="calendar__header">
<div class="header__pre" @click="handlePreMonth">
</div>
<div class="header__title">
{{selectedYear}}年{{selectedMonth + 1}}月{{selectedDate}}日
</div>
<div class="header__next" @click="handleNextMonth">
</div>
<div class="rightStyle">
<div class="boxStyle">
<div class="fontStyle" style="background:#1976d2;"></div>新建计划
</div>
<div class="boxStyle">
<div class="fontStyle" style="background:#4caf50;"></div>计划进行中
</div>
<div class="boxStyle">
<div class="fontStyle" style="background:#3f51b5"></div>计划已完成
</div>
</div>
</div>
<div class="calendar__main">
<div class="main__block-head" v-for="(item, index) in calendarHeader" :key="index">
{{item}}
</div>
<div :class="`main__block ${(item.type === 'pre' || item.type === 'next') ? 'main__block-not' : ''} ${item.type2 === 'pre' ? 'extraStyle' : ''} ${(item.content === selectedDate && item.type === 'normal') && 'main__block-today'}`"
@click="handleDayClick(item)" v-for="(item, index) in total_calendar_list" :key="item.type + item.content + `${index}`">
<p class="dateNum">{{item.content}}</p>
<div class="content" v-for="(list, index) in item.arr" :key="index">
<el-tooltip class="item" effect="dark" :content="list.storeCode+'-'+list.storeName" placement="left-start">
<el-button class="btnClass" :style="changeColor(list)">{{list.storeCode}}-{{list.storeName}}</el-button>
</el-tooltip>
<div class="cancelDiv" v-if="list.state === 1"></div>
<i @click="cancelStore(list)" v-if="list.state === 1" class="el-icon-circle-close cancelIcon"></i>
</div>
<img @click="chooseStore(item)" class="iconClass" :style="`${item.content === selectedDate && item.type === 'normal' && item.type2 !== 'pre' ? 'top:4px' : 'display:none'}`" src="@/../static/add22.png" alt="">
</div>
</div>
</div>
<el-dialog
title="选择店铺"
:visible.sync="dialogVisible"
width="380px">
<el-autocomplete
v-loading="loading"
class="inline-input"
v-model="searchInfo.storeName"
:fetch-suggestions="querySearch"
placeholder="请选择店铺列表"
size="mini"
clearable
ref="input"
:autofocus="focus"
style="width:330px"
@select="handleSelect"
>
<template slot-scope="props">
<div class="name">{{props.item.storeCode}}-{{ props.item.storeName }}</div>
</template>
</el-autocomplete>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel" size="mini">取 消</el-button>
<el-button type="primary" @click="confirm" size="mini">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import Device2 from '@/api/device2'
export default {
data () {
return {
calendarHeader: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
selectedYear: new Date().getFullYear(),
selectedMonth: new Date().getMonth(),
selectedDate: new Date().getDate(),
dialogVisible: false,
reqStore: [],
searchInfo: {
storeName: '',
storeId: '',
storeCode: '',
planDate: ''
},
focus: false,
event: [],
total_calendar_list: [],
loading: false,
planList: []
}
},
mounted () {
this.displayDaysPerMonthT(this.selectedYear)
},
methods: {
changeColor (list) {
switch (list.state) {
case 1:
return 'background:#1976d2;'
case 2:
return 'background:#4caf50;'
case 3:
return 'background:#3f51b5'
default:
return 'background:#fff'
}
},
cancelStore (info) {
if (info.state !== 1) {
this.$message.warning('任务只有在新建状态才能删除')
return
}
this.$confirm('此操作将永久删除该计划, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
var url = '/abnormal/plan/delete/' + info.planItemId
Device2.req2(url, 'post').then(res => {
this.$message.success('删除成功')
this.displayDaysPerMonthT(this.selectedYear)
this.reqStore.push(info)
})
})
},
confirm () {
var url = '/abnormal/plan/save'
var json = this.searchInfo
if (!this.searchInfo.storeName) {
this.$message.warning('请选择店铺再进行保存操作')
return
}
Device2.req2(url, 'post', json).then(res => {
if (res.data.code === 0) {
this.$message.success('新增计划成功')
this.dialogVisible = false
this.displayDaysPerMonthT(this.selectedYear)
this.reqStore.forEach((item, index) => {
if (item.storeId === json.storeId) {
this.reqStore.splice(index, 1)
}
this.clearSearchInfo()
})
}
})
},
chooseStore (info) {
this.dialogVisible = true
this.searchInfo.planDate = info.date + ' 00:00:00'
var that = this
var url = '/abnormal/plan/storeSelect'
this.loading = true
Device2.req(url, 'get').then(res => {
this.loading = false
if (res.data.code === 0) {
var arr = res.data.stores
this.reqStore = arr
setTimeout(function () {
that.$nextTick(() => {
that.$refs.input.$el.querySelector('input').focus()
})
}, 300)
}
})
},
cancel () {
this.dialogVisible = false
this.focus = false
this.clearSearchInfo()
},
clearSearchInfo () {
this.searchInfo.storeName = ''
this.searchInfo.storeCode = ''
this.searchInfo.storeId = ''
this.searchInfo.planDate = ''
},
displayDaysPerMonthT (year) {
var month2 = this.selectedMonth + 1
if (month2 < 10) {
month2 = '0' + month2
}
var url = '/abnormal/plan/' + this.selectedYear + month2
this.loading = true
Device2.req(url, 'get').then(res => {
this.loading = true
if (res.data.code === 0) {
this.loading = false
var arr = res.data.planList
this.planList = arr
// 定义每个月的天数,如果是闰年第二月改为29天
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
daysInMonth[1] = 29
}
// 定义上个月的天数
let daysPreMonth = [].concat(daysInMonth)
daysPreMonth.unshift(daysPreMonth.pop())
// 定义这个月月初需要添加的天数
let addDaysFromPreMonth = new Array(12).fill(null).map((item, index) => {
let day = new Date(year, index, 1).getDay()
if (day === 0) {
return 6
} else {
return day - 1
}
})
// 定义的日历列表数组
var arr2 = new Array(12).fill([])
let total_calendar_list = arr2.map((month, monthIndex) => {
let addDays = addDaysFromPreMonth[monthIndex] + 1,
daysCount = daysInMonth[monthIndex],
daysCountPre = daysPreMonth[monthIndex],
monthDate = []
if (addDays >= 7) {
addDays = addDays - 7
}
for (; addDays > 0; addDays--) {
var days = daysCountPre--
let obj = {
content: days,
type: 'pre',
arr: []
}
monthDate.unshift(obj)
}
for (let i = 1; i <= daysCount; ++i) {
let day = i
if (i < 10) {
day = '0' + i
}
let obj = {
content: i,
type: 'normal',
date: this.selectedYear + '-' + month2 + '-' + day,
arr: []
}
if (this.compareDate(i)) {
obj.type2 = 'pre'
}
arr.forEach(item => {
let planDate = new Date(item.planDate)
var year = planDate.getFullYear()
var month = planDate.getMonth()
var dayTime = planDate.getDate()
this.reqStore.forEach((item2, index) => {
if (item2.storeId === item.storeId) {
this.reqStore.splice(index, 1)
}
})
if (this.selectedYear === year && this.selectedMonth === month) {
if (i === dayTime) {
obj.arr.push({
storeName: item.storeName,
storeCode: item.storeCode,
storeId: item.storeId,
planDate: item.planDate,
state: item.state,
planItemId: item.planItemId
})
}
}
})
monthDate.push(obj)
}
if (monthDate.length > 35) {
for (let i = 42 - monthDate.length, j = 0; j < i; ) {
let obj = {
content: ++j,
type: 'next',
arr: []
}
arr.forEach(item => {
let planDate = new Date(item.planDate)
var year = planDate.getFullYear()
var month = planDate.getMonth()
var dayTime = planDate.getDate()
if (this.selectedYear === year && this.selectedMonth === month) {
if (i === dayTime) {
obj.arr.push({
storeName: item.storeName,
storeCode: item.storeCode,
storeId: item.storeId,
planDate: item.planDate,
state: item.state,
planItemId: item.planItemId
})
}
}
})
monthDate.push(obj)
}
} else {
for (let i = 35 - monthDate.length, j = 0; j < i;) {
let obj = {
content: ++j,
type: 'next',
arr: []
}
arr.forEach(item => {
let planDate = new Date(item.planDate)
var year = planDate.getFullYear()
var month = planDate.getMonth()
var dayTime = planDate.getDate()
if (this.selectedYear === year && this.selectedMonth === month) {
if (i === dayTime) {
obj.arr.push({
storeName: item.storeName,
storeCode: item.storeCode,
storeId: item.storeId,
planDate: item.planDate,
state: item.state,
planItemId: item.planItemId
})
}
}
})
monthDate.push(obj)
}
}
return monthDate
})
this.total_calendar_list = total_calendar_list[this.selectedMonth]
}
}).catch(err => {
this.loading = false
})
},
handleDayClick (item) {
if (item.type === 'normal' && !item.type2) {
// do anything...
this.selectedDate = Number(item.content)
}
},
handlePreMonth () {
if (this.selectedMonth === 0) {
this.selectedYear = this.selectedYear - 1
this.selectedMonth = 11
} else {
this.selectedMonth = this.selectedMonth - 1
}
this.isSelectDate()
this.displayDaysPerMonthT(this.selectedYear)
},
handleNextMonth () {
if (this.selectedMonth === 11) {
this.selectedYear = this.selectedYear + 1
this.selectedMonth = 0
} else {
this.selectedMonth = this.selectedMonth + 1
}
this.isSelectDate()
this.displayDaysPerMonthT(this.selectedYear)
},
compareDate (count) {
var date = new Date()
var year = date.getFullYear()
var month = date.getMonth()
var day = date.getDate()
var result
if (this.selectedYear <= year && this.selectedMonth < month) {
result = true
} else if (this.selectedMonth === month && count < day) {
result = true
} else {
result = false
}
return result
},
isSelectDate () {
var date = new Date()
var year = date.getFullYear()
var month = date.getMonth()
var day = date.getDate()
if (this.selectedYear === year && this.selectedMonth === month) {
this.selectedDate = day
} else {
this.selectedDate = 1
}
},
querySearch (queryString, cb) {
var reqStore = this.reqStore
var results = queryString ? reqStore.filter(this.createFilter(queryString)) : reqStore
// // 调用 callback 返回建议列表的数据
cb(results)
},
handleSelect (item) {
this.searchInfo.storeName = item.storeCode + '-' + item.storeName
this.searchInfo.storeId = item.storeId
this.searchInfo.storeCode = item.storeCode
},
createFilter (queryString) {
return (item) => {
return ((item.storeCode + '-' + item.storeName).toLowerCase().indexOf(queryString.toLowerCase()) !== -1)
}
}
}
}
</script>
<style lang="scss">
@function pxWithVw($n) {
@return 100vw * $n / 375;
}
@function pxWithVwMax($n) {
@return 480px * $n / 375;
}
.rightStyle{
position:absolute;right:10px;top:10px;
display: flex;
}
.boxStyle{
display: flex;align-items: center;font-size:12px;
}
.fontStyle{
width:18px;height:10px;margin:0 10px;border-radius:5px;
}
p{
padding: 0;
}
.content{
position: relative;
}
.cancelDiv{
position: absolute;
right:6px;
top:16px;
width: 10px;
height:10px;
border-radius:50%;
background: #ffffff;
z-index:1
}
.cancelIcon{
color:red;
position: absolute;
right:3px;
top:14px;
font-size:16px;
cursor: pointer;
z-index:2
}
.btnClass{
width:100% !important;
padding: 7px 5px !important;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-size: 11px !important;
margin-top:10px !important;
color:#fff !important;
}
.dateNum{
position: absolute;
top:5px;right:5px;
}
.iconClass{
position: absolute;top:0px;left:8px;
font-size:16px;
color:#eee;cursor: pointer;
width:18px;
transition: top 1s ease-in-out;
}
.mywk__calendar {
width: 92%;
margin: 0 auto;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.tips {
margin: 15px 0 0 0;
text-align: center;
}
.calendar {
position: relative;
flex-shrink: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0 0 0;
border-radius: 4px;
background-color: white;
box-shadow: 0 0 10px rgba(208, 208, 208, 0.5);
padding-bottom: 40px;
padding-top: 20px;
.calendar__header {
color: #2c3135;
font-size: 16px;
width: pxWithVw(315);
max-width: pxWithVwMax(315);
display: flex;
align-items: center;
justify-content: space-between;
line-height: 22px;
margin-top: 17px;
.header__title {
font-size: 16px;
letter-spacing: 1px;
}
.header__pre:hover{
border: 1px solid #ddd;
box-shadow: 0 0 3px #aaa;
}
.header__pre {
height: 24px;
width: 24px;
position: relative;
border-radius: 50%;
cursor: pointer;
&:after {
content: " ";
display: inline-block;
height: 9px;
width: 9px;
border-width: 2px 2px 0 0;
border-color: #c8c8cd;
border-style: solid;
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0) rotate(180deg);
position: absolute;
top: 50%;
margin-top: -4px;
right: 4px;
}
}
.header__next:hover{
border: 1px solid #ddd;
box-shadow: 0 0 3px #aaa;
}
.header__next {
height: 24px;
width: 24px;
position: relative;
border-radius: 50%;
cursor: pointer;
&:after {
content: " ";
display: inline-block;
height: 9px;
width: 9px;
border-width: 2px 2px 0 0;
border-color: #c8c8cd;
border-style: solid;
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
position: absolute;
top: 50%;
margin-top: -4px;
right: 7px;
}
}
}
.calendar__main {
width: 90%;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
padding-top: 19px;
.main__block {
width: 14%;
min-height: 70px;
padding: 20px 5px 15px 5px;
margin-bottom: 15px;
border-radius: 2px;
font-size: 12px;
border: 1px solid #efefef;
align-items: center;
justify-content: center;
color: #666666;
background-color: #fff;
flex-shrink: 0;
position: relative;
.main__block-piont {
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #cce4ff;
position: absolute;
left: calc(50% - 2.5px);
bottom: 0;
}
}
.extraStyle{
background-color: #efefef;
}
.main__block-not {
background-color: #edf2f5;
color: #7f8fa4;
}
.main__block-today {
transition: 0.5s all;
background-color: #cce4ff;
box-shadow: 0 2px 6px rgba(171, 171, 171, 0.5);
}
.main__block-head {
width: 14.2%;
margin-bottom: 15px;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #7f8fa4;
background-color: #fff;
flex-shrink: 0;
}
}
}
</style>