322 lines
7.6 KiB
Vue
322 lines
7.6 KiB
Vue
<template>
|
|
<table
|
|
:class="['tui-date-table', !isPC && 'tui-date-table-h5']"
|
|
cellspacing="0"
|
|
cellpadding="0"
|
|
role="grid"
|
|
>
|
|
<tbody class="tui-date-table-body">
|
|
<tr class="tui-date-table-body-weeks">
|
|
<th
|
|
v-for="item in WEEKS"
|
|
:key="item"
|
|
class="tui-date-table-body-weeks-item"
|
|
:aria-label="item + ''"
|
|
scope="col"
|
|
>
|
|
{{ TUITranslateService.t(`time.${item}`) }}
|
|
</th>
|
|
</tr>
|
|
<tr
|
|
v-for="(row, rowKey) in rows"
|
|
:key="rowKey"
|
|
class="tui-date-table-body-days"
|
|
>
|
|
<td
|
|
v-for="(col, colKey) in row"
|
|
:key="colKey"
|
|
:class="['tui-date-table-body-days-item', col.type]"
|
|
>
|
|
<div
|
|
:class="[
|
|
'tui-date-table-body-days-item-cell',
|
|
col.isSelected && 'selected',
|
|
col.isSelectedStart && 'selected-start',
|
|
col.isSelectedEnd && 'selected-end',
|
|
col.isInRange && 'range',
|
|
]"
|
|
@click="handlePick(col)"
|
|
>
|
|
<span class="tui-date-table-body-days-item-cell-text">
|
|
{{ col.text }}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import {
|
|
computed,
|
|
ref,
|
|
getCurrentInstance,
|
|
nextTick,
|
|
watch,
|
|
} from '../../../adapter-vue';
|
|
import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
|
|
import dayjs, { Dayjs } from 'dayjs';
|
|
import 'dayjs/locale/zh-cn';
|
|
import { DateCell, DateCellType } from './date-picker';
|
|
import { isPC } from '../../../utils/env';
|
|
|
|
const props = defineProps({
|
|
type: {
|
|
type: String,
|
|
default: 'range', // "single"/"range"
|
|
},
|
|
currentPanelDate: {
|
|
type: Dayjs,
|
|
default: () => dayjs(),
|
|
},
|
|
// Unique attribute when type is single
|
|
date: {
|
|
type: Dayjs,
|
|
default: null,
|
|
},
|
|
// Unique attribute when type is range
|
|
startDate: {
|
|
type: Dayjs,
|
|
default: null,
|
|
},
|
|
endDate: {
|
|
type: Dayjs,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(['pick']);
|
|
// vue instance
|
|
const instance = getCurrentInstance();
|
|
|
|
const tableRows = ref<DateCell[][]>([[], [], [], [], [], []]);
|
|
const currentPanelDateObject = ref<typeof Dayjs>(
|
|
dayjs(props.currentPanelDate || null),
|
|
);
|
|
const dateObject = ref<typeof Dayjs>(dayjs(props.date || null));
|
|
const startDateObject = ref<typeof Dayjs>(dayjs(props.startDate || null));
|
|
const endDateObject = ref<typeof Dayjs>(dayjs(props.endDate || null));
|
|
|
|
const WEEKS_CONSTANT = computed(() => {
|
|
return dayjs.weekdaysShort();
|
|
});
|
|
|
|
const WEEKS = computed(() =>
|
|
WEEKS_CONSTANT.value.map((w: string) => w.substring(1)),
|
|
);
|
|
|
|
const startDateOnTable = computed(() => {
|
|
const startDayOfMonth = currentPanelDateObject.value?.startOf('month');
|
|
return startDayOfMonth?.subtract(startDayOfMonth?.day() || 7, 'day');
|
|
});
|
|
|
|
// Table data
|
|
const rows = computed(() => {
|
|
const rows_ = tableRows.value;
|
|
const cols = WEEKS.value.length;
|
|
|
|
const startOfMonth = currentPanelDateObject.value?.startOf('month');
|
|
const startOfMonthDay = startOfMonth?.day() || 7; // day of this month first day
|
|
const dateCountOfMonth = startOfMonth?.daysInMonth(); // total days of this month
|
|
|
|
let count = 1;
|
|
for (let row = 0; row < tableRows.value.length; row++) {
|
|
for (let col = 0; col < cols; col++) {
|
|
const cellDate = startDateOnTable.value?.add(count, 'day');
|
|
const text = cellDate?.date();
|
|
// For type === "single", select the entered date
|
|
// For type === "range", select the entered start and end dates
|
|
const isSelected
|
|
= props.type === 'single'
|
|
&& cellDate?.format('YYYY-MM-DD')
|
|
=== dateObject.value?.format('YYYY-MM-DD');
|
|
const isSelectedStart
|
|
= props.type === 'range'
|
|
&& cellDate?.format('YYYY-MM-DD')
|
|
=== startDateObject.value?.format('YYYY-MM-DD');
|
|
const isSelectedEnd
|
|
= props.type === 'range'
|
|
&& cellDate?.format('YYYY-MM-DD')
|
|
=== endDateObject.value?.format('YYYY-MM-DD');
|
|
// For type === "range", check if it is within the selected range.
|
|
const isInRange
|
|
= cellDate?.isSameOrBefore(endDateObject.value, 'day')
|
|
&& cellDate?.isSameOrAfter(startDateObject.value, 'day');
|
|
let type: DateCellType = 'normal';
|
|
if (count < startOfMonthDay) {
|
|
// Prev month's date
|
|
type = 'prev-month';
|
|
} else if (count - startOfMonthDay >= dateCountOfMonth) {
|
|
// Next month's date
|
|
type = 'next-month';
|
|
}
|
|
rows_[row][col] = {
|
|
type,
|
|
date: cellDate,
|
|
text,
|
|
isSelected: isSelected || isSelectedStart || isSelectedEnd,
|
|
isSelectedStart,
|
|
isSelectedEnd,
|
|
isInRange,
|
|
};
|
|
count++;
|
|
}
|
|
}
|
|
return rows_;
|
|
});
|
|
|
|
const handlePick = (cell: DateCell) => {
|
|
if (cell?.type !== 'normal') {
|
|
return;
|
|
}
|
|
emit('pick', cell);
|
|
};
|
|
|
|
watch(
|
|
() => [props.currentPanelDate, props.date, props.startDate, props.endDate],
|
|
() => {
|
|
currentPanelDateObject.value = dayjs(props.currentPanelDate || null);
|
|
dateObject.value = dayjs(props.date || null);
|
|
startDateObject.value = dayjs(props.startDate || null);
|
|
endDateObject.value = dayjs(props.endDate || null);
|
|
nextTick(() => {
|
|
instance?.proxy?.$forceUpdate();
|
|
});
|
|
},
|
|
{
|
|
deep: true,
|
|
immediate: true,
|
|
},
|
|
);
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
/* stylelint-disable selector-class-pattern */
|
|
.tui-date-table {
|
|
border-spacing: 0;
|
|
-webkit-border-horizontal-spacing: 0;
|
|
-webkit-border-vertical-spacing: 0;
|
|
font-size: 12px;
|
|
user-select: none;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
|
|
&::after,
|
|
&::before {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
&-body {
|
|
width: 100%;
|
|
background-color: #fff;
|
|
|
|
&-weeks,
|
|
&-days {
|
|
box-sizing: border-box;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
overflow: hidden;
|
|
}
|
|
|
|
&-weeks {
|
|
width: 100%;
|
|
|
|
&-item {
|
|
color: #666;
|
|
font-size: 12px;
|
|
font-weight: 400px;
|
|
}
|
|
}
|
|
|
|
&-days {
|
|
color: #000;
|
|
|
|
&-item {
|
|
&-cell {
|
|
text-align: center;
|
|
padding: 2px;
|
|
margin: 2px 0;
|
|
|
|
&-text {
|
|
display: inline-flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
user-select: none;
|
|
cursor: pointer;
|
|
box-sizing: border-box;
|
|
}
|
|
}
|
|
|
|
.selected {
|
|
border-radius: 12px;
|
|
|
|
.tui-date-table-body-days-item-cell-text {
|
|
box-sizing: border-box;
|
|
color: #007aff;
|
|
border: 1px solid #007aff;
|
|
background-color: #fff;
|
|
}
|
|
}
|
|
|
|
.range {
|
|
background-color: #007aff33;
|
|
}
|
|
|
|
.selected-start {
|
|
border-radius: 12px 0 0 12px;
|
|
}
|
|
|
|
.selected-end {
|
|
border-radius: 0 12px 12px 0;
|
|
}
|
|
|
|
.selected-start.selected-end {
|
|
border-radius: 12px;
|
|
}
|
|
}
|
|
|
|
.prev-month,
|
|
.next-month {
|
|
color: #666;
|
|
background-color: #fff;
|
|
|
|
.range {
|
|
color: #666;
|
|
background-color: #fff;
|
|
}
|
|
|
|
.selected {
|
|
.tui-date-table-body-days-item-cell-text {
|
|
box-sizing: border-box;
|
|
color: #666;
|
|
border: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
.tui-date-table-h5 {
|
|
|
|
/* stylelint-disable-next-line no-descending-specificity */
|
|
.tui-date-table-body-days-item-cell-text {
|
|
cursor: none !important;
|
|
}
|
|
}
|
|
|
|
td,
|
|
._td,
|
|
.tui-date-table-body-days-item {
|
|
flex: 1;
|
|
}
|
|
</style>
|