列表中的日历

127 阅读4分钟

1.png 列表里面日历展示

首先分解上面的日历 我用的日期插件是moment,主要用来更好计算日期。你们也可以用其他的也可以 废话不多说直接上代码

    <div class='calendar-table'>
        <a-spin :spinning="loading">
            <div class="calendar-opt space-between">
                <div class="calendar-opt-left">
                    <div class="today xy-center" @click="todayClick">Today</div>
                    <LeftOutlined class="icon" @click="preClick"/>
                    <RightOutlined class="icon" @click="nextClick"/>
                    <span class="data">{{  convertToEnglishMonth(currentMonth) }}</span>
                    <span class="data">{{ currentYear }}</span>
                    <div class="text">
                    <span style="color: #FF7A00;">{{ countParams.employeeCount }} employee</span> on leave today | Total in this month: <span style="color: #FF7A00;">{{ countParams.totalOfMonth }} Days</span>
                    </div>
                </div>

                <div class="calendar-opt-right">
                </div>
            </div>

            <div class="header">
                <div class="container-left">
                    <div class="left-table">
                        <div class="table-header">
                            <div class="header-top">
                                <div>EMPLOYEE</div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="content-center">
                    <div class="center-moment">
                        <div class="moment-day" v-for="(item, index) in datesWithWeekdays" :key="index">
                            <div class="moment-top">{{ item.date }}</div>
                            <div class="moment-bottom">{{ weekDays[item.weekday] }}</div>
                        </div>
                    </div>
                </div>
                <div class="content-right">
                    <div class="right-table">
                    <div class="table-header">
                        <div class="header-top">TOTAL OF LEAVE DAYS</div>
                    </div>
                    </div>
                </div>
        
        
            </div>
        
            <div class="content">
                <div class="content-left">
                    <div style="padding-left: 6px;">
                        <div class="table-data-left" v-for="(item, index) in dataSource" :key="index">
                            <div class="table-header">
                                <div class="header-top">
                                    <div style="color: #66809E;">{{ item.employee }}</div>
                                    <div>{{ item.jobTitle }}</div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="content-center">
                    <template v-for="employee in dataSource" :key="employee.id">
                        <div class="table-data-center">
                            <div 
                                class="cell"
                                v-for="(cell, index) in datesWithWeekdays"
                                :key="index"
                                ref="demoRef"
                                data-width="index"
                                :class="{ 'leave-cell-weekend': isWeekend(weekDays[cell.weekday])}">
                            </div>
                            <div  
                                class="leave-bar"
                                v-for="(range, idx) in getLeaveRanges(employee.timeOffRequestDTOList)"
                                :key="idx"
                                :style="{
                                    background: getListName(range.timeOffType, timeOffTypeList),
                                    left: `${(range.start - 1) * getWidth}px`,
                                    width: `${(range.diffDays + 1) * getWidth}px`,
                                }"
                                @click="detailsClick(range.uuid)"
                            >  
                            {{ range.timeOffTypeName }}
                            </div>  
                        </div>
                    </template>
                </div>
                <div class="content-right">
                    <div class="table-data-right" v-for="(item, index) in dataSource" :key="index">
                        {{ item.inTotal }}
                    </div>
                </div>
            </div>
        </a-spin>
        <detailsModal ref="detailsRef"/>
    </div>
  </template>

这个是html部分,我是手写table,暂时没有分页功能

<script setup lang='ts'>
import moment from 'moment';
import detailsModal from "../components/details-modal.vue"
import { ref, reactive, toRefs, onBeforeMount, onMounted, watchEffect, computed } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import { GetCodeDict, GetBatchCodeDict } from '@/api/public'
const props = defineProps({})
const loading = ref(false)
const dataSource = ref<any[]>([])
const monthStr = {  
    "01": "January",  
    "02": "February",  
    "03": "March",  
    "04": "April",  
    "05": "May",  
    "06": "June",  
    "07": "July",  
    "08": "August",  
    "09": "September",  
    "10": "October",  
    "11": "November",  
    "12": "December"  
};
const params = ref({
    beginTime: moment().startOf('month').format("YYYY-MM-DD"), // 获取当前月份的第一天
    endTime: moment().endOf('month').format("YYYY-MM-DD")// 获取当前月份的最后一天
})
const currentMonth = ref(moment().format("MM"));
const currentYear = ref(moment().year());
function convertToEnglishMonth(monthNumber: any) {
    return monthStr[monthNumber];
}

const preClick = () => {
    params.value.beginTime = moment(params.value.beginTime).subtract(1, 'months').startOf('month').format("YYYY-MM-DD")
    params.value.endTime = moment(params.value.endTime).subtract(1, 'months').endOf('month').format("YYYY-MM-DD")
    currentMonth.value = moment(currentMonth.value).subtract(1, 'months').format("MM")
    currentYear.value = moment(params.value.endTime).year();
    getMonthDays()
    getCalendar()
}
const nextClick = () => {
    params.value.beginTime = moment(params.value.beginTime).add(1, 'months').startOf('month').format("YYYY-MM-DD")
    params.value.endTime = moment(params.value.endTime).add(1, 'months').endOf('month').format("YYYY-MM-DD")
    currentMonth.value = moment(currentMonth.value).add(1, 'months').format("MM")
    currentYear.value = moment(params.value.endTime).year();
    getMonthDays()
    getCalendar()
}
const todayClick = () => {
    params.value.beginTime = moment().startOf('month').format("YYYY-MM-DD")
    params.value.endTime = moment().endOf('month').format("YYYY-MM-DD")
    currentMonth.value = moment().format("MM")
    currentYear.value = moment().year();
    getMonthDays()
    getCalendar()
}
// 创建一个日期范围并获取相应星期  
const datesWithWeekdays = ref([]);  
const getMonthDays = () => {
    datesWithWeekdays.value = []
    for (let m = moment(params.value.beginTime); m.isBefore(params.value.endTime) || m.isSame(params.value.endTime, 'day'); m.add(1, 'days')) {  
        datesWithWeekdays.value.push({
            date: m.format('DD'),  
            weekday: m.format('dddd')
        });  
    }
}
const weekDays = {  
    Monday: 'Mo',  
    Tuesday: 'Tu',  
    Wednesday: 'We',  
    Thursday: 'Th',  
    Friday: 'Fr',  
    Saturday: 'Sa',  
    Sunday: 'Su'  
};

// 使用Moment.js获取该月份的天数
const daysInMonth = computed(() => {
    const date = moment(new Date(currentYear.value, currentMonth.value - 1)).daysInMonth();  
    return date;  
});
// 是否周末,周末两天颜色不是白色
const isWeekend = (dayIndex: any) => {
    return dayIndex === 'Su' || dayIndex === 'Sa';  
};
const getLeaveRanges = (leaves: any) => {
    const result =  leaves?.reduce((ranges:any, leave:any)=>{
        // 判断是否跨月
        if(moment(leave.beginTime).format("MM") != currentMonth.value){
            leave.beginTime = `${currentYear.value}-${currentMonth.value}-01`
        } 

        if(moment(leave.endTime).format("MM") != currentMonth.value){
            leave.endTime = `${currentYear.value}-${currentMonth.value}-${daysInMonth.value}`
        } 

        const start = Math.max(1, new Date(leave.beginTime).getDate());
        const end = Math.min(daysInMonth.value, new Date(leave.endTime).getDate());
        const diffDays = end - start;
        // console.log("========", diffDays);
        
        if (start <= daysInMonth.value && end >= 1) {
            ranges.push({ start, end, timeOffType: leave.timeOffType, diffDays, timeOffTypeName: leave.timeOffTypeName, uuid: leave.uuid });
        }
        return ranges;
    },[])
    // console.log(
    //     "result:",result
    // )
    return result;
};
const getWidth = ref(29.46)
const getCalendar = async () => {
    // 获取日历的接口
    if(code !== 200) return
    dataSource.value = data
    nextTick(() => {
        const ele =  document.querySelector('.table-data-center div.cell')
        console.log('onMounted', ele.getBoundingClientRect().width);
        getWidth.value = ele.getBoundingClientRect().width || 29.46
    })
}

const countParams = ref({
  employeeCount: 0,
  totalOfMonth: 0
})
const getCount = async () => {
    // 接口
    countParams.value = cloneDeep(data)
}
/**
 * 将颜色值HEX格式转换为rgb的格式
 * @param {hex} hex 需要转换的rgb字符串
 * @return {string}  ;
 */
function hexToRgb(hex) {
    let str = hex.replace("#", "");
    if (str.length % 3) {
    return "hex格式不正确!";
    }
    //获取截取的字符长度
    let count = str.length / 3;
    //根据字符串的长度判断是否需要 进行幂次方
    let power = 6 / str.length;
    let r = parseInt("0x" + str.substring(0 * count, 1 * count)) ** power;
    let g = parseInt("0x" + str.substring(1 * count, 2 * count)) ** power;
    let b = parseInt("0x" + str.substring(2 * count)) ** power;

    return `rgba(${r}, ${g}, ${b}, 0.8)`;
}
// 获取颜色,我们的颜色是通过接口拿的,你们可以写死
const getListName = (val: any, codeValue: any) => {
  let name = null;
  let obj = null;
  if (val || val === 0) {
    obj = codeValue.find((item: any) => {
      if (item.dictData == String(val)) return item;
    });
    name = obj?.remark || '#d9d9d9';
  }
  return hexToRgb(name);
};
const timeOffTypeList = ref([])
const getCodeDict = async () => {
  // 调用字典的接口
  timeOffTypeList.value = data
}
const detailsRef = ref(null)
const detailsClick = (uuid: any) => {
    detailsRef.value.showModal(uuid)
}
onBeforeMount(() => {
//console.log('2.组件挂载页面之前执行----onBeforeMount')
})
onMounted( async () => {
    await getMonthDays()
    await getCodeDict()
    await getCalendar()
    await getCount()
}); 

watchEffect(()=>{
})
</script>

这块就是js部分了

<style scoped lang='scss'>
.calendar-table {
    width: 100%;
    height: 100%;
    background-color: #fff;
    padding: 24px;
    border-radius: 8px;
}
.calendar-opt {
    &-left {
        display: flex;
        align-items: center;
        column-gap: 32px;
        .today {
        border: 1px solid #FF7A00;
        width: 80px;
        height: 36px;
        border-radius: 20px;
        color: #FF7A00;
        font-weight: 600;
        cursor: pointer;
        }
        .icon {
        color: #66809E;
        font-weight: 600;
        cursor: pointer;
        font-size: 14px;
        }
        .data {
        font-size: 24px;
        color: #66809E;
        font-weight: 600;
        }
        .text {
        font-weight: 500;
        color: #66809E;
        }
    }
    &-right {
        display: flex;
        align-items: center;
        column-gap: 16px;
    }
}
.header {
    margin-top: 16px;
    display: flex;
    height: 64px;
    color: #66809E;
    .container-left {
        flex: 0.2;
        .left-table {
            background-color: #eaebf0;
            display: flex;
            height: 64px;
            padding: 6px 6px;
            .table-header {
                display: flex;
                align-items: center;
                flex: 1;
                .header-top {
                text-align: left;
                }
            }
        }
    }
    .content-center {
        flex: 0.73;
        border-left: 1px solid #fff;
        border-right: 1px solid #fff;
        .center-moment {
            background-color: #eaebf0;
            display: flex;
            height: 64px;
            padding: 6px 0;
        .moment-day {
            display: flex;
            flex-direction: column;
            flex: 1;
            .moment-top {
                flex: 1;
                text-align: center;
            }
            .moment-bottom {
                text-align: center;
            }
        }
        }
        
    }
    .content-right {
        flex: 0.07;
        .right-table {
            background-color: #eaebf0;
            display: flex;
            height: 64px;
            padding: 6px 6px;
            .table-header {
                display: flex;
                align-items: center;
                .header-top {
                    flex: 1;
                    text-align: center;
                }
            }
        }
    }
}

.content {
    display: flex;
    margin: 0;
    .content-left {
        flex: 0.2;
        .table-data-left {
            height: 56px;
            display: flex;
            .table-header {
                display: flex;
                align-items: center;
                padding: 6px 0px;
                flex: 1;
                border-bottom: 1px solid #DAE0E6;
                .header-top {
                    text-align: left;
                }
                .header-right {
                    text-align: right;
                }
            }
        }
    }
    .content-center {
        flex: 0.73;
    }
    .content-right {
        flex: 0.07;
    }
}
.table-data-left {
    height: 56px;
    display: flex;
    // border-right: 1px solid #DAE0E6;
    .table-header {
    display: flex;
    align-items: center;
    padding: 6px 0px;
    flex: 1;
    border-bottom: 1px solid #DAE0E6;
    .header-top {
        text-align: left;
    }
    .header-right {
        text-align: right;
    }
    }
}
.table-data-center {
    height: 56px;
    display: flex;
    position: relative;
    .cell {
        flex: 1;
        border-left: 1px solid #DAE0E6;
    }
}
.table-data-right {
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 0px 12px 0 0;
    border-bottom: 1px solid #DAE0E6;
}

.leave-cell-weekend {
    background-color: #fafafb
}
.leave-bar {  
    position: absolute;  
    top: 0;  
    bottom: 0;
    left: 0;
    cursor: pointer;
    display: flex;
    font-size: 12px;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    color: #66809E;
    padding: 0px 3px;
}
</style>

这块就是样式部分了。大概就是这些,一步一步去分解很好弄的