713 lines
22 KiB
Vue
713 lines
22 KiB
Vue
<template>
|
|
<div class="calendar" @click="togglePopup">
|
|
<at-input class="input" :readonly="true" :value="inputValue">
|
|
<template #prepend>
|
|
<i class="icon icon-chevron-left previous" @click.stop.prevent="selectPrevious"></i>
|
|
</template>
|
|
|
|
<template #append>
|
|
<i class="icon icon-chevron-right next" @click.stop.prevent="selectNext"></i>
|
|
</template>
|
|
</at-input>
|
|
|
|
<span class="calendar-icon icon icon-calendar" />
|
|
|
|
<transition name="slide-up">
|
|
<div
|
|
v-show="showPopup"
|
|
:class="{
|
|
'datepicker-wrapper': true,
|
|
'datepicker-wrapper--range': datePickerRange,
|
|
'at-select__dropdown at-select__dropdown--bottom': true,
|
|
}"
|
|
@click.stop
|
|
>
|
|
<div>
|
|
<at-tabs ref="tabs" v-model="tab" @on-change="onTabChange">
|
|
<at-tab-pane v-if="day" :label="$t('control.day')" name="day"></at-tab-pane>
|
|
<at-tab-pane v-if="week" :label="$t('control.week')" name="week"></at-tab-pane>
|
|
<at-tab-pane v-if="month" :label="$t('control.month')" name="month"></at-tab-pane>
|
|
<at-tab-pane v-if="range" :label="$t('control.range')" name="range"></at-tab-pane>
|
|
</at-tabs>
|
|
</div>
|
|
|
|
<date-picker
|
|
:key="$i18n.locale"
|
|
class="datepicker"
|
|
:append-to-body="false"
|
|
:clearable="false"
|
|
:editable="false"
|
|
:inline="true"
|
|
:lang="datePickerLang"
|
|
:type="datePickerType"
|
|
:range="datePickerRange"
|
|
:value="datePickerValue"
|
|
@change="onDateChange"
|
|
>
|
|
<template #footer>
|
|
<div v-if="day" class="datepicker__footer">
|
|
<button class="mx-btn mx-btn-text" size="small" @click="setToday">
|
|
{{ $t('control.today') }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</date-picker>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import moment from 'moment';
|
|
import { getDateToday, getEndDay, getStartDay } from '@/utils/time';
|
|
|
|
export default {
|
|
name: 'Calendar',
|
|
props: {
|
|
day: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
week: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
month: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
range: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
initialTab: {
|
|
type: String,
|
|
default: 'day',
|
|
},
|
|
sessionStorageKey: {
|
|
type: String,
|
|
default: 'amazingcat.session.storage',
|
|
},
|
|
},
|
|
data() {
|
|
const { query } = this.$route;
|
|
const today = this.getDateToday();
|
|
|
|
const data = {
|
|
showPopup: false,
|
|
lang: null,
|
|
datePickerLang: {},
|
|
};
|
|
|
|
const sessionData = {
|
|
type: sessionStorage.getItem(this.sessionStorageKey + '.type'),
|
|
start: sessionStorage.getItem(this.sessionStorageKey + '.start'),
|
|
end: sessionStorage.getItem(this.sessionStorageKey + '.end'),
|
|
};
|
|
|
|
if (typeof query['type'] === 'string' && this.validateTab(query['type'])) {
|
|
data.tab = query['type'];
|
|
} else if (typeof sessionData.type === 'string' && this.validateTab(sessionData.type)) {
|
|
data.tab = sessionData.type;
|
|
} else {
|
|
data.tab = this.initialTab;
|
|
}
|
|
|
|
if (typeof query['start'] === 'string' && this.validateDate(query['start'])) {
|
|
data.start = query['start'];
|
|
} else if (typeof sessionData.start === 'string' && this.validateDate(sessionData.start)) {
|
|
data.start = sessionData.start;
|
|
} else {
|
|
data.start = today;
|
|
}
|
|
|
|
if (typeof query['end'] === 'string' && this.validateDate(query['end'])) {
|
|
data.end = query['end'];
|
|
} else if (typeof sessionData.end === 'string' && this.validateDate(sessionData.end)) {
|
|
data.end = sessionData.end;
|
|
} else {
|
|
data.end = today;
|
|
}
|
|
|
|
switch (data.tab) {
|
|
case 'day':
|
|
case 'date':
|
|
data.end = data.start;
|
|
break;
|
|
|
|
case 'week': {
|
|
const date = moment(data.start, 'YYYY-MM-DD', true);
|
|
if (date.isValid()) {
|
|
data.start = date.startOf('isoWeek').format('YYYY-MM-DD');
|
|
data.end = date.endOf('isoWeek').format('YYYY-MM-DD');
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'month': {
|
|
const date = moment(data.start, 'YYYY-MM-DD', true);
|
|
if (date.isValid()) {
|
|
data.start = date.startOf('month').format('YYYY-MM-DD');
|
|
data.end = date.endOf('month').format('YYYY-MM-DD');
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
},
|
|
mounted() {
|
|
window.addEventListener('click', this.hidePopup);
|
|
this.saveData(this.tab, this.start, this.end);
|
|
this.emitChangeEvent();
|
|
this.$nextTick(async () => {
|
|
try {
|
|
const locale = await import(`vue2-datepicker/locale/${this.$i18n.locale}`);
|
|
|
|
this.datePickerLang = {
|
|
...locale,
|
|
formatLocale: {
|
|
...locale.formatLocale,
|
|
firstDayOfWeek: 1,
|
|
},
|
|
monthFormat: 'MMMM',
|
|
};
|
|
} catch {
|
|
this.datePickerLang = {
|
|
formatLocale: { firstDayOfWeek: 1 },
|
|
monthFormat: 'MMMM',
|
|
};
|
|
}
|
|
});
|
|
},
|
|
beforeDestroy() {
|
|
window.removeEventListener('click', this.hidePopup);
|
|
},
|
|
computed: {
|
|
inputValue() {
|
|
switch (this.tab) {
|
|
case 'date':
|
|
default:
|
|
return moment(this.start, 'YYYY-MM-DD').locale(this.$i18n.locale).format('MMM DD, YYYY');
|
|
|
|
case 'week': {
|
|
const start = moment(this.start, 'YYYY-MM-DD').locale(this.$i18n.locale).startOf('isoWeek');
|
|
const end = moment(this.end, 'YYYY-MM-DD').locale(this.$i18n.locale).endOf('isoWeek');
|
|
if (start.month() === end.month()) {
|
|
return start.format('MMM DD-') + end.format('DD, YYYY');
|
|
}
|
|
|
|
return start.format('MMM DD — ') + end.format('MMM DD, YYYY');
|
|
}
|
|
|
|
case 'month':
|
|
return moment(this.start, 'YYYY-MM-DD')
|
|
.locale(this.$i18n.locale)
|
|
.startOf('month')
|
|
.format('MMM, YYYY');
|
|
|
|
case 'range': {
|
|
const start = moment(this.start, 'YYYY-MM-DD').locale(this.$i18n.locale);
|
|
const end = moment(this.end, 'YYYY-MM-DD').locale(this.$i18n.locale);
|
|
|
|
if (start.year() === end.year()) {
|
|
return start.format('MMM DD, — ') + end.format('MMM DD, YYYY');
|
|
} else {
|
|
return start.format('MMM DD, YYYY — ') + end.format('MMM DD, YYYY');
|
|
}
|
|
}
|
|
}
|
|
},
|
|
datePickerType() {
|
|
switch (this.tab) {
|
|
case 'day':
|
|
case 'range':
|
|
default:
|
|
return 'date';
|
|
|
|
case 'week':
|
|
return 'week';
|
|
|
|
case 'month':
|
|
return 'month';
|
|
}
|
|
},
|
|
datePickerRange() {
|
|
return this.tab === 'range';
|
|
},
|
|
datePickerValue() {
|
|
if (this.tab === 'range') {
|
|
return [moment(this.start, 'YYYY-MM-DD').toDate(), moment(this.end, 'YYYY-MM-DD').toDate()];
|
|
}
|
|
|
|
return moment(this.start, 'YYYY-MM-DD').toDate();
|
|
},
|
|
},
|
|
methods: {
|
|
getDateToday,
|
|
validateTab(tab) {
|
|
return ['day', 'date', 'week', 'month', 'range'].indexOf(tab) !== -1;
|
|
},
|
|
validateDate(date) {
|
|
return moment(date, 'YYYY-MM-DD', true).isValid();
|
|
},
|
|
togglePopup() {
|
|
this.showPopup = !this.showPopup;
|
|
},
|
|
hidePopup() {
|
|
if (this.$el.contains(event.target)) {
|
|
return;
|
|
}
|
|
|
|
this.showPopup = false;
|
|
},
|
|
selectPrevious() {
|
|
let start, end;
|
|
switch (this.tab) {
|
|
case 'day':
|
|
default: {
|
|
const date = moment(this.start).subtract(1, 'day').format('YYYY-MM-DD');
|
|
start = date;
|
|
end = date;
|
|
break;
|
|
}
|
|
|
|
case 'week': {
|
|
const date = moment(this.start).subtract(1, 'week');
|
|
start = date.startOf('isoWeek').format('YYYY-MM-DD');
|
|
end = date.endOf('isoWeek').format('YYYY-MM-DD');
|
|
break;
|
|
}
|
|
|
|
case 'month': {
|
|
const date = moment(this.start).subtract(1, 'month');
|
|
start = date.startOf('month').format('YYYY-MM-DD');
|
|
end = date.endOf('month').format('YYYY-MM-DD');
|
|
break;
|
|
}
|
|
|
|
case 'range': {
|
|
const diff = moment(this.end).diff(this.start, 'days') + 1;
|
|
start = moment(this.start).subtract(diff, 'days').format('YYYY-MM-DD');
|
|
end = moment(this.end).subtract(diff, 'days').format('YYYY-MM-DD');
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.saveData(this.tab, start, end);
|
|
this.emitChangeEvent();
|
|
},
|
|
selectNext() {
|
|
let start, end;
|
|
switch (this.tab) {
|
|
case 'day':
|
|
default: {
|
|
const date = moment(this.start).add(1, 'day').format('YYYY-MM-DD');
|
|
start = date;
|
|
end = date;
|
|
break;
|
|
}
|
|
|
|
case 'week': {
|
|
const date = moment(this.start).add(1, 'week');
|
|
start = date.startOf('isoWeek').format('YYYY-MM-DD');
|
|
end = date.endOf('isoWeek').format('YYYY-MM-DD');
|
|
break;
|
|
}
|
|
|
|
case 'month': {
|
|
const date = moment(this.start).add(1, 'month');
|
|
start = date.startOf('month').format('YYYY-MM-DD');
|
|
end = date.endOf('month').format('YYYY-MM-DD');
|
|
break;
|
|
}
|
|
|
|
case 'range': {
|
|
const diff = moment(this.end).diff(this.start, 'days') + 1;
|
|
start = moment(this.start).add(diff, 'days').format('YYYY-MM-DD');
|
|
end = moment(this.end).add(diff, 'days').format('YYYY-MM-DD');
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.saveData(this.tab, start, end);
|
|
this.emitChangeEvent();
|
|
},
|
|
onTabChange({ index, name }) {
|
|
this.tab = 'range';
|
|
this.$nextTick(() => {
|
|
this.tab = name;
|
|
});
|
|
},
|
|
setDate(value) {
|
|
let start, end;
|
|
|
|
switch (this.tab) {
|
|
case 'day':
|
|
default: {
|
|
const date = moment(value).format('YYYY-MM-DD');
|
|
start = date;
|
|
end = date;
|
|
break;
|
|
}
|
|
|
|
case 'week':
|
|
start = moment(value).startOf('isoWeek').format('YYYY-MM-DD');
|
|
end = moment(value).endOf('isoWeek').format('YYYY-MM-DD');
|
|
break;
|
|
|
|
case 'month':
|
|
start = moment(value).startOf('month').format('YYYY-MM-DD');
|
|
end = moment(value).endOf('month').format('YYYY-MM-DD');
|
|
break;
|
|
|
|
case 'range':
|
|
start = moment(value[0]).format('YYYY-MM-DD');
|
|
end = moment(value[1]).format('YYYY-MM-DD');
|
|
break;
|
|
}
|
|
|
|
this.saveData(this.tab, start, end);
|
|
this.emitChangeEvent();
|
|
},
|
|
saveData(type, start, end) {
|
|
this.tab = type;
|
|
this.start = start;
|
|
this.end = end;
|
|
|
|
sessionStorage.setItem(this.sessionStorageKey + '.type', type);
|
|
sessionStorage.setItem(this.sessionStorageKey + '.start', start);
|
|
sessionStorage.setItem(this.sessionStorageKey + '.end', end);
|
|
|
|
const { query } = this.$route;
|
|
|
|
const searchParams = new URLSearchParams({ type, start, end }).toString();
|
|
|
|
// HACK: The native history is used because changing
|
|
// params via Vue Router closes all pending requests
|
|
history.pushState(null, null, `?${searchParams}`);
|
|
},
|
|
emitChangeEvent() {
|
|
this.$emit('change', {
|
|
type: sessionStorage.getItem(this.sessionStorageKey + '.type'),
|
|
start: sessionStorage.getItem(this.sessionStorageKey + '.start'),
|
|
end: sessionStorage.getItem(this.sessionStorageKey + '.end'),
|
|
});
|
|
},
|
|
onDateChange(value) {
|
|
this.showPopup = false;
|
|
|
|
this.setDate(value);
|
|
},
|
|
setToday() {
|
|
this.tab = 'day';
|
|
this.$refs.tabs.setNavByIndex(0);
|
|
this.setDate(new Date());
|
|
this.hidePopup();
|
|
},
|
|
},
|
|
watch: {
|
|
$route(to, from) {
|
|
const { query } = to;
|
|
|
|
if (typeof query['type'] === 'string' && this.validateTab(query['type'])) {
|
|
sessionStorage.setItem(this.sessionStorageKey + '.type', (this.tab = query['type']));
|
|
}
|
|
|
|
if (typeof query['start'] === 'string' && this.validateDate(query['start'])) {
|
|
sessionStorage.setItem(this.sessionStorageKey + '.start', (this.start = query['start']));
|
|
}
|
|
|
|
if (typeof query['end'] === 'string' && this.validateDate(query['end'])) {
|
|
sessionStorage.setItem(this.sessionStorageKey + '.end', (this.end = query['end']));
|
|
}
|
|
|
|
this.emitChangeEvent();
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.calendar {
|
|
position: relative;
|
|
}
|
|
|
|
.calendar-icon {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 2em;
|
|
color: #2e2ef9;
|
|
line-height: 40px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.input {
|
|
background: #ffffff;
|
|
width: 330px;
|
|
height: 40px;
|
|
border: 1px solid #eeeef5;
|
|
border-radius: 5px;
|
|
|
|
cursor: pointer;
|
|
|
|
&::v-deep {
|
|
.at-input-group__prepend,
|
|
.at-input-group__append,
|
|
.at-input__original {
|
|
border: 0;
|
|
background: transparent;
|
|
}
|
|
|
|
.at-input-group__prepend,
|
|
.at-input-group__append {
|
|
padding: 0;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.at-input__original {
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
|
|
.fa-calendar {
|
|
color: #2e2ef9;
|
|
}
|
|
|
|
.previous,
|
|
.next {
|
|
color: #2e2ef9;
|
|
|
|
display: flex;
|
|
flex-flow: row nowrap;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
width: 28px;
|
|
height: 100%;
|
|
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
}
|
|
|
|
.datepicker-wrapper {
|
|
position: absolute;
|
|
width: 320px;
|
|
max-height: unset;
|
|
|
|
&--range {
|
|
width: 640px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 750px) {
|
|
.datepicker-wrapper {
|
|
&--range {
|
|
width: 320px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.datepicker__footer {
|
|
text-align: left;
|
|
}
|
|
|
|
.calendar::v-deep {
|
|
.at-tabs__header {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.at-tabs-nav {
|
|
display: flex;
|
|
flex-flow: row nowrap;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.at-tabs-nav__item {
|
|
color: #c4c4cf;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
margin-right: 0;
|
|
padding: 0;
|
|
|
|
flex: 1;
|
|
text-align: center;
|
|
|
|
&--active {
|
|
color: #2e2ef9;
|
|
}
|
|
|
|
&::after {
|
|
background-color: #2e2ef9;
|
|
}
|
|
}
|
|
|
|
.mx-datepicker {
|
|
max-height: unset;
|
|
}
|
|
|
|
.mx-datepicker-main,
|
|
.mx-datepicker-inline {
|
|
border: none;
|
|
}
|
|
|
|
.mx-datepicker-header {
|
|
padding: 0;
|
|
border-bottom: none;
|
|
}
|
|
|
|
.mx-calendar {
|
|
width: unset;
|
|
}
|
|
|
|
.mx-calendar-content {
|
|
width: unset;
|
|
}
|
|
|
|
.mx-calendar-header {
|
|
& > .mx-btn-text {
|
|
padding: 0;
|
|
width: 34px;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
.mx-calendar-header-label .mx-btn {
|
|
color: #1a051d;
|
|
}
|
|
|
|
.mx-table thead {
|
|
color: #b1b1be;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.mx-week-number-header,
|
|
.mx-week-number {
|
|
display: none;
|
|
}
|
|
|
|
.mx-table-date td {
|
|
font-size: 13px;
|
|
}
|
|
|
|
.mx-table-date .cell:last-child {
|
|
color: #ff5569;
|
|
}
|
|
|
|
.mx-table {
|
|
.cell.not-current-month {
|
|
color: #e7ecf2;
|
|
}
|
|
|
|
.cell.active {
|
|
background: transparent;
|
|
|
|
& > div {
|
|
display: inline-block;
|
|
background: #2e2ef9;
|
|
color: #ffffff;
|
|
border-radius: 7px;
|
|
width: 25px;
|
|
height: 25px;
|
|
line-height: 25px;
|
|
}
|
|
}
|
|
|
|
.cell.in-range {
|
|
background: transparent;
|
|
|
|
& > div {
|
|
display: inline-block;
|
|
background: #eeeef5;
|
|
color: inherit;
|
|
border-top-left-radius: 5px;
|
|
border-bottom-left-radius: 5px;
|
|
width: 100%;
|
|
height: 22px;
|
|
line-height: 22px;
|
|
}
|
|
|
|
&:last-child > div {
|
|
border-top-right-radius: 5px;
|
|
border-bottom-right-radius: 5px;
|
|
}
|
|
}
|
|
|
|
.cell.in-range + .cell.in-range > div {
|
|
border-top-left-radius: 0;
|
|
border-bottom-left-radius: 0;
|
|
}
|
|
|
|
.mx-active-week {
|
|
background: transparent;
|
|
|
|
.cell > div {
|
|
border-radius: 0;
|
|
}
|
|
|
|
.cell:nth-child(3) > div {
|
|
border-top-left-radius: 5px;
|
|
border-bottom-left-radius: 5px;
|
|
}
|
|
|
|
.cell:nth-child(7) > div {
|
|
border-top-right-radius: 5px;
|
|
border-bottom-right-radius: 5px;
|
|
}
|
|
|
|
.cell + .cell:not(:last-child) > div {
|
|
display: inline-block;
|
|
background: #eeeef5;
|
|
color: #151941;
|
|
width: 100%;
|
|
height: 22px;
|
|
line-height: 22px;
|
|
}
|
|
|
|
.mx-week-number + .cell > div,
|
|
.cell:last-child > div {
|
|
display: inline-block;
|
|
background: #2e2ef9;
|
|
color: #ffffff;
|
|
border-radius: 7px;
|
|
width: 25px;
|
|
height: 25px;
|
|
line-height: 25px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.mx-table-month {
|
|
color: #000000;
|
|
|
|
.cell {
|
|
height: 50px;
|
|
}
|
|
|
|
.cell.active > div {
|
|
border-radius: 5px;
|
|
width: 54px;
|
|
height: 30px;
|
|
}
|
|
}
|
|
|
|
.mx-table-year {
|
|
color: #000000;
|
|
|
|
.cell.active > div {
|
|
width: 54px;
|
|
}
|
|
}
|
|
|
|
.mx-btn:hover {
|
|
color: #2e2ef9;
|
|
}
|
|
|
|
.mx-table .cell.today {
|
|
color: #2a90e9;
|
|
}
|
|
}
|
|
</style>
|