mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
289 lines
6.9 KiB
JavaScript
289 lines
6.9 KiB
JavaScript
import { ref, computed, watch, nextTick } from 'vue';
|
|
|
|
// Utils
|
|
import { createNamespace, addUnit } from '../../utils';
|
|
import { unitToPx } from '../../utils/format/unit';
|
|
import { setScrollTop } from '../../utils/dom/scroll';
|
|
import { getMonthEndDay } from '../../datetime-picker/utils';
|
|
import {
|
|
t,
|
|
bem,
|
|
compareDay,
|
|
getPrevDay,
|
|
getNextDay,
|
|
formatMonthTitle,
|
|
} from '../utils';
|
|
|
|
// Composition
|
|
import { useToggle } from '@vant/use';
|
|
import { useRefs } from '../../composition/use-refs';
|
|
import { useExpose } from '../../composition/use-expose';
|
|
import { useRect, useHeight } from '../../composition/use-rect';
|
|
|
|
// Components
|
|
import Day from './Day';
|
|
|
|
const [createComponent] = createNamespace('calendar-month');
|
|
|
|
export default createComponent({
|
|
props: {
|
|
date: Date,
|
|
type: String,
|
|
color: String,
|
|
minDate: Date,
|
|
maxDate: Date,
|
|
showMark: Boolean,
|
|
rowHeight: [Number, String],
|
|
formatter: Function,
|
|
lazyRender: Boolean,
|
|
currentDate: [Date, Array],
|
|
allowSameDay: Boolean,
|
|
showSubtitle: Boolean,
|
|
realRowHeight: Number,
|
|
showMonthTitle: Boolean,
|
|
firstDayOfWeek: Number,
|
|
},
|
|
|
|
emits: ['click', 'update-height'],
|
|
|
|
setup(props, { emit }) {
|
|
const [visible, setVisible] = useToggle();
|
|
const [dayRefs, setDayRefs] = useRefs();
|
|
const daysRef = ref();
|
|
const monthRef = ref();
|
|
const height = useHeight(monthRef);
|
|
|
|
const title = computed(() => formatMonthTitle(props.date));
|
|
|
|
const rowHeight = computed(() => addUnit(props.rowHeight));
|
|
|
|
const offset = computed(() => {
|
|
const realDay = props.date.getDay();
|
|
|
|
if (props.firstDayOfWeek) {
|
|
return (realDay + 7 - props.firstDayOfWeek) % 7;
|
|
}
|
|
return realDay;
|
|
});
|
|
|
|
const totalDay = computed(() =>
|
|
getMonthEndDay(props.date.getFullYear(), props.date.getMonth() + 1)
|
|
);
|
|
|
|
const shouldRender = computed(() => visible.value || !props.lazyRender);
|
|
|
|
const monthStyle = computed(() => {
|
|
if (!shouldRender.value) {
|
|
const rowCount = Math.ceil((totalDay.value + offset.value) / 7);
|
|
const padding = rowCount * unitToPx(props.realRowHeight);
|
|
return {
|
|
paddingBottom: `${padding}px`,
|
|
};
|
|
}
|
|
});
|
|
|
|
const getDate = () => props.data;
|
|
|
|
const getTitle = () => title.value;
|
|
|
|
const scrollIntoView = (body) => {
|
|
const el = props.showSubtitle ? daysRef.value : monthRef.value;
|
|
|
|
const scrollTop =
|
|
el.getBoundingClientRect().top -
|
|
body.getBoundingClientRect().top +
|
|
body.scrollTop;
|
|
|
|
setScrollTop(body, scrollTop);
|
|
};
|
|
|
|
const getMultipleDayType = (day) => {
|
|
const isSelected = (date) =>
|
|
props.currentDate.some((item) => compareDay(item, date) === 0);
|
|
|
|
if (isSelected(day)) {
|
|
const prevDay = getPrevDay(day);
|
|
const nextDay = getNextDay(day);
|
|
const prevSelected = isSelected(prevDay);
|
|
const nextSelected = isSelected(nextDay);
|
|
|
|
if (prevSelected && nextSelected) {
|
|
return 'multiple-middle';
|
|
}
|
|
if (prevSelected) {
|
|
return 'end';
|
|
}
|
|
if (nextSelected) {
|
|
return 'start';
|
|
}
|
|
return 'multiple-selected';
|
|
}
|
|
|
|
return '';
|
|
};
|
|
|
|
const getRangeDayType = (day) => {
|
|
const [startDay, endDay] = props.currentDate;
|
|
|
|
if (!startDay) {
|
|
return '';
|
|
}
|
|
|
|
const compareToStart = compareDay(day, startDay);
|
|
|
|
if (!endDay) {
|
|
return compareToStart === 0 ? 'start' : '';
|
|
}
|
|
|
|
const compareToEnd = compareDay(day, endDay);
|
|
|
|
if (props.allowSameDay && compareToStart === 0 && compareToEnd === 0) {
|
|
return 'start-end';
|
|
}
|
|
if (compareToStart === 0) {
|
|
return 'start';
|
|
}
|
|
if (compareToEnd === 0) {
|
|
return 'end';
|
|
}
|
|
if (compareToStart > 0 && compareToEnd < 0) {
|
|
return 'middle';
|
|
}
|
|
};
|
|
|
|
const getDayType = (day) => {
|
|
const { type, minDate, maxDate, currentDate } = props;
|
|
|
|
if (compareDay(day, minDate) < 0 || compareDay(day, maxDate) > 0) {
|
|
return 'disabled';
|
|
}
|
|
|
|
if (currentDate === null) {
|
|
return;
|
|
}
|
|
|
|
if (Array.isArray(currentDate)) {
|
|
if (type === 'multiple') {
|
|
return getMultipleDayType(day);
|
|
}
|
|
if (type === 'range') {
|
|
return getRangeDayType(day);
|
|
}
|
|
} else if (type === 'single') {
|
|
return compareDay(day, currentDate) === 0 ? 'selected' : '';
|
|
}
|
|
};
|
|
|
|
const getBottomInfo = (dayType) => {
|
|
if (props.type === 'range') {
|
|
if (dayType === 'start' || dayType === 'end') {
|
|
return t(dayType);
|
|
}
|
|
if (dayType === 'start-end') {
|
|
return t('startEnd');
|
|
}
|
|
}
|
|
};
|
|
|
|
const renderTitle = () => {
|
|
if (props.showMonthTitle) {
|
|
return <div class={bem('month-title')}>{title.value}</div>;
|
|
}
|
|
};
|
|
|
|
const renderMark = () => {
|
|
if (props.showMark) {
|
|
return <div class={bem('month-mark')}>{props.date.getMonth() + 1}</div>;
|
|
}
|
|
};
|
|
|
|
const days = computed(() => {
|
|
const days = [];
|
|
const year = props.date.getFullYear();
|
|
const month = props.date.getMonth();
|
|
|
|
for (let day = 1; day <= totalDay.value; day++) {
|
|
const date = new Date(year, month, day);
|
|
const type = getDayType(date);
|
|
|
|
let config = {
|
|
date,
|
|
type,
|
|
text: day,
|
|
bottomInfo: getBottomInfo(type),
|
|
};
|
|
|
|
if (props.formatter) {
|
|
config = props.formatter(config);
|
|
}
|
|
|
|
days.push(config);
|
|
}
|
|
|
|
return days;
|
|
});
|
|
|
|
const renderDay = (item, index) => (
|
|
<Day
|
|
ref={setDayRefs(index)}
|
|
item={item}
|
|
index={index}
|
|
color={props.color}
|
|
offset={offset.value}
|
|
rowHeight={rowHeight.value}
|
|
onClick={(item) => {
|
|
emit('click', item);
|
|
}}
|
|
/>
|
|
);
|
|
|
|
const renderDays = () => {
|
|
if (shouldRender.value) {
|
|
return (
|
|
<div ref={daysRef} role="grid" class={bem('days')}>
|
|
{renderMark()}
|
|
{days.value.map(renderDay)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <div ref={daysRef} />;
|
|
};
|
|
|
|
watch(
|
|
() => props.realRowHeight,
|
|
() => {
|
|
nextTick(() => {
|
|
height.value = useRect(monthRef).height;
|
|
});
|
|
}
|
|
);
|
|
|
|
watch(shouldRender, (value) => {
|
|
if (value) {
|
|
nextTick(() => {
|
|
if (dayRefs.value[0] && !props.realRowHeight) {
|
|
const { height } = dayRefs.value[0].$el.getBoundingClientRect();
|
|
emit('update-height', height);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
useExpose({
|
|
getDate,
|
|
getTitle,
|
|
getHeight: () => height.value,
|
|
setVisible,
|
|
scrollIntoView,
|
|
});
|
|
|
|
return () => (
|
|
<div class={bem('month')} ref={monthRef} style={monthStyle.value}>
|
|
{renderTitle()}
|
|
{renderDays()}
|
|
</div>
|
|
);
|
|
},
|
|
});
|