From c993fb84efeb05135cbfc6b7125caccf74506f44 Mon Sep 17 00:00:00 2001 From: "w1359774872@163.com" Date: Fri, 23 Dec 2022 19:44:17 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ID1000830):=20=E3=80=90?= =?UTF-8?q?=E4=B8=B4=E3=80=91=E5=86=85=E9=83=A8=E5=B0=8F=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E9=94=80=E5=94=AE=E6=8A=A5=E8=A1=A8=E9=9D=A2=E6=96=99=E6=8E=92?= =?UTF-8?q?=E8=A1=8C=E5=87=BA=E8=A1=A5=E5=85=85=E9=9D=A2=E6=96=99=E7=BC=96?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【【临】内部小程序销售报表面料排行出补充面料编号】 https://www.tapd.cn/53459131/prong/stories/view/1153459131001000830 --- .../components/CellTips/index.module.scss | 53 +++ .../table/components/CellTips/index.tsx | 326 ++++++++++++++++++ src/components/table/index.tsx | 76 +++- src/components/toolTips/index.tsx | 11 +- src/pages/saleStatistic/index.config.ts | 2 +- src/pages/saleStatistic/index.tsx | 36 +- 6 files changed, 472 insertions(+), 32 deletions(-) create mode 100644 src/components/table/components/CellTips/index.module.scss create mode 100644 src/components/table/components/CellTips/index.tsx diff --git a/src/components/table/components/CellTips/index.module.scss b/src/components/table/components/CellTips/index.module.scss new file mode 100644 index 0000000..fc516a6 --- /dev/null +++ b/src/components/table/components/CellTips/index.module.scss @@ -0,0 +1,53 @@ +.mark { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: transparent; +} +.tooltip { + &-container { + position: relative; + } + position: absolute; + left: 0; + top: 0; + user-select: text; + color: white; + &-inner { + position: absolute; + background-color: #333333; + font-size: $font_size; + box-shadow: 0 0 30px 0 rgba(51, 51, 51, 0.2); + border-radius: 8px; + &-content { + font-size: 24px; + font-weight: lighter; + color: white; + padding: 8px 12px; + white-space: nowrap; + } + } + &-hidden { + display: none; + } +} +.arrowIcon { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + &-top { + transform: rotate(0deg); + } + &-left { + transform: rotate(270deg); + } + &-right { + transform: rotate(90deg); + } + &-bottom { + transform: rotate(180deg); + } +} diff --git a/src/components/table/components/CellTips/index.tsx b/src/components/table/components/CellTips/index.tsx new file mode 100644 index 0000000..aaaf006 --- /dev/null +++ b/src/components/table/components/CellTips/index.tsx @@ -0,0 +1,326 @@ +import { View } from '@tarojs/components' +import Taro from '@tarojs/taro' +import { nextTick } from '@tarojs/runtime' +import classNames from 'classnames' +import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, useTransition } from 'react' +import styles from './index.module.scss' +import IconFont from '@/components/iconfont/iconfont' + +const SystemWidth = Taro.getSystemInfoSync().windowWidth + +const arrowSize = 32 + +// 由于taro自动把px编译成rpx,所以这里需要把单位处理一下 转成真正的px +const convertPx = (px: number) => { + const realPx = (px / 750) * SystemWidth + return realPx +} + +type Placement = + | 'top' + | 'top-start' + | 'top-end' + | 'right' + | 'right-start' + | 'right-end' + | 'bottom' + | 'bottom-start' + | 'bottom-end' + | 'left' + | 'left-start' + | 'left-end' + +interface ToolTipEvent { + onVisibleChange?: (visible?: boolean) => void +} + +interface ToolTipPropsType extends ToolTipEvent { + defaultVisible?: boolean + childrenNode: { + cellWidth: number + cellHeight: number + } + content: React.ReactNode + placement?: Placement + children?: React.ReactNode + customClassName?: string + customStyle?: React.CSSProperties + customContainerStyle?: React.CSSProperties + customContentStyle?: React.CSSProperties +} + +export interface ToolTipRef { + show: () => void + hide: () => void + visible: boolean +} + +const popoverStyle = { + color: '#333333', +} + +const ToolTip = (props: ToolTipPropsType, ref) => { + const id = useId() + const [, setForceUpdate] = useState({}) + const { placement = 'top-start', defaultVisible = false, onVisibleChange, children, content = '请填入提示信息', customClassName, customStyle, customContentStyle, customContainerStyle, childrenNode } = props + + const [visible, setVisible] = useState(defaultVisible) + + const onVisibleChangeRef = useRef(onVisibleChange) + onVisibleChangeRef.current = onVisibleChange + + // 暴露方法给外部 + useImperativeHandle( + ref, + () => { + return { + show: () => setVisible(true), + hide: () => setVisible(false), + visible, + } + }, + [visible], + ) + + const handleClick = useCallback(() => { + setVisible((v) => { + onVisibleChangeRef.current?.(!v) + return !v + }) + }, []) + + const handleClickMark = useCallback(() => { + setVisible(false) + onVisibleChangeRef.current?.(false) + }, []) + + const getArrowSide = useMemo(() => { + const side = placement.split('-')[0] as string // placement = 'top-start' 取 'top' + + const arrowSide = { + top: 'bottom', + right: 'left', + bottom: 'top', + left: 'right', + }[side] as string + return arrowSide + }, [placement]) + + const [viewport, setViewport] = useState({}) + const viewportRef = useRef(viewport) + viewportRef.current = viewport + + const contextRectRef = useRef(null) + + useEffect(() => { + if (visible && !contextRectRef.current) { + nextTick(() => { + const query = Taro.createSelectorQuery() + query.select(`#content-${id}`).boundingClientRect((res) => { + console.log('contextRectRef', res) + contextRectRef.current = res + setForceUpdate({}) + }).exec() + }) + } + }, [visible, contextRectRef.current]) + + useEffect(() => { + console.log('useLayoutEffect') + const query = Taro.createSelectorQuery() + query.selectViewport().scrollOffset() + query.exec((res) => { + console.log(res[0]) + console.log(res[1]) + setViewport(res[0]) + viewportRef.current = res[0] + }) + }, []) + + // 箭头 坐标 + const arrowCoords = useMemo(() => { + const contextRect = contextRectRef.current + // contextRect 没获取到之前 隐藏 + if (!contextRect) { return { top: '9999px' } } + console.log('getArrowSide==>', getArrowSide, childrenNode) + switch (getArrowSide) { + case 'bottom': // 下箭头 + return { + left: childrenNode.cellWidth / 2 - convertPx(arrowSize) / 2, + top: -convertPx(arrowSize), + right: '', + bottom: '', + } + case 'left': // 左箭头 + return { + left: childrenNode.cellWidth, + right: '', + top: childrenNode.cellHeight / 2 - convertPx(arrowSize) / 2, + bottom: '', + } + case 'top': // 上箭头 + return { + left: childrenNode.cellWidth / 2 - convertPx(arrowSize) / 2, + right: '', + top: childrenNode.cellHeight, + bottom: '', + } + case 'right': // 右箭头 + return { + left: -convertPx(arrowSize), + right: '', + top: childrenNode.cellHeight / 2 - convertPx(arrowSize) / 2, + bottom: '', + } + } + }, [getArrowSide, childrenNode, contextRectRef.current]) + + // content 坐标 + const contentCoords = useMemo(() => { + const placementSide = placement.split('-')[1] as string // placement = 'top-start' 取 'start' + console.log('placementSide', placementSide) + let coordsStyle = {} + const contextRect = contextRectRef.current + // contextRect 没获取到之前 隐藏 + if (!contextRect) { return { top: '9999px' } } + console.log('contextRect', contextRect) + switch (getArrowSide) { + case 'bottom': // 下箭头 + coordsStyle = { + bottom: convertPx(arrowSize) - 5, + top: '', + } + if (placementSide === 'start') { + return { + ...coordsStyle, + left: 0, + right: '', + } + } + else if (placementSide === 'end') { + return { + ...coordsStyle, + left: '', + right: 0, + } + } + else { + return { + ...coordsStyle, + left: `${-(contextRect?.width / 2 - childrenNode.cellWidth / 2)}px`, + right: '', + } + } + case 'left': // 左箭头 + coordsStyle = { + left: childrenNode.cellWidth + convertPx(arrowSize) - 5, + right: '', + } + if (placementSide === 'start') { + return { + ...coordsStyle, + top: 0, + bottom: '', + } + } + else if (placementSide === 'end') { + return { + ...coordsStyle, + top: '', + bottom: `${-childrenNode.cellHeight}px`, + } + } + else { + return { + ...coordsStyle, + top: `${-(contextRect.height / 2 - childrenNode.cellHeight / 2)}px`, + bottom: '', + } + } + case 'top': // 上箭头 + coordsStyle = { + top: childrenNode.cellHeight + convertPx(arrowSize) - 5, + bottom: '', + } + if (placementSide === 'start') { + return { + ...coordsStyle, + left: 0, + right: '', + } + } + else if (placementSide === 'end') { + return { + ...coordsStyle, + left: '', + right: 0, + } + } + else { + return { + ...coordsStyle, + left: `${-(contextRect?.width / 2 - childrenNode.cellWidth / 2)}px`, + right: '', + } + } + case 'right': // 右箭头 + coordsStyle = { + left: '', + right: convertPx(arrowSize) - 5, + } + if (placementSide === 'start') { + return { + ...coordsStyle, + top: 0, + bottom: '', + } + } + else if (placementSide === 'end') { + return { + ...coordsStyle, + top: '', + bottom: `${-childrenNode.cellHeight}px`, + } + } + else { + return { + ...coordsStyle, + top: `${-(contextRect.height / 2 - childrenNode.cellHeight / 2)}px`, + bottom: '', + } + } + } + }, [getArrowSide, childrenNode, contextRectRef.current]) + + const ContentComponent = useMemo(() => { + return ( + + {/* 箭头 */} + + + + {/* content */} + + {content} + + + ) + }, [content, getArrowSide, visible, arrowCoords, contentCoords]) + + return ( + <> + {/* 遮罩层 */} + + + + + {children} + + {ContentComponent} + + + + ) +} + +export default forwardRef(ToolTip) diff --git a/src/components/table/index.tsx b/src/components/table/index.tsx index fded4e3..69b0863 100644 --- a/src/components/table/index.tsx +++ b/src/components/table/index.tsx @@ -1,10 +1,13 @@ -import { ScrollView, Text, View } from '@tarojs/components' +import { RootPortal, ScrollView, Text, View } from '@tarojs/components' +import Taro from '@tarojs/taro' import classnames from 'classnames' import type { FC } from 'react' -import { useCallback, useMemo, useState } from 'react' +import React, { useCallback, useMemo, useRef, useState } from 'react' import Iconfont from '../iconfont/iconfont' import InfiniteScroll from '../infiniteScroll' import LoadMore, { LoadMoreStatus } from '../LoadMore' +import type { ToolTipRef } from '../toolTips' +import CellTips from './components/CellTips' import styles from './index.module.scss' export interface ColumnsType { @@ -14,6 +17,10 @@ export interface ColumnsType { key: string render?: (text?: string, record?: RecordType, index?: number) => React.ReactNode ellipsis?: boolean | { isEllipsis: boolean; rows: number } + showTipsConfig?: { + tips: string | ((text?: string, record?: RecordType, rowIndex?: number) => string) + isShowTips: boolean + } } export interface RecordType { @@ -32,7 +39,10 @@ export interface TablePropsType { emptyText?: string safeAreaInsetBottom?: boolean } - +// 生成唯一的cellId +const generateCellId = (row: number, col: number) => { + return `cell-${row}-${col}` +} const Table: FC = (props) => { const { columns, dataSource, onLoadMore, height = 400, moreText = '查看更多', loadingText = '加载中', noMoreText = '没有更多了', emptyText = '暂无数据', safeAreaInsetBottom = true } = props @@ -74,25 +84,72 @@ const Table: FC = (props) => { } }, [dataSource]) + const toolTipsRef = useRef(null) + // 当前的提示信息 + const currentTips = useRef<{ title: string; tips?: string; width: string }>({ title: '', tips: '', width: '' }) + // 当前的子节点的宽高 + const currentChildrenNode = useRef<{ cellWidth: number; cellHeight: number }>({ + cellWidth: 0, + cellHeight: 0, + }) + const [toolTipsStyle, setToolTipsStyle] = useState({ position: 'fixed' }) + const [, setForceUpdate] = useState({}) + const handleClickCell = (id: string, columnConfig: ColumnsType, rowSource, rowIndex: number) => { + if (toolTipsRef.current?.visible) { + toolTipsRef.current?.hide() + } + // 提示 + if (typeof columnConfig.showTipsConfig === 'object' && columnConfig.showTipsConfig.isShowTips) { + currentTips.current = { + width: columnConfig.width, + title: rowSource[columnConfig.dataIndex], + tips: typeof columnConfig.showTipsConfig?.tips === 'function' ? columnConfig.showTipsConfig?.tips(rowSource[columnConfig.dataIndex], rowSource, rowIndex) : columnConfig.showTipsConfig?.tips, + } + setForceUpdate({}) + setTimeout(() => { + const query = Taro.createSelectorQuery() + query.select(`#${id}`).boundingClientRect() + query.exec((res) => { + currentChildrenNode.current = { + cellWidth: res[0].width, + cellHeight: res[0].height, + } + setToolTipsStyle({ + position: 'fixed', + top: `${res[0].top}px`, + left: `${res[0].left}px`, + }) + toolTipsRef.current?.show() + console.log('res===》', res) + }) + }, 100) + } + } + + const hiddenChildrenToolTips = useMemo(() => { + return {currentTips.current.title} + }, [currentTips.current.title]) + const sourceContainer = () => { return ( <> {!!dataSource?.list?.length - && dataSource.list.map((source) => { + && dataSource.list.map((source, row) => { return ( - {columns.map((column, key) => { + {columns.map((column, col) => { + const cellId = generateCellId(row, col) if (column.render) { return ( - + {/* 判断表头是不是有render 有就执行render */} - {column.render(source[column.dataIndex])} + {column.render(source[column.dataIndex], source, row)} ) } else { return ( - + handleClickCell(cellId, column, source, row)}> { source[column.dataIndex] // 根据表头填数据 } @@ -122,6 +179,9 @@ const Table: FC = (props) => { {sourceContainer()} + + {hiddenChildrenToolTips} + ) } diff --git a/src/components/toolTips/index.tsx b/src/components/toolTips/index.tsx index 48bfb33..29d4e81 100644 --- a/src/components/toolTips/index.tsx +++ b/src/components/toolTips/index.tsx @@ -41,10 +41,11 @@ interface ToolTipPropsType extends ToolTipEvent { children?: React.ReactNode customClassName?: string customStyle?: React.CSSProperties + customContainerStyle?: React.CSSProperties customContentStyle?: React.CSSProperties } -interface ToolTipRef { +export interface ToolTipRef { show: () => void hide: () => void visible: boolean @@ -57,11 +58,7 @@ const popoverStyle = { const ToolTip = (props: ToolTipPropsType, ref) => { const id = useId() const [, setForceUpdate] = useState({}) - const { placement = 'top-start', defaultVisible = false, onVisibleChange, children, content = '请填入提示信息', customClassName, customStyle, customContentStyle } = props - - if (!content) { - throw new Error('tooltip: content 不能为空') - } + const { placement = 'top-start', defaultVisible = false, onVisibleChange, children, content = '请填入提示信息', customClassName, customStyle, customContentStyle, customContainerStyle } = props const [visible, setVisible] = useState(defaultVisible) @@ -318,7 +315,7 @@ const ToolTip = (props: ToolTipPropsType, ref) => { <> {/* 遮罩层 */} - + {children} diff --git a/src/pages/saleStatistic/index.config.ts b/src/pages/saleStatistic/index.config.ts index bb6f29b..1d79a3d 100644 --- a/src/pages/saleStatistic/index.config.ts +++ b/src/pages/saleStatistic/index.config.ts @@ -1,4 +1,4 @@ export default { navigationBarTitleText: '销售统计', - enablePullDownRefresh: true, + enablePullDownRefresh: false, } diff --git a/src/pages/saleStatistic/index.tsx b/src/pages/saleStatistic/index.tsx index 5178c33..cc3ab24 100644 --- a/src/pages/saleStatistic/index.tsx +++ b/src/pages/saleStatistic/index.tsx @@ -5,7 +5,7 @@ import { memo, useCallback, useEffect, useRef, useState } from 'react' import styles from './index.module.scss' import { ArrangedForm, OrderForm, PaymentAmountForm, ReturnGoodsOrderForm } from './config' import { ProductRankApi, PurchaserRankApi, SaleOrderDataFormApi, SalesmanRankApi } from '@/api/statistic/saleStatistic' -import { dataUnit, formatPriceDiv, setPriceUnit } from '@/common/format' +import { dataUnit, formatHashTag, formatPriceDiv, setPriceUnit } from '@/common/format' import { getFilterData } from '@/common/util' import Cell from '@/components/cell' import Divider from '@/components/Divider' @@ -16,7 +16,7 @@ import SelectSaleRankingIndicators from '@/components/SelectSaleRankingIndicator import SelectSaleType from '@/components/SelectSaleType' import type { ChangedValue } from '@/components/SelectTimePicker' import SelectTimePicker from '@/components/SelectTimePicker' -import type { TablePropsType } from '@/components/table' +import type { ColumnsType, TablePropsType } from '@/components/table' import Table from '@/components/table' import ToolTip from '@/components/toolTips' import IconText from '@/components/iconText' @@ -85,29 +85,33 @@ const productRankingColumns = [ key: 'index', title: '编号', dataIndex: 'index', - width: '20%', + width: '10%', }, { key: 'product_name', title: '面料名称', dataIndex: 'product_name', - width: '20%', + width: '45%', ellipsis: { isEllipsis: true, - rows: 2, + rows: 1, + }, + showTipsConfig: { + tips: (text: string) => text, + isShowTips: true, }, }, { key: 'roll', title: '匹数', dataIndex: 'roll', - width: '30%', + width: '20%', }, { key: 'sales_amount', title: '交易金额', dataIndex: 'sales_amount', - width: '30%', + width: '25%', render: (text: string) => {text}, }, ] @@ -118,13 +122,13 @@ const purchaserRankingColumns = [ key: 'index', title: '编号', dataIndex: 'index', - width: '20%', + width: '10%', }, { key: 'purchaser_name', title: '客户名称', dataIndex: 'purchaser_name', - width: '20%', + width: '45%', ellipsis: { isEllipsis: true, rows: 2, @@ -134,13 +138,13 @@ const purchaserRankingColumns = [ key: 'roll', title: '交易匹数', dataIndex: 'roll', - width: '30%', + width: '20%', }, { key: 'sales_amount', title: '交易金额', dataIndex: 'sales_amount', - width: '30%', + width: '25%', render: (text: string) => {text}, }, ] @@ -151,13 +155,13 @@ const salesmanRankingColumns = [ key: 'index', title: '编号', dataIndex: 'index', - width: '20%', + width: '10%', }, { key: 'sale_user_name', title: '业务员名称', dataIndex: 'sale_user_name', - width: '25%', + width: '45%', ellipsis: { isEllipsis: true, rows: 2, @@ -167,13 +171,13 @@ const salesmanRankingColumns = [ key: 'roll', title: '销售匹数', dataIndex: 'roll', - width: '25%', + width: '20%', }, { key: 'sales_amount', title: '销售金额', dataIndex: 'sales_amount', - width: '30%', + width: '25%', render: (text: string) => {text}, }, ] @@ -262,7 +266,7 @@ const RankingBlock = (props: RankingBlockPropsType) => { list: res.data.list.map((item, index: number) => ({ key: index, index: index + 1, - product_name: item.product_name || '--', + product_name: formatHashTag(item.product_code, item.product_name) || '--', sales_amount: `¥${priceformat(item.sales_amount)}`, roll: dataUnit(item.roll), })),