列表里面日历展示
首先分解上面的日历 我用的日期插件是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>
这块就是样式部分了。大概就是这些,一步一步去分解很好弄的