index.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import classNames from 'classnames'
  2. import dayjs from 'dayjs'
  3. import { useState, useEffect } from 'react'
  4. import './index.less'
  5. enum ModeType {
  6. LINE = 'line',
  7. CARD = 'card'
  8. }
  9. type TimeLineListProps<T> = {
  10. rowKey?: ((item: T) => React.Key) | keyof T
  11. dataSource: T[]
  12. renderItem?: (item: T, index: number) => React.ReactNode
  13. dataField: keyof T
  14. mode: 'line' | 'card'
  15. }
  16. function TimeLineList<T>({
  17. dataField = 'createTime',
  18. dataSource = [],
  19. mode = 'line',
  20. renderItem
  21. }: TimeLineListProps<T>) {
  22. // 用二维数组去存数据
  23. const [dataMap, setDataMap] = useState<Nullable<T[][]>>(null)
  24. useEffect(() => {
  25. const formatTimeMap = () => {
  26. // 先去重,按日期进行分组
  27. const uniKeys = Array.from(new Set(dataSource.map(item => item?.[dataField])))
  28. const uniDataMap = []
  29. // 构建二维数组
  30. uniKeys.forEach(key => {
  31. uniDataMap.push(dataSource.filter(item => item?.[dataField] === key))
  32. })
  33. setDataMap(uniDataMap)
  34. }
  35. dataSource?.length && formatTimeMap(dataSource)
  36. }, [dataSource])
  37. if (!dataMap) return null
  38. // 渲染左侧日期
  39. const renderDate = (dataIndex: number, diffTime: number, renderTime: dayjs.Dayjs) => {
  40. let node = null
  41. if (dataIndex === 0) {
  42. switch (diffTime) {
  43. case 0:
  44. node = '今天'
  45. break
  46. case 1:
  47. node = '昨天'
  48. break
  49. case 2:
  50. node = renderTime.format('MM-DD')
  51. break
  52. default:
  53. node = (
  54. <div
  55. className={classNames('flex flex-col justify-center', {
  56. 'items-center': mode === ModeType.LINE
  57. })}>
  58. <span className="year">{renderTime.get('year')}</span>
  59. <span className="date">{renderTime.format('MM-DD')}</span>
  60. </div>
  61. )
  62. break
  63. }
  64. }
  65. return node
  66. }
  67. // render mode timeline
  68. const renderTimelineItem = () => {
  69. return dataMap.map(t =>
  70. t.map((item, i) => {
  71. const itemTime = dayjs(item?.[dataField])
  72. const currentTime = dayjs()
  73. if (itemTime && currentTime) {
  74. const renderTime = currentTime.diff(itemTime, 'day')
  75. return (
  76. <div key={item.id} className="time-item-content time-item-has-line">
  77. <div className="time-item-label time-item-label-has-line">
  78. <div
  79. className={classNames(i > 0 ? 'time-item-label-square' : 'time-item-label-base', {
  80. 'time-item-label-forward': renderTime <= 2
  81. })}>
  82. {renderDate(i, renderTime, itemTime)}
  83. </div>
  84. </div>
  85. <div className="time-item-render time-item-render-has-line">{renderItem?.(item, i)}</div>
  86. </div>
  87. )
  88. }
  89. return null
  90. })
  91. )
  92. }
  93. // render mode card
  94. const renderCardItem = () => {
  95. return dataMap.map(t =>
  96. t.map((item, i) => {
  97. const itemTime = dayjs(item?.[dataField])
  98. const currentTime = dayjs()
  99. if (itemTime && currentTime) {
  100. const renderTime = currentTime.diff(itemTime, 'day')
  101. return (
  102. <div
  103. key={item.id}
  104. className={classNames('time-item-content time-item-has-card', {
  105. 'time-item-last': t.length - 1 === i
  106. })}>
  107. <div className="time-item-label time-item-label-has-card">
  108. <div
  109. className={classNames('time-item-label-base', {
  110. 'time-item-label-forward': renderTime <= 2
  111. })}>
  112. {renderDate(i, renderTime, itemTime)}
  113. </div>
  114. </div>
  115. <div className="time-item-render time-item-render-has-card">{renderItem?.(item, i)}</div>
  116. <div className={classNames({ 'time-item-label-tail': t.length - 1 === i })} />
  117. </div>
  118. )
  119. }
  120. return null
  121. })
  122. )
  123. }
  124. return (
  125. <div className="w-full h-full time-list-container">
  126. {mode === ModeType.LINE ? renderTimelineItem() : renderCardItem()}
  127. </div>
  128. )
  129. }
  130. export default TimeLineList