date-table.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. // type 为 single 时特有属性
  72. date: {
  73. type: Dayjs,
  74. default: null,
  75. },
  76. // type 为 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 实例
  88. const instance = getCurrentInstance();
  89. // 面板行数
  90. const tableRows = ref<DateCell[][]>([[], [], [], [], [], []]);
  91. const currentPanelDateObject = ref<typeof Dayjs>(
  92. dayjs(props.currentPanelDate || null),
  93. );
  94. const dateObject = ref<typeof Dayjs>(dayjs(props.date || null));
  95. const startDateObject = ref<typeof Dayjs>(dayjs(props.startDate || null));
  96. const endDateObject = ref<typeof Dayjs>(dayjs(props.endDate || null));
  97. const WEEKS_CONSTANT = computed(() => {
  98. return dayjs.weekdaysShort();
  99. });
  100. // 表头数据
  101. const WEEKS = computed(() =>
  102. WEEKS_CONSTANT.value.map((w: string) => w.substring(1)),
  103. );
  104. // 表格开始日期
  105. const startDateOnTable = computed(() => {
  106. const startDayOfMonth = currentPanelDateObject.value?.startOf('month');
  107. return startDayOfMonth?.subtract(startDayOfMonth?.day() || 7, 'day');
  108. });
  109. // 表格数据
  110. const rows = computed(() => {
  111. const rows_ = tableRows.value;
  112. const cols = WEEKS.value.length;
  113. // 当月第一天
  114. const startOfMonth = currentPanelDateObject.value?.startOf('month');
  115. const startOfMonthDay = startOfMonth?.day() || 7; // day of this month first day
  116. const dateCountOfMonth = startOfMonth?.daysInMonth(); // total days of this month
  117. let count = 1;
  118. // 循环填充表格,6行7列
  119. for (let row = 0; row < tableRows.value.length; row++) {
  120. for (let col = 0; col < cols; col++) {
  121. const cellDate = startDateOnTable.value?.add(count, 'day');
  122. const text = cellDate?.date();
  123. // 对于 type === "single", 选中传入日期
  124. // 对于 type === "range", 选中传入的开始与结束的日期
  125. const isSelected
  126. = props.type === 'single'
  127. && cellDate?.format('YYYY-MM-DD')
  128. === dateObject.value?.format('YYYY-MM-DD');
  129. const isSelectedStart
  130. = props.type === 'range'
  131. && cellDate?.format('YYYY-MM-DD')
  132. === startDateObject.value?.format('YYYY-MM-DD');
  133. const isSelectedEnd
  134. = props.type === 'range'
  135. && cellDate?.format('YYYY-MM-DD')
  136. === endDateObject.value?.format('YYYY-MM-DD');
  137. // 对于 type === "range", 是否在选择区间中
  138. const isInRange
  139. = cellDate?.isSameOrBefore(endDateObject.value, 'day')
  140. && cellDate?.isSameOrAfter(startDateObject.value, 'day');
  141. let type: DateCellType = 'normal';
  142. if (count < startOfMonthDay) {
  143. // 上个月日期
  144. type = 'prev-month';
  145. } else if (count - startOfMonthDay >= dateCountOfMonth) {
  146. // 下个月日期
  147. type = 'next-month';
  148. }
  149. rows_[row][col] = {
  150. type,
  151. date: cellDate,
  152. text,
  153. isSelected: isSelected || isSelectedStart || isSelectedEnd,
  154. isSelectedStart,
  155. isSelectedEnd,
  156. isInRange,
  157. };
  158. count++;
  159. }
  160. }
  161. return rows_;
  162. });
  163. const handlePick = (cell: DateCell) => {
  164. if (cell?.type !== 'normal') {
  165. return;
  166. }
  167. emit('pick', cell);
  168. };
  169. watch(
  170. () => [props.currentPanelDate, props.date, props.startDate, props.endDate],
  171. () => {
  172. currentPanelDateObject.value = dayjs(props.currentPanelDate || null);
  173. dateObject.value = dayjs(props.date || null);
  174. startDateObject.value = dayjs(props.startDate || null);
  175. endDateObject.value = dayjs(props.endDate || null);
  176. nextTick(() => {
  177. instance?.proxy?.$forceUpdate();
  178. });
  179. },
  180. {
  181. deep: true,
  182. immediate: true,
  183. },
  184. );
  185. </script>
  186. <style scoped lang="scss">
  187. /* stylelint-disable selector-class-pattern */
  188. .tui-date-table {
  189. border-spacing: 0;
  190. -webkit-border-horizontal-spacing: 0;
  191. -webkit-border-vertical-spacing: 0;
  192. font-size: 12px;
  193. user-select: none;
  194. table-layout: fixed;
  195. width: 100%;
  196. box-sizing: border-box;
  197. &::after,
  198. &::before {
  199. box-sizing: border-box;
  200. }
  201. &-body {
  202. width: 100%;
  203. background-color: #fff;
  204. &-weeks,
  205. &-days {
  206. box-sizing: border-box;
  207. min-width: 0;
  208. display: flex;
  209. flex-direction: row;
  210. justify-content: space-around;
  211. overflow: hidden;
  212. }
  213. &-weeks {
  214. width: 100%;
  215. &-item {
  216. color: #666;
  217. font-size: 12px;
  218. font-weight: 400px;
  219. }
  220. }
  221. &-days {
  222. color: #000;
  223. &-item {
  224. &-cell {
  225. text-align: center;
  226. padding: 2px;
  227. margin: 2px 0;
  228. &-text {
  229. display: inline-flex;
  230. justify-content: center;
  231. align-items: center;
  232. width: 24px;
  233. height: 24px;
  234. border-radius: 50%;
  235. user-select: none;
  236. cursor: pointer;
  237. box-sizing: border-box;
  238. }
  239. }
  240. .selected {
  241. border-radius: 12px;
  242. .tui-date-table-body-days-item-cell-text {
  243. box-sizing: border-box;
  244. color: #007aff;
  245. border: 1px solid #007aff;
  246. background-color: #fff;
  247. }
  248. }
  249. .range {
  250. background-color: #007aff33;
  251. }
  252. .selected-start {
  253. border-radius: 12px 0 0 12px;
  254. }
  255. .selected-end {
  256. border-radius: 0 12px 12px 0;
  257. }
  258. .selected-start.selected-end {
  259. border-radius: 12px;
  260. }
  261. }
  262. .prev-month,
  263. .next-month {
  264. color: #666;
  265. background-color: #fff;
  266. .range {
  267. color: #666;
  268. background-color: #fff;
  269. }
  270. .selected {
  271. .tui-date-table-body-days-item-cell-text {
  272. box-sizing: border-box;
  273. color: #666;
  274. border: none;
  275. }
  276. }
  277. }
  278. }
  279. }
  280. }
  281. .tui-date-table-h5 {
  282. /* stylelint-disable-next-line no-descending-specificity */
  283. .tui-date-table-body-days-item-cell-text {
  284. cursor: none !important;
  285. }
  286. }
  287. td,
  288. ._td,
  289. .tui-date-table-body-days-item {
  290. flex: 1;
  291. }
  292. </style>