date-table.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <template>
  2. <table
  3. :class="['tui-date-table', !isPC && 'tui-date-table-h5']"
  4. cellspacing="0"
  5. cellpadding="0"
  6. role="grid"
  7. >
  8. <tbody class="tui-date-table-body">
  9. <tr class="tui-date-table-body-weeks">
  10. <th
  11. v-for="item in WEEKS"
  12. :key="item"
  13. class="tui-date-table-body-weeks-item"
  14. :aria-label="item + ''"
  15. scope="col"
  16. >
  17. {{ TUITranslateService.t(`time.${item}`) }}
  18. </th>
  19. </tr>
  20. <tr
  21. v-for="(row, rowKey) in rows"
  22. :key="rowKey"
  23. class="tui-date-table-body-days"
  24. >
  25. <td
  26. v-for="(col, colKey) in row"
  27. :key="colKey"
  28. :class="['tui-date-table-body-days-item', col.type]"
  29. >
  30. <div
  31. :class="[
  32. 'tui-date-table-body-days-item-cell',
  33. col.isSelected && 'selected',
  34. col.isSelectedStart && 'selected-start',
  35. col.isSelectedEnd && 'selected-end',
  36. col.isInRange && 'range',
  37. ]"
  38. @click="handlePick(col)"
  39. >
  40. <span class="tui-date-table-body-days-item-cell-text">
  41. {{ col.text }}
  42. </span>
  43. </div>
  44. </td>
  45. </tr>
  46. </tbody>
  47. </table>
  48. </template>
  49. <script lang="ts" setup>
  50. import {
  51. computed,
  52. ref,
  53. getCurrentInstance,
  54. nextTick,
  55. watch,
  56. } from '../../../adapter-vue';
  57. import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
  58. import dayjs, { Dayjs } from 'dayjs';
  59. import 'dayjs/locale/zh-cn';
  60. import { DateCell, DateCellType } from './date-picker';
  61. import { isPC } from '../../../utils/env';
  62. const props = defineProps({
  63. type: {
  64. type: String,
  65. default: 'range', // "single"/"range"
  66. },
  67. currentPanelDate: {
  68. type: Dayjs,
  69. default: () => dayjs(),
  70. },
  71. // Unique attribute when type is single
  72. date: {
  73. type: Dayjs,
  74. default: null,
  75. },
  76. // Unique attribute when type is range
  77. startDate: {
  78. type: Dayjs,
  79. default: null,
  80. },
  81. endDate: {
  82. type: Dayjs,
  83. default: null,
  84. },
  85. });
  86. const emit = defineEmits(['pick']);
  87. // vue instance
  88. const instance = getCurrentInstance();
  89. const tableRows = ref<DateCell[][]>([[], [], [], [], [], []]);
  90. const currentPanelDateObject = ref<typeof Dayjs>(
  91. dayjs(props.currentPanelDate || null),
  92. );
  93. const dateObject = ref<typeof Dayjs>(dayjs(props.date || null));
  94. const startDateObject = ref<typeof Dayjs>(dayjs(props.startDate || null));
  95. const endDateObject = ref<typeof Dayjs>(dayjs(props.endDate || null));
  96. const WEEKS_CONSTANT = computed(() => {
  97. return dayjs.weekdaysShort();
  98. });
  99. const WEEKS = computed(() =>
  100. WEEKS_CONSTANT.value.map((w: string) => w.substring(1)),
  101. );
  102. const startDateOnTable = computed(() => {
  103. const startDayOfMonth = currentPanelDateObject.value?.startOf('month');
  104. return startDayOfMonth?.subtract(startDayOfMonth?.day() || 7, 'day');
  105. });
  106. // Table data
  107. const rows = computed(() => {
  108. const rows_ = tableRows.value;
  109. const cols = WEEKS.value.length;
  110. const startOfMonth = currentPanelDateObject.value?.startOf('month');
  111. const startOfMonthDay = startOfMonth?.day() || 7; // day of this month first day
  112. const dateCountOfMonth = startOfMonth?.daysInMonth(); // total days of this month
  113. let count = 1;
  114. for (let row = 0; row < tableRows.value.length; row++) {
  115. for (let col = 0; col < cols; col++) {
  116. const cellDate = startDateOnTable.value?.add(count, 'day');
  117. const text = cellDate?.date();
  118. // For type === "single", select the entered date
  119. // For type === "range", select the entered start and end dates
  120. const isSelected
  121. = props.type === 'single'
  122. && cellDate?.format('YYYY-MM-DD')
  123. === dateObject.value?.format('YYYY-MM-DD');
  124. const isSelectedStart
  125. = props.type === 'range'
  126. && cellDate?.format('YYYY-MM-DD')
  127. === startDateObject.value?.format('YYYY-MM-DD');
  128. const isSelectedEnd
  129. = props.type === 'range'
  130. && cellDate?.format('YYYY-MM-DD')
  131. === endDateObject.value?.format('YYYY-MM-DD');
  132. // For type === "range", check if it is within the selected range.
  133. const isInRange
  134. = cellDate?.isSameOrBefore(endDateObject.value, 'day')
  135. && cellDate?.isSameOrAfter(startDateObject.value, 'day');
  136. let type: DateCellType = 'normal';
  137. if (count < startOfMonthDay) {
  138. // Prev month's date
  139. type = 'prev-month';
  140. } else if (count - startOfMonthDay >= dateCountOfMonth) {
  141. // Next month's date
  142. type = 'next-month';
  143. }
  144. rows_[row][col] = {
  145. type,
  146. date: cellDate,
  147. text,
  148. isSelected: isSelected || isSelectedStart || isSelectedEnd,
  149. isSelectedStart,
  150. isSelectedEnd,
  151. isInRange,
  152. };
  153. count++;
  154. }
  155. }
  156. return rows_;
  157. });
  158. const handlePick = (cell: DateCell) => {
  159. if (cell?.type !== 'normal') {
  160. return;
  161. }
  162. emit('pick', cell);
  163. };
  164. watch(
  165. () => [props.currentPanelDate, props.date, props.startDate, props.endDate],
  166. () => {
  167. currentPanelDateObject.value = dayjs(props.currentPanelDate || null);
  168. dateObject.value = dayjs(props.date || null);
  169. startDateObject.value = dayjs(props.startDate || null);
  170. endDateObject.value = dayjs(props.endDate || null);
  171. nextTick(() => {
  172. instance?.proxy?.$forceUpdate();
  173. });
  174. },
  175. {
  176. deep: true,
  177. immediate: true,
  178. },
  179. );
  180. </script>
  181. <style scoped lang="scss">
  182. /* stylelint-disable selector-class-pattern */
  183. .tui-date-table {
  184. border-spacing: 0;
  185. -webkit-border-horizontal-spacing: 0;
  186. -webkit-border-vertical-spacing: 0;
  187. font-size: 12px;
  188. user-select: none;
  189. table-layout: fixed;
  190. width: 100%;
  191. box-sizing: border-box;
  192. &::after,
  193. &::before {
  194. box-sizing: border-box;
  195. }
  196. &-body {
  197. width: 100%;
  198. background-color: #fff;
  199. &-weeks,
  200. &-days {
  201. box-sizing: border-box;
  202. min-width: 0;
  203. display: flex;
  204. flex-direction: row;
  205. justify-content: space-around;
  206. overflow: hidden;
  207. }
  208. &-weeks {
  209. width: 100%;
  210. &-item {
  211. color: #666;
  212. font-size: 12px;
  213. font-weight: 400px;
  214. }
  215. }
  216. &-days {
  217. color: #000;
  218. &-item {
  219. &-cell {
  220. text-align: center;
  221. padding: 2px;
  222. margin: 2px 0;
  223. &-text {
  224. display: inline-flex;
  225. justify-content: center;
  226. align-items: center;
  227. width: 24px;
  228. height: 24px;
  229. border-radius: 50%;
  230. user-select: none;
  231. cursor: pointer;
  232. box-sizing: border-box;
  233. }
  234. }
  235. .selected {
  236. border-radius: 12px;
  237. .tui-date-table-body-days-item-cell-text {
  238. box-sizing: border-box;
  239. color: #007aff;
  240. border: 1px solid #007aff;
  241. background-color: #fff;
  242. }
  243. }
  244. .range {
  245. background-color: #007aff33;
  246. }
  247. .selected-start {
  248. border-radius: 12px 0 0 12px;
  249. }
  250. .selected-end {
  251. border-radius: 0 12px 12px 0;
  252. }
  253. .selected-start.selected-end {
  254. border-radius: 12px;
  255. }
  256. }
  257. .prev-month,
  258. .next-month {
  259. color: #666;
  260. background-color: #fff;
  261. .range {
  262. color: #666;
  263. background-color: #fff;
  264. }
  265. .selected {
  266. .tui-date-table-body-days-item-cell-text {
  267. box-sizing: border-box;
  268. color: #666;
  269. border: none;
  270. }
  271. }
  272. }
  273. }
  274. }
  275. }
  276. .tui-date-table-h5 {
  277. /* stylelint-disable-next-line no-descending-specificity */
  278. .tui-date-table-body-days-item-cell-text {
  279. cursor: none !important;
  280. }
  281. }
  282. td,
  283. ._td,
  284. .tui-date-table-body-days-item {
  285. flex: 1;
  286. }
  287. </style>