基于ant Design Vue日历组件a-calendar封装的日历组件
示例
结构
<month-calendar :date="dateStr" :data="workData" @panelChange="onPanelChange">
<template v-slot:header="{ date }">
<div class="calendar-header">
<a-month-picker :value="date" :allow-clear="false" @change="onPanelChange"></a-month-picker>
<div>其他内容呀</div>
</div>
</template>
<template v-slot:dateCell="{ item }">
<div class="calendar-data">
<div v-if="item.todaySummary" class="calendar-data__text">{{ item.todaySummary }}</div>
</div>
</template>
<template v-slot:dateTooltip="{ item }">
<div v-if="item.todaySummary" class="popover-content">{{ item.todaySummary }}</div>
</template>
</month-calendar>
逻辑
import MonthCalendar from './modules/monthCalendar.vue';
import moment from 'moment';
export default {
name: 'Test',
components: {
MonthCalendar
},
data() {
return {
date: moment(),
workData: {}
}
},
computed: {
dateStr() {
return this.date.format('YYYY-MM-DD');
}
},
created() {
this.queryMonth();
},
methods: {
queryMonth() {
const month = this.date.format('YYYY-MM');
const workData = {
[`${month}-12`]: {tooltip: true, todaySummary: '123'},
[`${month}-13`]: {tooltip: true, todaySummary: '工作内容'},
[`${month}-15`]: {tooltip: true, todaySummary: '广袤星河里亿万生命,何其有幸,与你一同在场,就在,这里。'},
[`${month}-16`]: {tooltip: true, todaySummary: '从每一个寻寻觅觅的日子里,穿行而过,看似捕手的,可能也是猎物,看似平常的,可能正是宝藏。灵魂触角,敏感或是迟钝,都有权尽情舞蹈,大千世界啊,风马牛秘密联络,肉眼所得,仅供参考。'},
[`${month}-18`]: {tooltip: true, todaySummary: '过去,当下,未来,未必真有界限,时间,只是行一个方便的幻觉。所以,无忧无惧,专心游戏,无牵无挂,寻宝之旅。'},
[`${month}-21`]: {tooltip: true, todaySummary: '常见的一个定义是:“材料是提供文章内容和表达主题的事物和观念。”严格地说,“事物”并不是材料:尚未反映到头脑中的“事物”不会是材料;已经反映到头脑中(或写入文章中)的“事物”,已是一种观念,一种关于“事物”的感性或理性的认识。这是唯物论的常识。与此相一致,人们有“文章是客观事物的反映”的正确命题:“反映”二字,不独指文章的观点,也指其中的材料。也正因为如此,人们评价文中材料时才有“真与假”、“片面与全面”等标准。如果材料的外延包含与“观念”相对的“事物”本身,那材料(事物)就没有“真假”、“偏全”等区别了。所以,材料是“事物”的说法不能成立'},
};
this.workData = Object.freeze(workData);
console.log(workData);
},
onPanelChange(value) {
// 日期变化
this.date = value;
this.queryMonth();
}
}
}
样式
@font-df: 0.14rem;
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.calendar-data {
min-height: calc(@font-df * 1.6 * 4 + 0.2rem); // 4行 + padding-bottom
padding: 0 0.2rem 0.4rem;
&__text {
display: -webkit-box !important;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical !important;
word-break: break-all;
-webkit-line-clamp: 3; /* 两行 */
line-height: 1.4em; /* 行高 */
max-height: 4.2em; /* 1.4 * 2 */
}
}
.popover-content {
max-width: 500px;
white-space: pre-wrap;
}
API
attributes
events
事件名 | 说明 | 参数 |
panelChange | 日期面板变化回调 | data:moment, mode: string |
select | 点击选择日期回调 | data:moment |
scoped slot
插槽名 | 说明 |
header | 自定义头部,参数:{date} |
dateCell | 自定义日期单元格,参数:{item} |
dateTooltip | 自定义tooptip,参数:{item} |
组件代码
结构、逻辑
import moment from 'moment';
export default {
name: 'MonthCalendar',
props: {
date: {
// 查询日期
type: String,
default: ''
},
format: {
// 日期格式
type: String,
default: 'YYYY-MM-DD'
},
data: {
type: Object,
default: () => ({})
},
defaultProps: {
type: Object,
default: () => ({ tooltip: 'tooltip' }),
}
},
computed: {
dutyDate() {
if (this.date) {
const date = moment(this.date, this.format);
if (date.isValid()) {
return date;
}
}
// date 没传日期或传递日期格式错误 默认今天
const today = moment();
return today;
},
validRange() {
// 可以显示的日期 对应月份
let date = moment(this.dutyDate);
const start = date.startOf('month'); // 会修改原始值
date = moment(this.dutyDate);
const end = date.endOf('month');
return [start, end];
},
todayDate() {
// 今天
return moment(moment().format('YYYY-MM-DD'));
},
tooltipKey() {
return this.defaultProps.tooltip || 'tooltip';
}
},
methods: {
headerRender() {
// 自定义头部内容
if (this.$scopedSlots.header) {
// 处理作用域插槽 header
return (this.$scopedSlots.header({ date: this.dutyDate }));
}
return (<div class="fullcalendar-header">
<a-month-picker
value={this.dutyDate}
allowClear={false}
onChange={this.onChangeMonth}
></a-month-picker>
</div>);
},
onChangeMonth(date, dateString) {
// 选择日期发生变化
this.$emit('panelChange', date, 'month');
},
onPanelChange(value, mode) {
// 日期面板变化回调
this.$emit('panelChange', value, mode);
},
onSelect(date) {
// 点击选择日期回调
this.$emit('select', date);
}
},
render(h) {
let lastDay = this.validRange[1].day(); // 星期几(星期日为 0, 星期六为 6)
if (lastDay === 0) lastDay = 7; // 当月最后一天是星期几 1-7
const last = moment(this.validRange[1]).add((7 - lastDay), 'days'); // 显示的最后一天 (显示从星期一开始)
const dateFullCellRender = (value) => {
// 作用域插槽 dateFullCellRender
if (value.isAfter(last)) {
// 最后一行 不渲染
return null;
}
const day = value.date();
const date = value.format(this.format);
let item = {}, itemHas = false;
if (this.data[date]) {
item = this.data[date];
itemHas = true;
}
const isSameMonth = (value.isAfter(this.validRange[0]) || value.isSame(this.validRange[0])) && value.isBefore(this.validRange[1]); // 本月
const isSameDay = this.todayDate.isSame(value); // 当天
const dateFullCell = (<div class='fullcalendar-date'>
<div class='fullcalendar-value'>
<span class={[isSameMonth ? 'is-same-month' : '', isSameDay ? 'is-same-day' : '']}>{day}</span>
</div>
{/* 处理作用域插槽 dateCell */}
{this.$scopedSlots.dateCell
?
this.$scopedSlots.dateCell({ item: item })
:
<div class="fullcalendar-content">
{itemHas ? (<a-icon type="calendar" theme="twoTone" style="font-size: 36px;" />) : ''}
</div>
}
</div>);
return (<div class="fullcalendar-date-wrap">
{/* 处理作用域插槽 dateTooltip */}
{(this.$scopedSlots.dateTooltip && !!item[this.tooltipKey])
?
(<a-popover title={date}>
<template slot="content">{this.$scopedSlots.dateTooltip({ item: item })}</template>
{dateFullCell}
</a-popover>)
:
dateFullCell
}
</div>);
}
return (
// validRange={this.validRange}
<a-calendar
value={this.dutyDate}
headerRender={this.headerRender}
class="month-calendar"
scopedSlots={{ dateFullCellRender }}
onPanelChange={this.onPanelChange}
onSelect={this.onSelect}
>
</a-calendar>
);
}
}
样式
@primaryColor: #1890FF;
@primaryLight: #F2F8FD;
@tableBorderColor: #EBEEEF;
@white: #FFFFFF;
@font-lg: 0.16rem;
@font-df: 0.14rem;
@fontColor: #333333;
@fontLight: #999999;
@fontDisabled: #DDDDDD;
.month-calendar {
/deep/ .ant-fullcalendar-calendar-body {
padding-left: 0;
padding-right: 0;
}
/deep/ table {
border-collapse: collapse;
thead {
font-size: @font-lg;
color: @fontLight;
font-weight: 400;
th {
padding-right: 12px;
padding-bottom: 13px;
}
}
tbody {
border-top: 1px solid @tableBorderColor;
border-left: 1px solid @tableBorderColor;
}
td {
border-bottom: 1px solid @tableBorderColor;
border-right: 1px solid @tableBorderColor;
vertical-align: top;
&:empty {
// 最后一行 不渲染 (空元素 隐藏边框)
border: unset;
}
}
}
.fullcalendar-date-wrap {
height: 100%;
}
.fullcalendar-date {
height: 100%;
&:hover {
background: @primaryLight;
// cursor: pointer;
}
.fullcalendar-value {
padding: 0.08rem;
text-align: right;
& > span {
width: 0.32rem;
height: 0.32rem;
line-height: 0.32rem;
text-align: center;
display: inline-block;
font-size: @font-lg;
font-weight: bold;
color: @fontDisabled;
&.is-same-month {
color: @fontColor;
}
&.is-same-day {
background-color: @primaryColor;
color: @white;
border-radius: 50%;
}
}
}
.fullcalendar-content {
min-height: calc(@font-df * 1.6 * 4 + 0.2rem); // 4行 + padding-bottom
padding: 0 0.2rem 0.4rem;
display: flex;
justify-content: center;
align-items: center;
}
}
.fullcalendar-header {
padding: 11px 16px 11px 0;
display: flex;
justify-content: flex-end;
}
}