ai-watch-app/uni_modules/punch-calendar/components/punch-calendar/punch-calendar.vue

465 lines
12 KiB
Vue

<template>
<view class="punch-calendar-wrapper">
<view class="calendar">
<view class="top flex-bt padding-32">
<view class="prev flex-center" @click="switchMonth('prev')">
<image class="icon" src="../../static/images/calendar-prev.png"></image>
</view>
<text>{{calendarConfig.yearAndMonth}}</text>
<view class="next flex-center" @click="switchMonth('next')">
<image class="icon" :src="nextIconStyle"></image>
</view>
</view>
<view class="center">
<view class="week flex-bt">
<view class="items" v-for="item in calendarConfig.week" :key="item">{{item}}</view>
</view>
<view class="day">
<template v-for="(item,index) in calendarConfig.day">
<view class="flex-start" :key="index" v-if="item.open">
<view class="items flex-center" :class="[child.tag]" v-for="(child,childIndex) in item.list"
:key="childIndex">
<text :class="[child.className,child.active]" @click="chooseDay(item,child)">
{{child.value}}
</text>
</view>
</view>
</template>
</view>
</view>
<view class="bottom flex-center" @click="fold">
<image src="../../static/images/cakebdar-open.png" :class="[!calendarConfig.fold ? 'active' : '']"
class="icon"></image>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'punch-calendar',
props: {
// 成功打卡
successList: {
type: Array,
default: ()=> []
},
// 打卡异常点
errorList: {
type: Array,
default: ()=>[]
},
},
data() {
return {
successArr: [],
errorArr: [],
calendarConfig: {
week: ['日', '一', '二', '三', '四', '五', '六'],
nowYear: '', //当前日历 年
nowMonth: '', //当前日历 月
nowDay: '', //当前日历 日
day: [], //日历列表
fold: false, // 展开/折叠状态
yearAndMonth: '', // 当前日历年月
needClock: true, //当天是否需要打卡
},
monthStatisticList: [],
}
},
computed: {
nextIconStyle: function() {
const {
year,
month
} = this.getNowDate()
const {
nowYear,
nowMonth
} = this.calendarConfig
if (nowYear == year && nowMonth == month) {
return '../../static/images/calendar-next.png';
} else {
return '../../static/images/calendar-next-active.png';
}
},
},
watch: {
successList: {
handler(val) {
this.successArr = JSON.parse(JSON.stringify(val))
this.initCalendar(false)
},
immediate: true,
deep: true,
},
errorList: {
handler(val) {
this.errorArr = JSON.parse(JSON.stringify(val))
this.initCalendar(false)
},
immediate: true,
deep: true,
},
},
methods: {
//切换日历
switchMonth(type = 'next') {
let {nowYear, nowMonth} = this.calendarConfig
let {year, month, day} = this.getNowDate()
if(type == 'next' && nowYear == year && nowMonth == month)return false;
if(type == 'prev'){
if (nowMonth <= 1) {
nowMonth = 12
nowYear -= 1
} else {
nowMonth--;
}
}else{
if (nowMonth >= 12) {
nowMonth = 1
nowYear += 1
} else {
nowMonth++;
}
}
this.$emit('switchMonth', type, nowYear, nowMonth)
this.calendarConfig.nowYear = nowYear
this.calendarConfig.nowMonth = nowMonth
this.calendarConfig.nowDay = (nowYear == year && nowMonth == month) ? day : ''
this.calendarConfig.day = this.calendar(nowYear, nowMonth, this.calendarConfig.nowDay, this.calendarConfig.fold, this.successArr, this.errorArr)
this.calendarConfig.yearAndMonth = this.monDetail(nowYear, nowMonth)
},
chooseDay(item, child) {
if(child.className == 'last' || child.className == 'nextMonth')return false;
this.$emit('chooseDay',child)
this.calendarConfig.day.map(parent=>{
parent.list.map(child=>{
child.className == 'active' && (child.className = '')
return child
})
return parent
})
child.className = 'active'
},
// 展开/折叠
fold(){
const {now, year, month, day} = this.getNowDate()
const {nowYear, nowMonth} = this.calendarConfig
this.calendarConfig.day.map((parent,index)=>{
if(!this.calendarConfig.fold){
parent.open = true
}else{
if(nowYear == year && nowMonth == month){
parent.open = parent.list.reduce((result, child)=>{
if(child.value == day && child.className !== 'last' && child.className !== 'nextMonth'){
result = true
}
return result
}, false)
}else{
parent.open = index == 0 ? true : false
}
}
return parent
})
this.calendarConfig.fold = !this.calendarConfig.fold
},
// 初始化
initCalendar(nowDate = true) {
if(nowDate) {
const { year, month, day } = this.getNowDate()
this.calendarConfig.nowYear = year
this.calendarConfig.nowMonth = month
this.calendarConfig.nowDay = day
}
const {nowYear, nowMonth, nowDay} = this.calendarConfig
// 渲染日历
this.calendarConfig.day = this.calendar(nowYear, nowMonth, nowDay, this.calendarConfig.fold, this.successArr, this.errorArr)
// 日历title
this.calendarConfig.yearAndMonth = this.monDetail(nowYear, nowMonth)
},
// 获取当前年月日
getNowDate(){
let now = new Date()
let year = now.getFullYear()
let month = now.getMonth() + 1
let day = now.getDate()
return {now, year, month, day}
},
// 显示当前年月
monDetail(ynow, mnow) {
return `${ynow}${mnow}`
},
//判断是否为闰年
is_leap(year) {
let res = year % 100 == 0 ? (year % 400 == 0 ? 1 : 0) : (year % 4 == 0 ? 1 : 0)
return res
},
/**
* 渲染日历
* ynow 年
* mnow 月
* dnow 日
* fold 折叠状态
* successArr 需要打卡
* errorArr 异常标记
**/
calendar(ynow, mnow, dnow, fold, successArr = [], errorArr = []) {
let arr = []
var nlstr = new Date(ynow, mnow - 1, 1)
var firstday = nlstr.getDay()
const {now, year, month, day} = this.getNowDate()
//每个月的天数
var m_days = new Array(31, 28 + this.is_leap(ynow), 31, 30, 31, 31, 30, 31, 30, 31, 30, 31)
//当前月天数+第一天是星期几的数值 获得 表格行数
var tr_str = Math.ceil((m_days[mnow-1] + firstday) / 7)
var i, k, idx, date_str;
for (i = 0; i < tr_str; i++) {
//表格每行的单元格
let row = []
for (k = 0; k < 7; k++) {
//单元格自然序列号
idx = i * 7 + k;
//计算日期
date_str = idx - firstday + 1;
if(date_str <= 0){
// 处理上月空缺
row.push({ value: '', className: '' })
}else if(year == ynow && month == mnow){
if(date_str > m_days[mnow-1]) {
row.push({ value: '', className: 'nextMonth' })
}else if(date_str > dnow ){
row.push({ value: date_str, className: 'last', year: ynow, month: mnow, day: date_str})
}else{
row.push({ value: idx - firstday + 1, className: '', year: ynow, month: mnow, day: date_str})
}
}else{
if(date_str > m_days[mnow-1]) {
row.push({ value: '', className: 'nextMonth' })
}else if(date_str > dnow ){
row.push({ value: date_str, className: '', year: ynow, month: mnow, day: date_str})
}else{
row.push({ value: date_str, className: '', year: ynow, month: mnow, day: date_str})
}
}
}
// 处理下月候补日期
let list = row.reduce((arr,item,index)=>{
if(item.className == 'nextMonth'){
if(arr[index-1].value == m_days[mnow-1]){
item.value = 1
}else{
item.value = arr[index-1].value + 1
}
}
// 判断是否为当日
item.className !== 'nextMonth' && item.year == year && item.month == month && item.day == day && (item.active = 'thatDay')
item.className !== 'nextMonth' && item.year == year && item.month == month && item.day == day && (item.className = 'active')
// 处理需要打卡标记
if(item.year && item.month && item.day){
let nowDate = `${item.year}/${item.month}/${item.day}`
let timer = new Date(nowDate).getTime().toString()
successArr.includes(timer) && (item.tag = 'border')
errorArr.includes(timer) && (item.tag = 'error')
}
arr.push(item)
return arr
},[])
arr.push({list, open: false})
}
// 处理默认折叠状态
arr.forEach((parent,index)=>{
if(fold){
parent.open = true
}else{
parent.open = parent.list.reduce((result, child)=>{
if(ynow == year && mnow == month){
child.value == dnow && child.className !== 'nextMonth' && (result = true)
}else{
index == 0 && (result = true)
}
return result
}, false)
}
})
return arr
}
}
}
</script>
<style lang="scss" scoped>
.punch-calendar-wrapper {
.flex-center{
display: flex;
align-items: center;
justify-content: center;
}
.flex-end{
display: flex;
align-items: center;
justify-content: flex-end;
}
.flex-start{
display: flex;
align-items: center;
justify-content: flex-start;
}
.flex-bt{
display: flex;
align-items: center;
justify-content: space-between;
}
.calendar {
width: 686rpx;
background: #ffffff;
border-radius: 16rpx;
box-shadow: 0 10rpx 25rpx rgba(0,0,0,0.1);
.center {
.week {
padding-top: 32rpx;
.items {
font-size: 32rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 600;
color: #8c8f94;
line-height: 32rpx;
width: 98rpx;
text-align: center;
}
}
.day {
width: 100%;
flex-wrap: wrap;
.items {
font-size: 32rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 600;
color: #8c8f94;
line-height: 32rpx;
text-align: center;
padding-top: 24rpx;
padding-bottom: 32rpx;
width: 98rpx;
position: relative;
&.border ::before {
content: '';
width: 12rpx;
height: 12rpx;
background: #b6d1ff;
border-radius: 50%;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
bottom: 10rpx;
border-radius: 50%;
}
&.error::before {
content: '';
width: 12rpx;
height: 12rpx;
background: #ff5040;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
bottom: 10rpx;
border-radius: 50%;
}
text {
width: 48rpx;
height: 48rpx;
display: block;
transition: all 0.6s;
border-radius: 32rpx;
text-align: center;
line-height: 48rpx;
color: #474a50;
&.last,
&.nextMonth {
color: #c0c3c7;
}
&.thatDay {
background: #e6efff !important;
color: #474a50;
}
&.active {
background: #0D67FF !important;
color: #ffffff;
}
}
}
}
}
.bottom {
width: 100%;
height: 78rpx;
border-top: 1px solid #e6efff;
.icon {
width: 32rpx;
height: 32rpx;
transition: all 0.6s;
&.active {
transform: rotate(180deg);
}
}
}
.top {
width: 100%;
height: 112rpx;
border-bottom: 2px solid #e6efff;
text {
font-size: 32rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 600;
color: #1a1a1a;
line-height: 32rpx;
}
.prev,
.next {
width: 100rpx;
height: 100%;
.icon {
width: 40rpx;
height: 40rpx;
display: block;
}
}
}
}
}
</style>