feat(ID1000830): 【临】内部小程序销售报表面料排行出补充面料编号

【【临】内部小程序销售报表面料排行出补充面料编号】 https://www.tapd.cn/53459131/prong/stories/view/1153459131001000830
This commit is contained in:
w1359774872@163.com 2022-12-23 19:44:17 +08:00
parent 913cbc9d42
commit c993fb84ef
6 changed files with 472 additions and 32 deletions

View File

@ -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);
}
}

View File

@ -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<any>({})
const viewportRef = useRef(viewport)
viewportRef.current = viewport
const contextRectRef = useRef<any>(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 (
<View className={classNames(styles.tooltip, visible ? '' : styles['tooltip-hidden'], customClassName)} style={customStyle}>
{/* 箭头 */}
<View className={classNames(styles.arrowIcon, styles[`arrowIcon-${getArrowSide}`])} style={arrowCoords}>
<IconFont name="icon-shouqi1" size={arrowSize} color={popoverStyle.color}></IconFont>
</View>
{/* content */}
<View className={styles['tooltip-inner']} id={`content-${id}`} style={{ ...contentCoords, ...customContentStyle }}>
<View className={styles['tooltip-inner-content']}>{content}</View>
</View>
</View>
)
}, [content, getArrowSide, visible, arrowCoords, contentCoords])
return (
<>
{/* 遮罩层 */}
<View className={classNames(styles.mark, visible ? '' : styles['tooltip-hidden'])} onClick={handleClickMark}></View>
<View style={customContainerStyle}>
<View className={styles['tooltip-container']}>
<View onClick={handleClick}>
{children}
</View>
{ContentComponent}
</View>
</View>
</>
)
}
export default forwardRef<ToolTipRef, ToolTipPropsType>(ToolTip)

View File

@ -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<TablePropsType> = (props) => {
const { columns, dataSource, onLoadMore, height = 400, moreText = '查看更多', loadingText = '加载中', noMoreText = '没有更多了', emptyText = '暂无数据', safeAreaInsetBottom = true } = props
@ -74,25 +84,72 @@ const Table: FC<TablePropsType> = (props) => {
}
}, [dataSource])
const toolTipsRef = useRef<ToolTipRef>(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<React.CSSProperties>({ 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 <Text id="hiddenTips" className={styles.td} style={{ color: 'transparent', width: currentChildrenNode.current.cellWidth, height: currentChildrenNode.current.cellHeight }}>{currentTips.current.title}</Text>
}, [currentTips.current.title])
const sourceContainer = () => {
return (
<>
{!!dataSource?.list?.length
&& dataSource.list.map((source) => {
&& dataSource.list.map((source, row) => {
return (
<View className={classnames(styles.tr, styles['bg-line'])} key={source.key}>
{columns.map((column, key) => {
{columns.map((column, col) => {
const cellId = generateCellId(row, col)
if (column.render) {
return (
<View key={key} className={styles.td} style={{ width: column.width }}>
<View id={cellId} key={col} className={styles.td} style={{ width: column.width }}>
{/* 判断表头是不是有render 有就执行render */}
{column.render(source[column.dataIndex])}
{column.render(source[column.dataIndex], source, row)}
</View>
)
}
else {
return (
<View key={key} className={classnames(styles.td, getColumnStyle(column))} style={{ width: column.width }}>
<View id={cellId} key={col} className={classnames(styles.td, getColumnStyle(column))} style={{ width: column.width }} onClick={() => handleClickCell(cellId, column, source, row)}>
{
source[column.dataIndex] // 根据表头填数据
}
@ -122,6 +179,9 @@ const Table: FC<TablePropsType> = (props) => {
<InfiniteScroll enableLoadMoreStatus={false} safeAreaInsetBottom={safeAreaInsetBottom}>
{sourceContainer()}
</InfiniteScroll>
<CellTips childrenNode={currentChildrenNode.current} ref={toolTipsRef} customContainerStyle={toolTipsStyle} placement="top" content={currentTips.current?.tips}>
{hiddenChildrenToolTips}
</CellTips>
</View>
)
}

View File

@ -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) => {
<>
{/* 遮罩层 */}
<View className={classNames(styles.mark, visible ? '' : styles['tooltip-hidden'])} onClick={handleClickMark}></View>
<View className={styles['tooltip-container']}>
<View className={styles['tooltip-container']} style={customContainerStyle}>
<View onClick={handleClick} id="children">
{children}
</View>

View File

@ -1,4 +1,4 @@
export default {
navigationBarTitleText: '销售统计',
enablePullDownRefresh: true,
enablePullDownRefresh: false,
}

View File

@ -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 className={styles.amount}>{text}</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 className={styles.amount}>{text}</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 className={styles.amount}>{text}</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),
})),