Преглед изворни кода

Merge branch 'dev' of http://192.168.1.41:3000/outaozhen/cldV2react into dev

outaozhen пре 4 година
родитељ
комит
d96d671448

+ 1 - 1
config/defaultSettings.js

@@ -8,7 +8,7 @@ const proSettings = {
   colorWeak: false,
   title: 'CLD.V2',
   pwa: false,
-  iconfontUrl: '//at.alicdn.com/t/font_2486255_ue9i7q0sf5e.js',
+  iconfontUrl: '//at.alicdn.com/t/font_2486255_rvbghry9dyr.js',
   splitMenus: true
 }
 export default proSettings

+ 6 - 6
config/routes.js

@@ -70,13 +70,13 @@ export default [
         component: './Customer/Business',
         access: 'authRouteFilter',
         validatePerm: true
+      },
+      {
+        path: '/customer/test',
+        name: 'test',
+        icon: 'icon-usd-circle',
+        component: './Customer/Test'
       }
-      // {
-      //   path: '/customer/test',
-      //   name: 'test',
-      //   icon: 'icon-usd-circle',
-      //   component: './Customer/Test'
-      // }
     ]
   },
   {

+ 3 - 3
package.json

@@ -50,8 +50,8 @@
   "dependencies": {
     "@ant-design/charts": "^1.3.2",
     "@ant-design/icons": "^4.0.0",
-    "@ant-design/pro-form": "^1.66.0",
-    "@ant-design/pro-layout": "^6.19.3",
+    "@ant-design/pro-form": "1.66.0",
+    "@ant-design/pro-layout": "6.19.3",
     "@ant-design/pro-table": "^2.60.0",
     "@icon-park/react": "^1.3.3",
     "@umijs/route-utils": "^2.0.5",
@@ -71,7 +71,7 @@
     "react-dom": "17.0.2",
     "react-infinite-scroll-component": "^6.1.0",
     "react-markdown": "^7.1.0",
-    "umi": "3.5.4",
+    "umi": "3.5.24",
     "vditor": "3.8.14",
     "virtuallist-antd": "^0.7.2"
   },

+ 0 - 1
src/components/Table/src/BasicTable.tsx

@@ -6,7 +6,6 @@ import { TABLE_COLUMNS_MAP, ColumnStateEnum } from '@/utils/cache/cacheEnum'
 import { isBoolean, isMobile, isNullOrUnDef, isObject, isUnDef } from '@/utils/is'
 import ProTable from '@ant-design/pro-table'
 import { useModel } from 'umi'
-import { useUpdateLayoutEffect } from 'ahooks'
 import { getAuthCache, getPermAuthCache, setAuthCache } from '@/utils/auth'
 import { useTableScroll } from './hooks/useTableScroll'
 import type { ColumnsState } from '@ant-design/pro-table'

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

@@ -0,0 +1,64 @@
+.time-list-container {
+  .time-item-content {
+    @apply w-full w-full;
+    .time-item-label {
+      .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 {
+      height: 40px;
+      min-width: 120px;
+      @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;
+    .time-item-label-has-card {
+      @apply flex justify-start items-center;
+    }
+    &.time-item-last {
+      @apply mb-8;
+    }
+    .time-item-label-tail {
+      position: absolute;
+      width: 100%;
+      height: 1px;
+      bottom: -16px;
+      border-bottom: 2px solid rgba(0, 0, 0, 0.1);
+    }
+    .time-item-render-has-card {
+      @apply my-4 ml-0;
+    }
+  }
+}

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

@@ -0,0 +1,140 @@
+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
+  /** @name 指定渲染时间轴的字段, 默认:createTime */
+  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={classNames('time-item-content time-item-has-card', {
+                'time-item-last': t.length - 1 === i
+              })}>
+              <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 className={classNames({ 'time-item-label-tail': t.length - 1 === i })} />
+            </div>
+          )
+        }
+        return null
+      })
+    )
+  }
+
+  return (
+    <div className="w-full h-full time-list-container">
+      {mode === ModeType.LINE ? renderTimelineItem() : renderCardItem()}
+    </div>
+  )
+}
+export default TimeLineList

+ 10 - 18
src/pages/Customer/Company/components/CompanyDetail/TabList.jsx

@@ -58,8 +58,7 @@ const CompanyTabList = ({ initData, customerId, client, software, customer, busi
         if (key !== defaultSelectedKey) {
           changePriority(key, id)
         }
-      }}
-    >
+      }}>
       <Menu.Item key="1">1</Menu.Item>
       <Menu.Item key="2">2</Menu.Item>
       <Menu.Item key="3">3</Menu.Item>
@@ -87,12 +86,10 @@ const CompanyTabList = ({ initData, customerId, client, software, customer, busi
       title: '所有部门',
       dataIndex: 'department',
       width: 100,
-      filters: Array.from(
-        new Set(client.client?.map(item => item.department)).map(item => ({
-          text: item,
-          value: item
-        }))
-      ),
+      filters: Array.from(new Set(client.client?.map(item => item.department))).map(item => ({
+        text: item,
+        value: item
+      })),
       onFilter: (value, item) => item.department === value
     },
     {
@@ -104,8 +101,7 @@ const CompanyTabList = ({ initData, customerId, client, software, customer, busi
         <div className="text-primary hover:text-hex-967bbd">
           <span
             onClick={() => dispatchModal('D_CLIENT_DETAIL', { dataId: record.id })}
-            className="cursor-pointer "
-          >
+            className="cursor-pointer ">
             {clientName}
           </span>
         </div>
@@ -140,8 +136,7 @@ const CompanyTabList = ({ initData, customerId, client, software, customer, busi
           onClick={() => dispatchModal('D_LOCK_DETAIL', { dataId: record.id })}
           className={classNames('cursor-pointer hover:text-hex-967bbd', {
             'text-primary': record.preserveStatus === 3
-          })}
-        >
+          })}>
           {record.preserveStatus === 3 ? (
             <Text delete type="secondary">
               {clientName}
@@ -278,8 +273,7 @@ const CompanyTabList = ({ initData, customerId, client, software, customer, busi
               dataId: record.ssoId,
               projectType: record.project_type
             })
-          }
-        >
+          }>
           {text}
         </div>
       )
@@ -428,8 +422,7 @@ const CompanyTabList = ({ initData, customerId, client, software, customer, busi
           ghost
           size="small"
           className="mr-1"
-          onClick={() => dispatchModal('M_RECORD_ADD', { onConfirm: handleAddCustomerService })}
-        >
+          onClick={() => dispatchModal('M_RECORD_ADD', { onConfirm: handleAddCustomerService })}>
           <Plus className="mr-1" /> 添加服务记录
         </Button>
         <Button
@@ -448,8 +441,7 @@ const CompanyTabList = ({ initData, customerId, client, software, customer, busi
                 landmarks: customer.landmarks
               }
             })
-          }
-        >
+          }>
           <Plus className="mr-1" />
           客户
         </Button>

+ 0 - 11
src/pages/Customer/Email/index.tsx

@@ -1,11 +0,0 @@
-import { PageContainer } from '@ant-design/pro-layout'
-
-const Email = () => {
-  return (
-    <PageContainer title={false} breadcrumb={false}>
-      111
-    </PageContainer>
-  )
-}
-
-export default Email

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

@@ -1,53 +1,97 @@
-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'
+import { Select } from 'antd'
 
-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-500px h-400px">
+        <TimeLineList<LogItem>
+          mode="line"
+          dataSource={dataSource}
+          renderItem={() => <div className="w-full h-150px bg-yellow-400 mb-4" />}
+        />
+      </div>
+
+      <div className="w-500px h-400px ml-9">
+        <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>
   )
 }

+ 43 - 0
src/typings.d.ts

@@ -0,0 +1,43 @@
+declare module 'slash2'
+declare module '*.css'
+declare module '*.less'
+declare module '*.scss'
+declare module '*.sass'
+declare module '*.svg'
+declare module '*.png'
+declare module '*.jpg'
+declare module '*.jpeg'
+declare module '*.gif'
+declare module '*.bmp'
+declare module '*.tiff'
+declare module 'omit.js'
+declare module 'numeral'
+declare module '@antv/data-set'
+declare module 'mockjs'
+declare module 'react-fittext'
+declare module 'bizcharts-plugin-slider'
+
+// google analytics interface
+type GAFieldsObject = {
+  eventCategory: string
+  eventAction: string
+  eventLabel?: string
+  eventValue?: number
+  nonInteraction?: boolean
+}
+
+type Window = {
+  ga: (command: 'send', hitType: 'event' | 'pageview', fieldsObject: GAFieldsObject | string) => void
+  reloadAuthorized: () => void
+  routerBase: string
+}
+
+declare let ga: () => void
+
+// preview.pro.ant.design only do not use in your production ;
+// preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
+declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined
+
+declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false
+
+declare type Nullable<T> = T | null