date-picker-panel.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <template>
  2. <div
  3. :class="[n('')]"
  4. @mouseup.stop
  5. >
  6. <div :class="[n('body')]">
  7. <div :class="[n('body-header')]">
  8. <div :class="[n('body-header-prev')]">
  9. <div
  10. v-if="canYearLess"
  11. :class="[n('icon')]"
  12. @click="change('year', -1)"
  13. >
  14. <Icon
  15. :file="dLeftArrowIcon"
  16. :width="'12px'"
  17. :height="'12px'"
  18. />
  19. </div>
  20. <div
  21. v-if="canMonthLess"
  22. :class="[n('icon')]"
  23. @click="change('month', -1)"
  24. >
  25. <Icon
  26. :file="leftArrowIcon"
  27. :width="'10px'"
  28. :height="'10px'"
  29. />
  30. </div>
  31. </div>
  32. <div :class="[n('body-header-label')]">
  33. <div :class="[n('body-header-label-item')]">
  34. {{ year }}
  35. </div>
  36. <div :class="[n('body-header-label-item')]">
  37. {{ TUITranslateService.t(`time.${month}`) }}
  38. </div>
  39. </div>
  40. <div :class="[n('body-header-next')]">
  41. <div
  42. v-if="canMonthMore"
  43. :class="[n('icon')]"
  44. @click="change('month', 1)"
  45. >
  46. <Icon
  47. :file="rightArrowIcon"
  48. :width="'10px'"
  49. :height="'10px'"
  50. />
  51. </div>
  52. <div
  53. v-if="canYearMore"
  54. :class="[n('icon')]"
  55. @click="change('year', 1)"
  56. >
  57. <Icon
  58. :file="dRightArrowIcon"
  59. :width="'12px'"
  60. :height="'12px'"
  61. />
  62. </div>
  63. </div>
  64. </div>
  65. <div :class="[n('body-content')]">
  66. <DateTable
  67. :type="props.type"
  68. :date="props.date"
  69. :startDate="props.startDate"
  70. :endDate="props.endDate"
  71. :currentPanelDate="currentPanelDate"
  72. @pick="handlePick"
  73. />
  74. </div>
  75. </div>
  76. </div>
  77. </template>
  78. <script lang="ts" setup>
  79. import { computed, ref, onBeforeMount } from '../../../adapter-vue';
  80. import dayjs, { Dayjs, ManipulateType } from 'dayjs';
  81. import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
  82. import { DateCell } from './date-picker';
  83. import DateTable from './date-table.vue';
  84. import Icon from '../Icon.vue';
  85. import leftArrowIcon from '../../../assets/icon/left-arrow.svg';
  86. import rightArrowIcon from '../../../assets/icon/right-arrow.svg';
  87. import dLeftArrowIcon from '../../../assets/icon/d-left-arrow.svg';
  88. import dRightArrowIcon from '../../../assets/icon/d-right-arrow.svg';
  89. import { isPC } from '../../../utils/env';
  90. const props = defineProps({
  91. type: {
  92. type: String,
  93. default: 'range', // "single"/"range"
  94. },
  95. // Unique attribute when type is single
  96. date: {
  97. type: Dayjs,
  98. default: () => dayjs(),
  99. },
  100. // Unique attribute when type is range
  101. startDate: {
  102. type: Dayjs,
  103. default: null,
  104. },
  105. endDate: {
  106. type: Dayjs,
  107. default: null,
  108. },
  109. rangeType: {
  110. type: String,
  111. default: '', // "left"/"right"
  112. },
  113. currentOtherPanelValue: {
  114. type: Dayjs,
  115. default: null,
  116. },
  117. });
  118. const emit = defineEmits(['pick', 'change']);
  119. const n = (className: string) => {
  120. return className
  121. ? [
  122. 'tui-date-picker-panel-' + className,
  123. !isPC && 'tui-date-picker-panel-h5-' + className,
  124. ]
  125. : ['tui-date-picker-panel', !isPC && 'tui-date-picker-panel-h5'];
  126. };
  127. const currentPanelDate = ref<typeof Dayjs>();
  128. const year = computed(() => currentPanelDate.value?.get('year'));
  129. const month = computed(() => currentPanelDate.value?.format('MMMM'));
  130. const canYearMore = computed(() => {
  131. const prevYearNumber = props.currentOtherPanelValue?.year() - 1;
  132. const prevYear = props.currentOtherPanelValue?.year(prevYearNumber);
  133. return (
  134. props.rangeType === 'right'
  135. || currentPanelDate.value?.isBefore(prevYear, 'year')
  136. );
  137. });
  138. const canMonthMore = computed(() => {
  139. const prevMonthNumber = props.currentOtherPanelValue?.month() - 1;
  140. const prevMonth = props.currentOtherPanelValue?.month(prevMonthNumber);
  141. return (
  142. props.rangeType === 'right'
  143. || currentPanelDate.value?.isBefore(prevMonth, 'month')
  144. );
  145. });
  146. const canYearLess = computed(() => {
  147. const nextYearNumber = props.currentOtherPanelValue?.year() + 1;
  148. const nextYear = props.currentOtherPanelValue?.year(nextYearNumber);
  149. return (
  150. props.rangeType === 'left'
  151. || currentPanelDate.value?.isAfter(nextYear, 'year')
  152. );
  153. });
  154. const canMonthLess = computed(() => {
  155. const nextMonthNumber = props.currentOtherPanelValue?.month() + 1;
  156. const nextMonth = props.currentOtherPanelValue?.month(nextMonthNumber);
  157. return (
  158. props.rangeType === 'left'
  159. || currentPanelDate.value?.isAfter(nextMonth, 'month')
  160. );
  161. });
  162. // Range judgment:
  163. // Premise: If there is only one, it must be the start.
  164. // If there is a startDate:
  165. // When the left side of the interface first displays the month/year of the startDate.
  166. // If there is both a startDate and an endDate:
  167. // If they are in the same month:
  168. // Both are displayed on the left, and the next month is displayed on the right.
  169. // If they are not in the same month:
  170. // The start is displayed on the left, and the end is displayed on the right.
  171. // That is, to determine whether the start and end are in the same month.
  172. // If neither is present, the left displays the current month, and the right displays the next month.
  173. const handleSingleDate = (): { date: typeof Dayjs } => {
  174. if (props.date && dayjs(props.date)?.isValid()) {
  175. // props.date year and month
  176. return {
  177. date: props?.date,
  178. };
  179. }
  180. // nowadays year and month
  181. return {
  182. date: dayjs(),
  183. };
  184. };
  185. const handleRangeDate = (): { date: typeof Dayjs } => {
  186. switch (props.rangeType) {
  187. case 'left':
  188. if (props.startDate && dayjs.isDayjs(props.startDate)) {
  189. return {
  190. date: props?.startDate,
  191. };
  192. } else {
  193. return {
  194. date: dayjs(),
  195. };
  196. }
  197. case 'right':
  198. if (
  199. props.endDate
  200. && dayjs.isDayjs(props.endDate)
  201. && props?.endDate?.isAfter(props.startDate, 'month')
  202. ) {
  203. return {
  204. date: props?.endDate,
  205. };
  206. } else {
  207. const _month = (props.startDate || dayjs()).month();
  208. return {
  209. date: (props.startDate || dayjs()).month(_month + 1),
  210. };
  211. }
  212. default:
  213. return {
  214. date: dayjs(),
  215. };
  216. }
  217. };
  218. function handlePick(cell: DateCell) {
  219. emit('pick', cell);
  220. }
  221. function change(type: typeof ManipulateType, num: number) {
  222. currentPanelDate.value = dayjs(currentPanelDate.value.toDate()).add(
  223. num,
  224. type,
  225. );
  226. emit('change', currentPanelDate.value);
  227. }
  228. onBeforeMount(() => {
  229. switch (props.type) {
  230. case 'single':
  231. currentPanelDate.value = handleSingleDate().date;
  232. emit('change', currentPanelDate.value);
  233. break;
  234. case 'range':
  235. currentPanelDate.value = handleRangeDate().date;
  236. emit('change', currentPanelDate.value);
  237. break;
  238. }
  239. });
  240. </script>
  241. <style scoped lang="scss">
  242. .tui-date-picker-panel {
  243. width: 200px;
  244. margin: 5px;
  245. &-body {
  246. width: 200px;
  247. display: flex;
  248. flex-direction: column;
  249. &-header {
  250. width: 100%;
  251. display: flex;
  252. flex-direction: row;
  253. height: 30px;
  254. padding: 0 5px;
  255. box-sizing: border-box;
  256. &-prev {
  257. display: flex;
  258. flex-direction: row;
  259. cursor: pointer;
  260. width: 24px;
  261. }
  262. &-label {
  263. flex: 1;
  264. display: flex;
  265. flex-direction: row;
  266. text-align: center;
  267. align-items: center;
  268. justify-content: center;
  269. user-select: none;
  270. color: #666;
  271. &-item {
  272. padding: 0 5px;
  273. color: #666;
  274. }
  275. }
  276. &-next {
  277. display: flex;
  278. flex-direction: row;
  279. cursor: pointer;
  280. width: 24px;
  281. }
  282. }
  283. }
  284. &-icon {
  285. display: flex;
  286. justify-content: center;
  287. align-items: center;
  288. width: 12px;
  289. }
  290. }
  291. </style>