Bläddra i källkod

feat: 新增TimeLineList组件以及示例

lanjianrong 4 år sedan
förälder
incheckning
2295fdff63

+ 66 - 0
src/components/TimeLineList/index.less

@@ -0,0 +1,66 @@
+.time-list-container {
+  .time-item-content {
+    @apply w-full w-full;
+    .time-item-label {
+      height: 40px;
+      min-width: 120px;
+
+      .time-item-label-forward {
+        @apply text-xl text-black text-opacity-80 font-medium;
+      }
+      .time-item-label-base {
+        @apply flex items-center;
+        .year {
+          @apply text-xs text-opacity-40 text-hex-000;
+        }
+        .date {
+          @apply text-sm font-medium text-opacity-80;
+        }
+      }
+      .time-item-label-square {
+        @apply w-2.5 h-2.5 bg-black opacity-15 rounded-1/2;
+      }
+    }
+    .time-item-render {
+      @apply w-full w-full ml-3;
+    }
+  }
+  .time-item-has-line {
+    @apply flex flex-nowrap relative;
+    .time-item-label-has-line {
+      @apply flex justify-center items-center;
+    }
+    .time-item-render-has-line {
+      @apply min-h-22;
+    }
+    &:not(:last-child)::after {
+      content: ' ';
+      position: absolute;
+      left: 59px;
+      top: 40px;
+      height: calc((100% - 40px));
+      width: 1px;
+      border-left: 2px solid rgba(0, 0, 0, 0.1);
+    }
+  }
+  .time-item-has-card {
+    @apply flex flex-col relative pb-8;
+    .time-item-label-has-card {
+      @apply flex justify-start items-center;
+    }
+
+    .time-item-render-has-card {
+      @apply my-4 ml-0;
+    }
+    &::after {
+      content: ' ';
+      position: absolute;
+      width: 100%;
+      bottom: 0;
+      border-bottom: 2px solid rgba(0, 0, 0, 0.1);
+    }
+    &:not(:first-child) {
+      @apply pt-8;
+    }
+  }
+}

+ 134 - 0
src/components/TimeLineList/index.tsx

@@ -0,0 +1,134 @@
+import classNames from 'classnames'
+import dayjs from 'dayjs'
+import { useState, useEffect } from 'react'
+import './index.less'
+
+enum ModeType {
+  LINE = 'line',
+  CARD = 'card'
+}
+
+type TimeLineListProps<T> = {
+  rowKey?: ((item: T) => React.Key) | keyof T
+  dataSource: T[]
+  renderItem?: (item: T, index: number) => React.ReactNode
+  dataField: keyof T
+  mode: 'line' | 'card'
+}
+
+function TimeLineList<T>({
+  dataField = 'createTime',
+  dataSource = [],
+  mode = 'line',
+  renderItem
+}: TimeLineListProps<T>) {
+  // 用二维数组去存数据
+  const [dataMap, setDataMap] = useState<Nullable<T[][]>>(null)
+
+  useEffect(() => {
+    const formatTimeMap = () => {
+      // 先去重,按日期进行分组
+      const uniKeys = Array.from(new Set(dataSource.map(item => item?.[dataField])))
+      const uniDataMap = []
+      // 构建二维数组
+      uniKeys.forEach(key => {
+        uniDataMap.push(dataSource.filter(item => item?.[dataField] === key))
+      })
+      setDataMap(uniDataMap)
+    }
+    dataSource?.length && formatTimeMap(dataSource)
+  }, [dataSource])
+
+  if (!dataMap) return null
+
+  // 渲染左侧日期
+  const renderDate = (dataIndex: number, diffTime: number, renderTime: dayjs.Dayjs) => {
+    let node = null
+    if (dataIndex === 0) {
+      switch (diffTime) {
+        case 0:
+          node = '今天'
+          break
+        case 1:
+          node = '昨天'
+          break
+        case 2:
+          node = renderTime.format('MM-DD')
+          break
+        default:
+          node = (
+            <div
+              className={classNames('flex flex-col justify-center', {
+                'items-center': mode === ModeType.LINE
+              })}>
+              <span className="year">{renderTime.get('year')}</span>
+              <span className="date">{renderTime.format('MM-DD')}</span>
+            </div>
+          )
+          break
+      }
+    }
+    return node
+  }
+
+  // render mode timeline
+  const renderTimelineItem = () => {
+    return dataMap.map(t =>
+      t.map((item, i) => {
+        const itemTime = dayjs(item?.[dataField])
+        const currentTime = dayjs()
+        if (itemTime && currentTime) {
+          const renderTime = currentTime.diff(itemTime, 'day')
+          return (
+            <div key={item.id} className="time-item-content time-item-has-line">
+              <div className="time-item-label time-item-label-has-line">
+                <div
+                  className={classNames(i > 0 ? 'time-item-label-square' : 'time-item-label-base', {
+                    'time-item-label-forward': renderTime <= 2
+                  })}>
+                  {renderDate(i, renderTime, itemTime)}
+                </div>
+              </div>
+              <div className="time-item-render time-item-render-has-line">{renderItem?.(item, i)}</div>
+            </div>
+          )
+        }
+        return null
+      })
+    )
+  }
+
+  // render mode card
+  const renderCardItem = () => {
+    return dataMap.map(t =>
+      t.map((item, i) => {
+        const itemTime = dayjs(item?.[dataField])
+        const currentTime = dayjs()
+        if (itemTime && currentTime) {
+          const renderTime = currentTime.diff(itemTime, 'day')
+          return (
+            <div key={item.id} className="time-item-content time-item-has-card">
+              <div className="time-item-label time-item-label-has-card">
+                <div
+                  className={classNames('time-item-label-base', {
+                    'time-item-label-forward': renderTime <= 2
+                  })}>
+                  {renderDate(i, renderTime, itemTime)}
+                </div>
+              </div>
+              <div className="time-item-render time-item-render-has-card">{renderItem?.(item, i)}</div>
+            </div>
+          )
+        }
+        return null
+      })
+    )
+  }
+
+  return (
+    <div className="w-full h-full time-list-container">
+      {mode === ModeType.LINE ? renderTimelineItem() : renderCardItem()}
+    </div>
+  )
+}
+export default TimeLineList

+ 91 - 42
src/pages/Customer/Test/index.tsx

@@ -1,53 +1,102 @@
-import { useWebSocket } from 'ahooks'
-import { Button } from 'antd'
-import { useRef, useEffect, useMemo } from 'react'
-// import QueueAnim from 'rc-queue-anim'
+import TimeLineList from '@/components/TimeLineList'
 
-enum ReadyState {
-  '0' = '连接中',
-  '1' = '正常',
-  '2' = '断开中',
-  '3' = '断开'
+type LogItem = {
+  id: string
+  createTime: string
 }
 
 const Test = () => {
-  const { readyState, latestMessage, disconnect, connect } = useWebSocket(
-    'ws://192.168.1.26:9000/m/v1/mail/fanout/progress'
-  )
-  const messageHistory = useRef([])
-  const messageBox = useRef<HTMLElement>(null)
-  messageHistory.current = useMemo(() => {
-    if (latestMessage?.data) {
-      const data = JSON.parse(latestMessage.data)
-      return messageHistory.current.concat(data)
+  const dataSource = [
+    {
+      id: 'McTlcd1iMdgcO0O0OpO0O0O',
+      createTime: '2022-05-24 12:09:41'
+    },
+    {
+      id: 'McTlcd1iMdgcO0O0Op245O',
+      createTime: '2022-05-23 12:09:41'
+    },
+    {
+      id: 'McTlc60Op245O',
+      createTime: '2022-05-23 12:09:41'
+    },
+    {
+      id: 'McTlcd1iM10O0Op245O',
+      createTime: '2022-05-23 12:09:41'
+    },
+    {
+      id: 'McTlcd1iMdgaaadO0OpO0O0O',
+      createTime: '2022-05-22 12:09:41'
+    },
+    {
+      id: 'McTlcd1bwerMdgcO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
+    },
+    {
+      id: 'McTlcd1b1cO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
+    },
+    {
+      id: 'McTl2werMdgcO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
+    },
+    {
+      id: 'McTlc5rMdgcO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
     }
-    return messageHistory.current
-  }, [latestMessage])
-
-  useEffect(() => {
-    if (messageBox.current.scrollHeight - messageBox.current.clientHeight > messageBox.current.scrollTop) {
-      messageBox.current?.scroll({ top: messageBox.current?.scrollHeight })
+  ]
+  const dataSource1 = [
+    {
+      id: 'McTlcd1iMdgcO0O0OpO0O0O',
+      createTime: '2022-05-24 12:09:41'
+    },
+    {
+      id: 'McTlcd1iMdgcO0O0Op245O',
+      createTime: '2022-05-23 12:09:41'
+    },
+    {
+      id: 'McTlcd1iMdgaaadO0OpO0O0O',
+      createTime: '2022-05-22 12:09:41'
+    },
+    {
+      id: 'McTlcd1bwerMdgcO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
+    },
+    {
+      id: 'McTlcd1b1cO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
+    },
+    {
+      id: 'McTl2werMdgcO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
+    },
+    {
+      id: 'McTlc5rMdgcO0O0OpO0O0O',
+      createTime: '2021-08-25 12:21:41'
     }
-  }, [messageHistory.current])
-
+  ]
   return (
-    <div className="w-full h-full bg-hex-cccccc p-4 flex flex-row">
-      <div>
-        <Button onClick={() => connect()}>连接</Button>
-        <Button onClick={() => disconnect()}>断开</Button>
-        <div>当前连接状态:{ReadyState[readyState]}</div>
+    <div className="h-full w-full flex flex-row overflow-scroll">
+      <div className="w-300px h-400px">
+        <TimeLineList<LogItem>
+          mode="line"
+          dataSource={dataSource}
+          renderItem={() => <div className="w-200px h-150px bg-yellow-400" />}
+        />
+      </div>
+      <div className="w-300px h-400px">
+        <TimeLineList<LogItem>
+          mode="line"
+          dataSource={dataSource}
+          // renderItem={() => <div className="w-200px h-150px bg-yellow-400" />}
+        />
+      </div>
+      <div className="w-300px h-400px">
+        <TimeLineList<LogItem>
+          mode="card"
+          dataSource={dataSource1}
+          renderItem={() => <div className="w-full h-150px bg-green-400" />}
+        />
       </div>
-      {/* <QueueAnim
-        className="ml-4 border w-400px h-400px border-gray-400 overflow-y-auto overflow-x-hidden"
-        ref={messageBox}
-      >
-        {messageHistory.current?.map(item => (
-          <div key={item.mail}>
-            <span>{item.mail}</span>
-            <span>{item.status}</span>
-          </div>
-        ))}
-      </QueueAnim> */}
     </div>
   )
 }