date-picker-panel.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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. <!-- todo: 此处催产品给个双箭头icon -->
  53. <div
  54. v-if="canYearMore"
  55. :class="[n('icon')]"
  56. @click="change('year', 1)"
  57. >
  58. <Icon
  59. :file="dRightArrowIcon"
  60. :width="'12px'"
  61. :height="'12px'"
  62. />
  63. </div>
  64. </div>
  65. </div>
  66. <div :class="[n('body-content')]">
  67. <DateTable
  68. :type="props.type"
  69. :date="props.date"
  70. :startDate="props.startDate"
  71. :endDate="props.endDate"
  72. :currentPanelDate="currentPanelDate"
  73. @pick="handlePick"
  74. />
  75. </div>
  76. </div>
  77. </div>
  78. </template>
  79. <script lang="ts" setup>
  80. import { computed, ref, onBeforeMount } from '../../../adapter-vue';
  81. import dayjs, { Dayjs, ManipulateType } from 'dayjs';
  82. import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
  83. import { DateCell } from './date-picker';
  84. import DateTable from './date-table.vue';
  85. import Icon from '../Icon.vue';
  86. import leftArrowIcon from '../../../assets/icon/left-arrow.svg';
  87. import rightArrowIcon from '../../../assets/icon/right-arrow.svg';
  88. import dLeftArrowIcon from '../../../assets/icon/d-left-arrow.svg';
  89. import dRightArrowIcon from '../../../assets/icon/d-right-arrow.svg';
  90. import { isPC } from '../../../utils/env';
  91. const props = defineProps({
  92. type: {
  93. type: String,
  94. default: 'range', // "single"/"range"
  95. },
  96. // type 为 single 时特有属性
  97. date: {
  98. type: Dayjs,
  99. default: () => dayjs(),
  100. },
  101. // type 为 range 时特有属性
  102. startDate: {
  103. type: Dayjs,
  104. default: null,
  105. },
  106. endDate: {
  107. type: Dayjs,
  108. default: null,
  109. },
  110. rangeType: {
  111. type: String,
  112. default: '', // "left"/"right"
  113. },
  114. currentOtherPanelValue: {
  115. type: Dayjs,
  116. default: null,
  117. },
  118. });
  119. const emit = defineEmits(['pick', 'change']);
  120. const n = (className: string) => {
  121. return className
  122. ? [
  123. 'tui-date-picker-panel-' + className,
  124. !isPC && 'tui-date-picker-panel-h5-' + className,
  125. ]
  126. : ['tui-date-picker-panel', !isPC && 'tui-date-picker-panel-h5'];
  127. };
  128. const currentPanelDate = ref<typeof Dayjs>();
  129. const year = computed(() => currentPanelDate.value?.get('year'));
  130. const month = computed(() => currentPanelDate.value?.format('MMMM'));
  131. const canYearMore = computed(() => {
  132. const prevYearNumber = props.currentOtherPanelValue?.year() - 1;
  133. const prevYear = props.currentOtherPanelValue?.year(prevYearNumber);
  134. return (
  135. props.rangeType === 'right'
  136. || currentPanelDate.value?.isBefore(prevYear, 'year')
  137. );
  138. });
  139. const canMonthMore = computed(() => {
  140. const prevMonthNumber = props.currentOtherPanelValue?.month() - 1;
  141. const prevMonth = props.currentOtherPanelValue?.month(prevMonthNumber);
  142. return (
  143. props.rangeType === 'right'
  144. || currentPanelDate.value?.isBefore(prevMonth, 'month')
  145. );
  146. });
  147. const canYearLess = computed(() => {
  148. const nextYearNumber = props.currentOtherPanelValue?.year() + 1;
  149. const nextYear = props.currentOtherPanelValue?.year(nextYearNumber);
  150. return (
  151. props.rangeType === 'left'
  152. || currentPanelDate.value?.isAfter(nextYear, 'year')
  153. );
  154. });
  155. const canMonthLess = computed(() => {
  156. const nextMonthNumber = props.currentOtherPanelValue?.month() + 1;
  157. const nextMonth = props.currentOtherPanelValue?.month(nextMonthNumber);
  158. return (
  159. props.rangeType === 'left'
  160. || currentPanelDate.value?.isAfter(nextMonth, 'month')
  161. );
  162. });
  163. // range 判断:
  164. // 前提,如果只有一个,那一定是start
  165. // 如果有startDate
  166. // left所在界面首次展示startDate所在month/year
  167. // 如果有startDate && endDate
  168. // 如果在同一个月
  169. // 均在left展示,right展示下一个月
  170. // 如果不在同个月
  171. // start在left展示,end在right展示
  172. // 即要判断start和end是否在同一个月
  173. // 如果都没有 left展示当前月 right展示下一个月
  174. const handleSingleDate = (): { date: typeof Dayjs } => {
  175. if (props.date && dayjs(props.date)?.isValid()) {
  176. // props.date year and month
  177. return {
  178. date: props?.date,
  179. };
  180. }
  181. // nowadays year and month
  182. return {
  183. date: dayjs(),
  184. };
  185. };
  186. const handleRangeDate = (): { date: typeof Dayjs } => {
  187. // 计算左边
  188. switch (props.rangeType) {
  189. case 'left':
  190. if (props.startDate && dayjs.isDayjs(props.startDate)) {
  191. return {
  192. date: props?.startDate,
  193. };
  194. } else {
  195. return {
  196. date: dayjs(),
  197. };
  198. }
  199. case 'right':
  200. if (
  201. props.endDate
  202. && dayjs.isDayjs(props.endDate)
  203. && props?.endDate?.isAfter(props.startDate, 'month')
  204. ) {
  205. return {
  206. date: props?.endDate,
  207. };
  208. } else {
  209. const _month = (props.startDate || dayjs()).month();
  210. return {
  211. date: (props.startDate || dayjs()).month(_month + 1),
  212. };
  213. }
  214. default:
  215. return {
  216. date: dayjs(),
  217. };
  218. }
  219. };
  220. function handlePick(cell: DateCell) {
  221. emit('pick', cell);
  222. }
  223. // 统一处理日期切换
  224. function change(type: typeof ManipulateType, num: number) {
  225. currentPanelDate.value = dayjs(currentPanelDate.value.toDate()).add(
  226. num,
  227. type,
  228. );
  229. emit('change', currentPanelDate.value);
  230. }
  231. onBeforeMount(() => {
  232. switch (props.type) {
  233. case 'single':
  234. currentPanelDate.value = handleSingleDate().date;
  235. emit('change', currentPanelDate.value);
  236. break;
  237. case 'range':
  238. currentPanelDate.value = handleRangeDate().date;
  239. emit('change', currentPanelDate.value);
  240. break;
  241. }
  242. });
  243. </script>
  244. <style scoped lang="scss">
  245. .tui-date-picker-panel {
  246. width: 200px;
  247. margin: 5px;
  248. &-body {
  249. width: 200px;
  250. display: flex;
  251. flex-direction: column;
  252. &-header {
  253. width: 100%;
  254. display: flex;
  255. flex-direction: row;
  256. height: 30px;
  257. padding: 0 5px;
  258. box-sizing: border-box;
  259. &-prev {
  260. display: flex;
  261. flex-direction: row;
  262. cursor: pointer;
  263. width: 24px;
  264. }
  265. &-label {
  266. flex: 1;
  267. display: flex;
  268. flex-direction: row;
  269. text-align: center;
  270. align-items: center;
  271. justify-content: center;
  272. user-select: none;
  273. color: #666;
  274. &-item {
  275. padding: 0 5px;
  276. color: #666;
  277. }
  278. }
  279. &-next {
  280. display: flex;
  281. flex-direction: row;
  282. cursor: pointer;
  283. width: 24px;
  284. }
  285. }
  286. }
  287. &-icon {
  288. display: flex;
  289. justify-content: center;
  290. align-items: center;
  291. width: 12px;
  292. }
  293. }
  294. </style>