@ant-design/icons#FilterOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#FilterOutlined. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ActionFilterRow.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function ActionFilterRow({
    logic,
    filter,
    index,
    hideMathSelector,
    hidePropertySelector,
    singleFilter,
    showOr,
    hideFilter,
    hideRename,
    onRenameClick = () => {},
    showSeriesIndicator,
    seriesIndicatorType = 'alpha',
    horizontalUI = false,
    fullWidth = false,
    filterCount,
    customRowPrefix,
    customRowSuffix,
    rowClassName,
    propertyFilterWrapperClassName,
    stripeActionRow = true,
    hasBreakdown,
    showNestedArrow = false,
    hideDeleteBtn = false,
    actionsTaxonomicGroupTypes = [TaxonomicFilterGroupType.Events, TaxonomicFilterGroupType.Actions],
    propertiesTaxonomicGroupTypes,
    disabled = false,
    renderRow,
}: ActionFilterRowProps): JSX.Element {
    const { selectedFilter, entities, entityFilterVisible } = useValues(logic)
    const {
        updateFilter,
        selectFilter,
        updateFilterMath,
        removeLocalFilter,
        updateFilterProperty,
        setEntityFilterVisibility,
        duplicateFilter,
    } = useActions(logic)
    const { numericalPropertyNames } = useValues(propertyDefinitionsModel)
    const { actions } = useValues(actionsModel)
    const { mathDefinitions } = useValues(mathsLogic)

    const visible = typeof filter.order === 'number' ? entityFilterVisible[filter.order] : false

    let entity: BareEntity, name: string | null | undefined, value: PropertyFilterValue
    const { math, math_property: mathProperty, math_group_type_index: mathGroupTypeIndex } = filter

    const onClose = (): void => {
        removeLocalFilter({ ...filter, index })
    }
    const onMathSelect = (_: unknown, selectedMath: string): void => {
        updateFilterMath({
            ...mathTypeToApiValues(selectedMath),
            math_property: mathDefinitions[selectedMath]?.onProperty ? mathProperty ?? '$time' : undefined,
            type: filter.type,
            index,
        })
    }
    const onMathPropertySelect = (_: unknown, property: string): void => {
        updateFilterMath({
            ...filter,
            math_property: property,
            index,
        })
    }

    const dropDownCondition = Boolean(
        selectedFilter && selectedFilter?.type === filter.type && selectedFilter?.index === index
    )

    const onClick = (): void => {
        if (dropDownCondition) {
            selectFilter(null)
        } else {
            selectFilter({ ...filter, index })
        }
    }

    if (filter.type === EntityTypes.NEW_ENTITY) {
        name = null
        value = null
    } else {
        entity = (entities[filter.type] as BareEntity[])?.filter((action) => action.id === filter.id)[0] || {}
        name = entity.name || filter.name
        value = entity.id || filter.id
    }

    const orLabel = <div className="stateful-badge or width-locked">OR</div>

    const seriesIndicator =
        seriesIndicatorType === 'numeric' ? (
            <SeriesGlyph style={{ borderColor: 'var(--border)' }}>{index + 1}</SeriesGlyph>
        ) : (
            <SeriesLetter seriesIndex={index} hasBreakdown={hasBreakdown} />
        )

    const prefix = typeof customRowPrefix === 'function' ? customRowPrefix({ filter, index, onClose }) : customRowPrefix

    const filterElement = (
        <Popup
            overlay={
                <TaxonomicFilter
                    groupType={filter.type as TaxonomicFilterGroupType}
                    value={
                        filter.type === 'actions' && typeof value === 'string' ? parseInt(value) : value || undefined
                    }
                    onChange={(taxonomicGroup, changedValue, item) => {
                        updateFilter({
                            type: taxonomicFilterGroupTypeToEntityType(taxonomicGroup.type) || undefined,
                            id: `${changedValue}`,
                            name: item?.name,
                            index,
                        })
                    }}
                    onClose={() => selectFilter(null)}
                    taxonomicGroupTypes={actionsTaxonomicGroupTypes}
                />
            }
            visible={dropDownCondition}
            onClickOutside={() => selectFilter(null)}
        >
            {({ setRef }) => (
                <Button
                    data-attr={'trend-element-subject-' + index}
                    onClick={onClick}
                    block={fullWidth}
                    ref={setRef}
                    disabled={disabled}
                    style={{
                        maxWidth: '100%',
                        display: 'flex',
                        alignItems: 'center',
                        justifyContent: 'space-between',
                    }}
                >
                    <span className="text-overflow" style={{ maxWidth: '100%' }}>
                        <EntityFilterInfo filter={filter} />
                    </span>
                    <DownOutlined style={{ fontSize: 10 }} />
                </Button>
            )}
        </Popup>
    )

    const suffix = typeof customRowSuffix === 'function' ? customRowSuffix({ filter, index, onClose }) : customRowSuffix

    const propertyFiltersButton = (
        <Button
            type="link"
            onClick={() => {
                typeof filter.order === 'number' ? setEntityFilterVisibility(filter.order, !visible) : undefined
            }}
            className={`row-action-btn show-filters${visible ? ' visible' : ''}`}
            data-attr={'show-prop-filter-' + index}
            title="Show filters"
        >
            <FilterOutlined />
            {filter.properties?.length ? pluralize(filter.properties?.length, 'filter') : null}
        </Button>
    )

    const renameRowButton = (
        <Button
            type="link"
            onClick={() => {
                selectFilter(filter)
                onRenameClick()
            }}
            className={`row-action-btn show-rename`}
            data-attr={'show-prop-rename-' + index}
            title="Rename graph series"
        >
            <EditOutlined />
        </Button>
    )

    const duplicateRowButton = (
        <Button
            type="link"
            onClick={() => {
                duplicateFilter(filter)
            }}
            className={`row-action-btn show-duplicabe`}
            data-attr={'show-prop-duplicate-' + index}
            title="Duplicate graph series"
        >
            <CopyOutlined />
        </Button>
    )

    const deleteButton = (
        <Button
            type="link"
            onClick={onClose}
            className="row-action-btn delete"
            data-attr={'delete-prop-filter-' + index}
            title="Delete graph series"
        >
            <DeleteOutlined />
        </Button>
    )

    return (
        <div
            className={clsx({
                'action-row-striped': horizontalUI && stripeActionRow,
                'action-row': !horizontalUI || !stripeActionRow,
                'full-width': fullWidth,
            })}
        >
            {!horizontalUI && index > 0 && showOr && (
                <Row align="middle" style={{ marginTop: 12 }}>
                    {orLabel}
                </Row>
            )}

            <Row gutter={8} align="middle" className={`${!horizontalUI ? 'mt' : ''} ${rowClassName}`} wrap={!fullWidth}>
                {renderRow ? (
                    renderRow({
                        seriesIndicator,
                        prefix,
                        filter: filterElement,
                        suffix,
                        propertyFiltersButton: propertyFiltersButton,
                        renameRowButton,
                        deleteButton,
                        orLabel,
                    })
                ) : (
                    <>
                        {!hideDeleteBtn && horizontalUI && !singleFilter && filterCount > 1 && (
                            <Col>
                                <Button
                                    type="link"
                                    onClick={onClose}
                                    className="row-action-btn delete"
                                    title="Remove graph series"
                                    danger
                                    icon={<CloseSquareOutlined />}
                                />
                            </Col>
                        )}
                        {showSeriesIndicator && <Col className="action-row-letter">{seriesIndicator}</Col>}
                        {customRowPrefix !== undefined ? (
                            <Col>{prefix}</Col>
                        ) : (
                            <>{horizontalUI && <Col>Showing</Col>}</>
                        )}
                        <Col
                            className="column-filter"
                            style={fullWidth ? {} : { maxWidth: `calc(${hideMathSelector ? '100' : '50'}% - 16px)` }}
                            flex={fullWidth ? 'auto' : undefined}
                        >
                            {filterElement}
                        </Col>
                        {customRowSuffix !== undefined && <Col className="column-row-suffix">{suffix}</Col>}
                        {!hideMathSelector && (
                            <>
                                {horizontalUI && <Col>counted by</Col>}
                                <Col style={{ maxWidth: `calc(50% - 16px${showSeriesIndicator ? ' - 32px' : ''})` }}>
                                    <MathSelector
                                        math={math}
                                        mathGroupTypeIndex={mathGroupTypeIndex}
                                        index={index}
                                        onMathSelect={onMathSelect}
                                        areEventPropertiesNumericalAvailable={!!numericalPropertyNames.length}
                                        style={{ maxWidth: '100%', width: 'initial' }}
                                    />
                                </Col>
                                {mathDefinitions[math || '']?.onProperty && (
                                    <>
                                        {horizontalUI && <Col>on property</Col>}
                                        <Col
                                            style={{
                                                maxWidth: `calc(50% - 16px${showSeriesIndicator ? ' - 32px' : ''})`,
                                            }}
                                        >
                                            <MathPropertySelector
                                                name={name}
                                                math={math}
                                                mathProperty={mathProperty}
                                                index={index}
                                                onMathPropertySelect={onMathPropertySelect}
                                                properties={numericalPropertyNames}
                                                horizontalUI={horizontalUI}
                                            />
                                        </Col>
                                    </>
                                )}
                            </>
                        )}
                        {(horizontalUI || fullWidth) && !hideFilter && <Col>{propertyFiltersButton}</Col>}
                        {(horizontalUI || fullWidth) && !hideRename && <Col>{renameRowButton}</Col>}
                        {(horizontalUI || fullWidth) && !hideFilter && !singleFilter && <Col>{duplicateRowButton}</Col>}
                        {!hideDeleteBtn && !horizontalUI && !singleFilter && (
                            <Col className="column-delete-btn">{deleteButton}</Col>
                        )}
                        {horizontalUI && filterCount > 1 && index < filterCount - 1 && showOr && orLabel}
                    </>
                )}
            </Row>
            {(!hidePropertySelector || (filter.properties && filter.properties.length > 0)) &&
                !horizontalUI &&
                !fullWidth && (
                    <div style={{ paddingTop: 6 }}>
                        <span style={{ color: '#C4C4C4', fontSize: 18, paddingLeft: 6, paddingRight: 2 }}>&#8627;</span>
                        <Button
                            className="ant-btn-md"
                            onClick={() =>
                                typeof filter.order === 'number'
                                    ? setEntityFilterVisibility(filter.order, !visible)
                                    : undefined
                            }
                            data-attr={'show-prop-filter-' + index}
                        >
                            {determineFilterLabel(visible, filter)}
                        </Button>
                    </div>
                )}

            {visible && (
                <div
                    className={
                        propertyFilterWrapperClassName
                            ? `mr property-filter-wrapper ${propertyFilterWrapperClassName}`
                            : 'mr property-filter-wrapper'
                    }
                >
                    <PropertyFilters
                        pageKey={`${index}-${value}-filter`}
                        propertyFilters={filter.properties}
                        onChange={(properties) => updateFilterProperty({ properties, index })}
                        disablePopover={horizontalUI}
                        style={{ marginBottom: 0 }}
                        showNestedArrow={showNestedArrow}
                        taxonomicGroupTypes={propertiesTaxonomicGroupTypes}
                        eventNames={
                            filter.type === TaxonomicFilterGroupType.Events && filter.id
                                ? [String(filter.id)]
                                : filter.type === TaxonomicFilterGroupType.Actions && filter.id
                                ? getEventNamesForAction(parseInt(String(filter.id)), actions)
                                : []
                        }
                    />
                </div>
            )}
        </div>
    )
}
Example #2
Source File: SessionRecordingsTable.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function SessionRecordingsTable({ personUUID, isPersonPage = false }: SessionRecordingsTableProps): JSX.Element {
    const sessionRecordingsTableLogicInstance = sessionRecordingsTableLogic({ personUUID })
    const {
        sessionRecordings,
        sessionRecordingsResponseLoading,
        sessionRecordingId,
        entityFilters,
        propertyFilters,
        hasNext,
        hasPrev,
        fromDate,
        toDate,
        durationFilter,
        showFilters,
    } = useValues(sessionRecordingsTableLogicInstance)
    const {
        openSessionPlayer,
        closeSessionPlayer,
        setEntityFilters,
        setPropertyFilters,
        loadNext,
        loadPrev,
        setDateRange,
        setDurationFilter,
        enableFilter,
    } = useActions(sessionRecordingsTableLogicInstance)
    const { preflight } = useValues(preflightLogic)

    const columns: LemonTableColumns<SessionRecordingType> = [
        {
            title: 'Start time',
            render: function RenderStartTime(_: any, sessionRecording: SessionRecordingType) {
                return <TZLabel time={sessionRecording.start_time} formatString="MMMM DD, YYYY h:mm" />
            },
        },
        {
            title: 'Duration',
            render: function RenderDuration(_: any, sessionRecording: SessionRecordingType) {
                return <span>{humanFriendlyDuration(sessionRecording.recording_duration)}</span>
            },
        },
        {
            title: 'Person',
            key: 'person',
            render: function RenderPersonLink(_: any, sessionRecording: SessionRecordingType) {
                return <PersonHeader withIcon person={sessionRecording.person} />
            },
        },

        {
            render: function RenderPlayButton(_: any, sessionRecording: SessionRecordingType) {
                return (
                    <div className="play-button-container">
                        <Button
                            className={sessionRecording.viewed ? 'play-button viewed' : 'play-button'}
                            data-attr="session-recordings-button"
                            icon={<PlayCircleOutlined />}
                        >
                            Watch recording
                        </Button>
                    </div>
                )
            },
        },
    ]
    return (
        <div className="session-recordings-table" data-attr="session-recordings-table">
            <Row className="filter-row">
                <div className="filter-container" style={{ display: showFilters ? undefined : 'none' }}>
                    <div>
                        <Typography.Text strong>
                            {`Filter by events and actions `}
                            <Tooltip title="Show recordings where all of the events or actions listed below happen.">
                                <InfoCircleOutlined className="info-icon" />
                            </Tooltip>
                        </Typography.Text>
                        <ActionFilter
                            fullWidth={true}
                            filters={entityFilters}
                            setFilters={(payload) => {
                                setEntityFilters(payload)
                            }}
                            typeKey={isPersonPage ? `person-${personUUID}` : 'session-recordings'}
                            hideMathSelector={true}
                            buttonCopy="Add another filter"
                            horizontalUI
                            stripeActionRow={false}
                            propertyFilterWrapperClassName="session-recording-action-property-filter"
                            customRowPrefix=""
                            hideRename
                            showOr
                            renderRow={(props) => <FilterRow {...props} />}
                            showNestedArrow={false}
                            actionsTaxonomicGroupTypes={[
                                TaxonomicFilterGroupType.Actions,
                                TaxonomicFilterGroupType.Events,
                            ]}
                            propertiesTaxonomicGroupTypes={[
                                TaxonomicFilterGroupType.EventProperties,
                                TaxonomicFilterGroupType.Elements,
                            ]}
                        />
                    </div>
                    {!isPersonPage && preflight?.is_clickhouse_enabled && (
                        <div className="mt-2">
                            <Typography.Text strong>
                                {`Filter by persons and cohorts `}
                                <Tooltip title="Show recordings by persons who match the set criteria">
                                    <InfoCircleOutlined className="info-icon" />
                                </Tooltip>
                            </Typography.Text>
                            <PropertyFilters
                                popoverPlacement="bottomRight"
                                pageKey={isPersonPage ? `person-${personUUID}` : 'session-recordings'}
                                taxonomicGroupTypes={[
                                    TaxonomicFilterGroupType.PersonProperties,
                                    TaxonomicFilterGroupType.Cohorts,
                                ]}
                                propertyFilters={propertyFilters}
                                onChange={(properties) => {
                                    setPropertyFilters(properties)
                                }}
                            />
                        </div>
                    )}
                </div>
                <Button
                    style={{ display: showFilters ? 'none' : undefined }}
                    onClick={() => {
                        enableFilter()
                        if (isPersonPage) {
                            const entityFilterButtons = document.querySelectorAll('.entity-filter-row button')
                            if (entityFilterButtons.length > 0) {
                                ;(entityFilterButtons[0] as HTMLElement).click()
                            }
                        }
                    }}
                >
                    <FilterOutlined /> Filter recordings
                </Button>

                <Row className="time-filter-row">
                    <Row className="time-filter">
                        <DateFilter
                            makeLabel={(key) => (
                                <>
                                    <CalendarOutlined />
                                    <span> {key}</span>
                                </>
                            )}
                            defaultValue="Last 7 days"
                            bordered={true}
                            dateFrom={fromDate ?? undefined}
                            dateTo={toDate ?? undefined}
                            onChange={(changedDateFrom, changedDateTo) => {
                                setDateRange(changedDateFrom, changedDateTo)
                            }}
                            dateOptions={{
                                Custom: { values: [] },
                                'Last 24 hours': { values: ['-24h'] },
                                'Last 7 days': { values: ['-7d'] },
                                'Last 21 days': { values: ['-21d'] },
                            }}
                        />
                    </Row>
                    <Row className="time-filter">
                        <Typography.Text className="filter-label">Duration</Typography.Text>
                        <DurationFilter
                            onChange={(newFilter) => {
                                setDurationFilter(newFilter)
                            }}
                            initialFilter={durationFilter}
                            pageKey={isPersonPage ? `person-${personUUID}` : 'session-recordings'}
                        />
                    </Row>
                </Row>
            </Row>

            <LemonTable
                dataSource={sessionRecordings}
                columns={columns}
                loading={sessionRecordingsResponseLoading}
                onRow={(sessionRecording) => ({
                    onClick: (e) => {
                        // Lets the link to the person open the person's page and not the session recording
                        if (!(e.target as HTMLElement).closest('a')) {
                            openSessionPlayer(sessionRecording.id, RecordingWatchedSource.RecordingsList)
                        }
                    },
                })}
                rowClassName="cursor-pointer"
                data-attr="session-recording-table"
            />
            {(hasPrev || hasNext) && (
                <Row className="pagination-control">
                    <Button
                        type="link"
                        disabled={!hasPrev}
                        onClick={() => {
                            loadPrev()
                            window.scrollTo(0, 0)
                        }}
                    >
                        <LeftOutlined /> Previous
                    </Button>
                    <Button
                        type="link"
                        disabled={!hasNext}
                        onClick={() => {
                            loadNext()
                            window.scrollTo(0, 0)
                        }}
                    >
                        Next <RightOutlined />
                    </Button>
                </Row>
            )}
            <div style={{ marginBottom: 64 }} />
            {!!sessionRecordingId && <SessionPlayerDrawer isPersonPage={isPersonPage} onClose={closeSessionPlayer} />}
        </div>
    )
}
Example #3
Source File: Icon.tsx    From html2sketch with MIT License 4 votes vote down vote up
IconSymbol: FC = () => {
  return (
    <Row>
      {/*<CaretUpOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
      {/*/>*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
      {/*/>*/}
      {/*<StepBackwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      {/*<StepForwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      <StepForwardOutlined />
      <ShrinkOutlined />
      <ArrowsAltOutlined />
      <DownOutlined />
      <UpOutlined />
      <LeftOutlined />
      <RightOutlined />
      <CaretUpOutlined />
      <CaretDownOutlined />
      <CaretLeftOutlined />
      <CaretRightOutlined />
      <VerticalAlignTopOutlined />
      <RollbackOutlined />
      <FastBackwardOutlined />
      <FastForwardOutlined />
      <DoubleRightOutlined />
      <DoubleLeftOutlined />
      <VerticalLeftOutlined />
      <VerticalRightOutlined />
      <VerticalAlignMiddleOutlined />
      <VerticalAlignBottomOutlined />
      <ForwardOutlined />
      <BackwardOutlined />
      <EnterOutlined />
      <RetweetOutlined />
      <SwapOutlined />
      <SwapLeftOutlined />
      <SwapRightOutlined />
      <ArrowUpOutlined />
      <ArrowDownOutlined />
      <ArrowLeftOutlined />
      <ArrowRightOutlined />
      <LoginOutlined />
      <LogoutOutlined />
      <MenuFoldOutlined />
      <MenuUnfoldOutlined />
      <BorderBottomOutlined />
      <BorderHorizontalOutlined />
      <BorderInnerOutlined />
      <BorderOuterOutlined />
      <BorderLeftOutlined />
      <BorderRightOutlined />
      <BorderTopOutlined />
      <BorderVerticleOutlined />
      <PicCenterOutlined />
      <PicLeftOutlined />
      <PicRightOutlined />
      <RadiusBottomleftOutlined />
      <RadiusBottomrightOutlined />
      <RadiusUpleftOutlined />
      <RadiusUprightOutlined />
      <FullscreenOutlined />
      <FullscreenExitOutlined />
      <QuestionOutlined />
      <PauseOutlined />
      <MinusOutlined />
      <PauseCircleOutlined />
      <InfoOutlined />
      <CloseOutlined />
      <ExclamationOutlined />
      <CheckOutlined />
      <WarningOutlined />
      <IssuesCloseOutlined />
      <StopOutlined />
      <EditOutlined />
      <CopyOutlined />
      <ScissorOutlined />
      <DeleteOutlined />
      <SnippetsOutlined />
      <DiffOutlined />
      <HighlightOutlined />
      <AlignCenterOutlined />
      <AlignLeftOutlined />
      <AlignRightOutlined />
      <BgColorsOutlined />
      <BoldOutlined />
      <ItalicOutlined />
      <UnderlineOutlined />
      <StrikethroughOutlined />
      <RedoOutlined />
      <UndoOutlined />
      <ZoomInOutlined />
      <ZoomOutOutlined />
      <FontColorsOutlined />
      <FontSizeOutlined />
      <LineHeightOutlined />
      <SortAscendingOutlined />
      <SortDescendingOutlined />
      <DragOutlined />
      <OrderedListOutlined />
      <UnorderedListOutlined />
      <RadiusSettingOutlined />
      <ColumnWidthOutlined />
      <ColumnHeightOutlined />
      <AreaChartOutlined />
      <PieChartOutlined />
      <BarChartOutlined />
      <DotChartOutlined />
      <LineChartOutlined />
      <RadarChartOutlined />
      <HeatMapOutlined />
      <FallOutlined />
      <RiseOutlined />
      <StockOutlined />
      <BoxPlotOutlined />
      <FundOutlined />
      <SlidersOutlined />
      <AndroidOutlined />
      <AppleOutlined />
      <WindowsOutlined />
      <IeOutlined />
      <ChromeOutlined />
      <GithubOutlined />
      <AliwangwangOutlined />
      <DingdingOutlined />
      <WeiboSquareOutlined />
      <WeiboCircleOutlined />
      <TaobaoCircleOutlined />
      <Html5Outlined />
      <WeiboOutlined />
      <TwitterOutlined />
      <WechatOutlined />
      <AlipayCircleOutlined />
      <TaobaoOutlined />
      <SkypeOutlined />
      <FacebookOutlined />
      <CodepenOutlined />
      <CodeSandboxOutlined />
      <AmazonOutlined />
      <GoogleOutlined />
      <AlipayOutlined />
      <AntDesignOutlined />
      <AntCloudOutlined />
      <ZhihuOutlined />
      <SlackOutlined />
      <SlackSquareOutlined />
      <BehanceSquareOutlined />
      <DribbbleOutlined />
      <DribbbleSquareOutlined />
      <InstagramOutlined />
      <YuqueOutlined />
      <AlibabaOutlined />
      <YahooOutlined />
      <RedditOutlined />
      <SketchOutlined />
      <AccountBookOutlined />
      <AlertOutlined />
      <ApartmentOutlined />
      <ApiOutlined />
      <QqOutlined />
      <MediumWorkmarkOutlined />
      <GitlabOutlined />
      <MediumOutlined />
      <GooglePlusOutlined />
      <AppstoreAddOutlined />
      <AppstoreOutlined />
      <AudioOutlined />
      <AudioMutedOutlined />
      <AuditOutlined />
      <BankOutlined />
      <BarcodeOutlined />
      <BarsOutlined />
      <BellOutlined />
      <BlockOutlined />
      <BookOutlined />
      <BorderOutlined />
      <BranchesOutlined />
      <BuildOutlined />
      <BulbOutlined />
      <CalculatorOutlined />
      <CalendarOutlined />
      <CameraOutlined />
      <CarOutlined />
      <CarryOutOutlined />
      <CiCircleOutlined />
      <CiOutlined />
      <CloudOutlined />
      <ClearOutlined />
      <ClusterOutlined />
      <CodeOutlined />
      <CoffeeOutlined />
      <CompassOutlined />
      <CompressOutlined />
      <ContactsOutlined />
      <ContainerOutlined />
      <ControlOutlined />
      <CopyrightCircleOutlined />
      <CopyrightOutlined />
      <CreditCardOutlined />
      <CrownOutlined />
      <CustomerServiceOutlined />
      <DashboardOutlined />
      <DatabaseOutlined />
      <DeleteColumnOutlined />
      <DeleteRowOutlined />
      <DisconnectOutlined />
      <DislikeOutlined />
      <DollarCircleOutlined />
      <DollarOutlined />
      <DownloadOutlined />
      <EllipsisOutlined />
      <EnvironmentOutlined />
      <EuroCircleOutlined />
      <EuroOutlined />
      <ExceptionOutlined />
      <ExpandAltOutlined />
      <ExpandOutlined />
      <ExperimentOutlined />
      <ExportOutlined />
      <EyeOutlined />
      <FieldBinaryOutlined />
      <FieldNumberOutlined />
      <FieldStringOutlined />
      <DesktopOutlined />
      <DingtalkOutlined />
      <FileAddOutlined />
      <FileDoneOutlined />
      <FileExcelOutlined />
      <FileExclamationOutlined />
      <FileOutlined />
      <FileImageOutlined />
      <FileJpgOutlined />
      <FileMarkdownOutlined />
      <FilePdfOutlined />
      <FilePptOutlined />
      <FileProtectOutlined />
      <FileSearchOutlined />
      <FileSyncOutlined />
      <FileTextOutlined />
      <FileUnknownOutlined />
      <FileWordOutlined />
      <FilterOutlined />
      <FireOutlined />
      <FlagOutlined />
      <FolderAddOutlined />
      <FolderOutlined />
      <FolderOpenOutlined />
      <ForkOutlined />
      <FormatPainterOutlined />
      <FrownOutlined />
      <FunctionOutlined />
      <FunnelPlotOutlined />
      <GatewayOutlined />
      <GifOutlined />
      <GiftOutlined />
      <GlobalOutlined />
      <GoldOutlined />
      <GroupOutlined />
      <HddOutlined />
      <HeartOutlined />
      <HistoryOutlined />
      <HomeOutlined />
      <HourglassOutlined />
      <IdcardOutlined />
      <ImportOutlined />
      <InboxOutlined />
      <InsertRowAboveOutlined />
      <InsertRowBelowOutlined />
      <InsertRowLeftOutlined />
      <InsertRowRightOutlined />
      <InsuranceOutlined />
      <InteractionOutlined />
      <KeyOutlined />
      <LaptopOutlined />
      <LayoutOutlined />
      <LikeOutlined />
      <LineOutlined />
      <LinkOutlined />
      <Loading3QuartersOutlined />
      <LoadingOutlined />
      <LockOutlined />
      <MailOutlined />
      <ManOutlined />
      <MedicineBoxOutlined />
      <MehOutlined />
      <MenuOutlined />
      <MergeCellsOutlined />
      <MessageOutlined />
      <MobileOutlined />
      <MoneyCollectOutlined />
      <MonitorOutlined />
      <MoreOutlined />
      <NodeCollapseOutlined />
      <NodeExpandOutlined />
      <NodeIndexOutlined />
      <NotificationOutlined />
      <NumberOutlined />
      <PaperClipOutlined />
      <PartitionOutlined />
      <PayCircleOutlined />
      <PercentageOutlined />
      <PhoneOutlined />
      <PictureOutlined />
      <PoundCircleOutlined />
      <PoundOutlined />
      <PoweroffOutlined />
      <PrinterOutlined />
      <ProfileOutlined />
      <ProjectOutlined />
      <PropertySafetyOutlined />
      <PullRequestOutlined />
      <PushpinOutlined />
      <QrcodeOutlined />
      <ReadOutlined />
      <ReconciliationOutlined />
      <RedEnvelopeOutlined />
      <ReloadOutlined />
      <RestOutlined />
      <RobotOutlined />
      <RocketOutlined />
      <SafetyCertificateOutlined />
      <SafetyOutlined />
      <ScanOutlined />
      <ScheduleOutlined />
      <SearchOutlined />
      <SecurityScanOutlined />
      <SelectOutlined />
      <SendOutlined />
      <SettingOutlined />
      <ShakeOutlined />
      <ShareAltOutlined />
      <ShopOutlined />
      <ShoppingCartOutlined />
      <ShoppingOutlined />
      <SisternodeOutlined />
      <SkinOutlined />
      <SmileOutlined />
      <SolutionOutlined />
      <SoundOutlined />
      <SplitCellsOutlined />
      <StarOutlined />
      <SubnodeOutlined />
      <SyncOutlined />
      <TableOutlined />
      <TabletOutlined />
      <TagOutlined />
      <TagsOutlined />
      <TeamOutlined />
      <ThunderboltOutlined />
      <ToTopOutlined />
      <ToolOutlined />
      <TrademarkCircleOutlined />
      <TrademarkOutlined />
      <TransactionOutlined />
      <TrophyOutlined />
      <UngroupOutlined />
      <UnlockOutlined />
      <UploadOutlined />
      <UsbOutlined />
      <UserAddOutlined />
      <UserDeleteOutlined />
      <UserOutlined />
      <UserSwitchOutlined />
      <UsergroupAddOutlined />
      <UsergroupDeleteOutlined />
      <VideoCameraOutlined />
      <WalletOutlined />
      <WifiOutlined />
      <BorderlessTableOutlined />
      <WomanOutlined />
      <BehanceOutlined />
      <DropboxOutlined />
      <DeploymentUnitOutlined />
      <UpCircleOutlined />
      <DownCircleOutlined />
      <LeftCircleOutlined />
      <RightCircleOutlined />
      <UpSquareOutlined />
      <DownSquareOutlined />
      <LeftSquareOutlined />
      <RightSquareOutlined />
      <PlayCircleOutlined />
      <QuestionCircleOutlined />
      <PlusCircleOutlined />
      <PlusSquareOutlined />
      <MinusSquareOutlined />
      <MinusCircleOutlined />
      <InfoCircleOutlined />
      <ExclamationCircleOutlined />
      <CloseCircleOutlined />
      <CloseSquareOutlined />
      <CheckCircleOutlined />
      <CheckSquareOutlined />
      <ClockCircleOutlined />
      <FormOutlined />
      <DashOutlined />
      <SmallDashOutlined />
      <YoutubeOutlined />
      <CodepenCircleOutlined />
      <AliyunOutlined />
      <PlusOutlined />
      <LinkedinOutlined />
      <AimOutlined />
      <BugOutlined />
      <CloudDownloadOutlined />
      <CloudServerOutlined />
      <CloudSyncOutlined />
      <CloudUploadOutlined />
      <CommentOutlined />
      <ConsoleSqlOutlined />
      <EyeInvisibleOutlined />
      <FileGifOutlined />
      <DeliveredProcedureOutlined />
      <FieldTimeOutlined />
      <FileZipOutlined />
      <FolderViewOutlined />
      <FundProjectionScreenOutlined />
      <FundViewOutlined />
      <MacCommandOutlined />
      <PlaySquareOutlined />
      <OneToOneOutlined />
      <RotateLeftOutlined />
      <RotateRightOutlined />
      <SaveOutlined />
      <SwitcherOutlined />
      <TranslationOutlined />
      <VerifiedOutlined />
      <VideoCameraAddOutlined />
      <WhatsAppOutlined />

      {/*</Col>*/}
    </Row>
  );
}
Example #4
Source File: index.tsx    From imove with MIT License 4 votes vote down vote up
MyConsole: React.FC = () => {
  const [filter, setFilter] = useState('');
  const [level, setLevel] = useState('all');
  const [logList, setLogList] = useState<ILog[]>([]);
  const cache = useRef<{ allLogs: ILog[] }>({ allLogs: [] });

  useEffect(() => {
    hijackConsole();
    return () => {
      resetConsole();
    };
  }, []);

  useEffect(() => {
    const filteredLogs = cache.current.allLogs
      .filter((log) => {
        if (level === 'all') {
          return true;
        } else {
          return log.type === level;
        }
      })
      .filter((log) => {
        return log.strVal.indexOf(filter) > -1;
      });
    setLogList(filteredLogs);
  }, [filter, level]);

  const hijackConsole = () => {
    Object.keys(hijackMap).forEach((method) => {
      // @ts-ignore
      window.console[method] = (...args: any[]) => {
        hijackMap[method].originMethod(...args);
        cache.current.allLogs = cache.current.allLogs.concat({
          type: method,
          data: args,
          strVal: Helper.getArgsToString(args),
        });
        setLogList(cache.current.allLogs);
      };
    });
  };

  const resetConsole = () => {
    Object.keys(hijackMap).forEach((method) => {
      // @ts-ignore
      window.console[method] = hijackMap[method].originMethod;
    });
  };

  const onChangeFilter = useCallback((evt: ChangeEvent<HTMLInputElement>) => {
    setFilter(evt.target.value);
  }, []);

  const onChangeLevel = useCallback((level: string) => {
    setLevel(level);
  }, []);

  const onClickClear = useCallback(() => {
    setLogList([]);
    cache.current.allLogs = [];
  }, []);

  const renderToolBar = () => {
    return (
      <div className={styles.toolBar}>
        <Input
          allowClear={true}
          placeholder={'过滤'}
          prefix={<FilterOutlined />}
          onChange={onChangeFilter}
        />
        <Select
          className={styles.levels}
          defaultValue={'all'}
          onChange={onChangeLevel}
        >
          {['all', 'info', 'warn', 'error', 'debug'].map((method) => (
            <Select.Option key={method} value={method}>
              {method}
            </Select.Option>
          ))}
        </Select>
        <Button type={'link'} onClick={onClickClear}>
          <ClearOutlined /> 清空
        </Button>
      </div>
    );
  };

  const renderLogPanel = (logList: ILog[]) => {
    return (
      <div className={styles.logPanel}>
        {logList.map((log: ILog, index: number) => (
          <LogLine key={index} {...log} />
        ))}
      </div>
    );
  };

  return (
    <div className={styles.container}>
      <Tabs type={'card'} tabBarExtraContent={renderToolBar()}>
        <Tabs.TabPane tab={'控制台'} key={'log'}>
          {renderLogPanel(logList)}
        </Tabs.TabPane>
      </Tabs>
    </div>
  );
}
Example #5
Source File: monitor.tsx    From leek with Apache License 2.0 4 votes vote down vote up
MonitorPage = () => {
  const service = new StatsService();
  const { currentTheme } = useThemeSwitcher();
  const [loading, setLoading] = useState<boolean>();
  const [totalHits, setTotalHits] = useState<number>(0);
  const [statesDistribution, setStatesDistribution] = useState<any>([]);
  const [queuesDistribution, setQueuesDistribution] = useState<any>([]);
  const [topExecutions, setTopExecutions] = useState<any>([]);
  const [topSlow, setTopSlow] = useState<any>([]);
  const [tasksOverTimeDistribution, setTasksOverTimeDistribution] =
    useState<any>([]);

  // Filters
  const { currentApp, currentEnv } = useApplication();
  const [filters, setFilters] = useState<any>();
  const [timeDistributionTSType, setTimeDistributionTSType] =
    useState<any>("timestamp");
  const [timeFilters, setTimeFilters] = useState<any>({
    timestamp_type: "timestamp",
    interval_type: "past",
    offset: 900000,
  });

  function handleFilterChange(values) {
    setFilters(values);
  }

  useEffect(() => {
    if (!currentApp) return;
    let allFilters = {
      ...filters,
      ...timeFilters,
    };
    setLoading(true);
    service
      .charts(
        currentApp,
        currentEnv,
        "desc",
        allFilters,
        timeDistributionTSType
      )
      .then(handleAPIResponse)
      .then((result: any) => {
        setStatesDistribution(result.aggregations.statesDistribution.buckets);
        let totalInQueues = 0;
        setQueuesDistribution(
          result.aggregations.queuesDistribution.buckets.map(
            ({ key, doc_count }) => {
              totalInQueues += doc_count;
              return { id: key, label: key, value: doc_count };
            }
          )
        );
        let tasksDistribution =
          result.aggregations.tasksDistribution.buckets.map(
            ({ key, statesDistribution, runtimeDistribution, doc_count }) => {
              let tasksStatesSeries = {
                QUEUED: 0,
                RECEIVED: 0,
                STARTED: 0,
                SUCCEEDED: 0,
                FAILED: 0,
                REJECTED: 0,
                REVOKED: 0,
                RETRY: 0,
                RECOVERED: 0,
                CRITICAL: 0,
              };
              const states = statesDistribution.buckets.reduce(
                (result, item) => {
                  result[item.key] = item.doc_count;
                  return result;
                },
                tasksStatesSeries
              );
              return {
                id: key,
                runtime: runtimeDistribution.value,
                executions: doc_count,
                ...states,
              };
            }
          );
        setTopExecutions(tasksDistribution.slice(0, 5));
        setTopSlow(
          [...tasksDistribution]
            .sort(function (a, b) {
              return b.runtime - a.runtime;
            })
            .slice(0, 5)
            .filter((task) => {
              return task.runtime;
            })
        );
        setTasksOverTimeDistribution([
          {
            id: "tasks",
            data: result.aggregations.timeDistribution.buckets.map(
              ({ key, doc_count }) => ({
                x: moment(key).format("YYYY-MM-DD HH:mm:ss"),
                y: doc_count,
              })
            ),
          },
        ]);
        setTotalHits(totalInQueues);
      }, handleAPIError)
      .catch(handleAPIError)
      .finally(() => setLoading(false));
  }, [currentApp, currentEnv, filters, timeFilters, timeDistributionTSType]);

  return (
    <>
      <Helmet>
        <html lang="en" />
        <title>Monitor</title>
        <meta name="description" content="Tasks monitor" />
        <meta name="keywords" content="celery, monitor" />
      </Helmet>

      <Row style={{ marginBottom: "16px" }} gutter={[12, 12]}>
        <Col xxl={5} xl={6} md={7} lg={8} sm={24} xs={24}>
          <AttributesFilter
            onFilter={handleFilterChange}
            filters={timeFilters}
          />
        </Col>
        <Col xxl={19} xl={18} md={17} lg={16} sm={24} xs={24}>
          <Card size="small">
            <Row
              align="middle"
              style={{ textAlign: "center" }}
              justify="space-between"
            >
              <Col>
                <TimeFilter
                  timeFilter={timeFilters}
                  onTimeFilterChange={setTimeFilters}
                />
              </Col>
              <Col>
                <Statistic
                  title="Total Filtered"
                  value={totalHits}
                  prefix={<FilterOutlined />}
                />
              </Col>
            </Row>
          </Card>
          <Row style={{ width: "100%", marginTop: 13 }} gutter={[10, 0]}>
            <Col span={12}>
              <Card
                bodyStyle={{
                  paddingBottom: 0,
                  paddingRight: 0,
                  paddingLeft: 0,
                }}
                size="small"
                style={{ width: "100%" }}
                title="States distribution"
              >
                <Row style={{ height: "400px" }}>
                  <LeekPie data={statesDistribution} theme={currentTheme} />
                </Row>
              </Card>
            </Col>
            <Col span={12}>
              <Card
                bodyStyle={{
                  paddingBottom: 0,
                  paddingRight: 0,
                  paddingLeft: 0,
                }}
                size="small"
                style={{ width: "100%" }}
                title="Queues distribution"
              >
                <Row style={{ height: "400px" }}>
                  <LeekWaffle
                    total={totalHits}
                    data={queuesDistribution}
                    theme={currentTheme}
                  />
                </Row>
              </Card>
            </Col>
          </Row>
          <Row justify="center" style={{ width: "100%", marginTop: 13 }}>
            <Card
              bodyStyle={{ paddingBottom: 0, paddingRight: 0, paddingLeft: 0 }}
              size="small"
              style={{ width: "100%" }}
              title="Top 5 Executed Tasks"
            >
              <Row style={{ height: "400px" }}>
                <LeekBar
                  data={topExecutions}
                  keys={StatesKeys}
                  theme={currentTheme}
                />
              </Row>
            </Card>
          </Row>
          <Row justify="center" style={{ width: "100%", marginTop: 13 }}>
            <Card
              bodyStyle={{ paddingBottom: 0, paddingRight: 0, paddingLeft: 0 }}
              size="small"
              style={{ width: "100%" }}
              title="Tasks over time distribution"
              extra={[
                <Select
                  key="timestamp"
                  defaultValue="timestamp"
                  dropdownMatchSelectWidth
                  style={{ width: 115 }}
                  size="small"
                  onChange={(type) => setTimeDistributionTSType(type)}
                >
                  <Option value="timestamp">Seen</Option>
                  <Option value="queued_at">Queued</Option>
                  <Option value="received_at">Received</Option>
                  <Option value="started_at">Started</Option>
                  <Option value="succeeded_at">Succeeded</Option>
                  <Option value="failed_at">Failed</Option>
                  <Option value="retried_at">Retried</Option>
                  <Option value="rejected_at">Rejected</Option>
                  <Option value="revoked_at">Revoked</Option>
                  <Option value="eta">ETA</Option>
                  <Option value="expires">Expires</Option>
                </Select>,
              ]}
            >
              <Row style={{ height: "400px" }}>
                <LeekLine
                  data={tasksOverTimeDistribution}
                  theme={currentTheme}
                />
              </Row>
            </Card>
          </Row>
          <Row justify="center" style={{ width: "100%", marginTop: 13 }}>
            <Card
              bodyStyle={{ paddingBottom: 0, paddingRight: 0, paddingLeft: 0 }}
              size="small"
              style={{ width: "100%" }}
              title={
                <>
                  Top 5 Slow <TaskState state={"SUCCEEDED"} />
                  <TaskState state={"RECOVERED"} />
                  Tasks
                </>
              }
            >
              <Row style={{ height: "400px" }}>
                <LeekVerticalBar
                  data={topSlow}
                  keys={["runtime"]}
                  color="yellow_orange_red"
                  theme={currentTheme}
                />
              </Row>
            </Card>
          </Row>
        </Col>
      </Row>
    </>
  );
}
Example #6
Source File: BasicTable.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
// 泛型函数组件 https://juejin.im/post/5cd7f2c4e51d453a7d63b715#heading-7
function BasicTable<T>(props: IBasicTableProps<T>) {
  const prefixCls = `${props.prefixCls || 'dantd'}-table`;
  const filterType = props.filterType || 'half';
  const tmpDataSource = props.dataSource || ([] as T[]);
  const t = intlZhMap;
  const [dataSource, setDataSource] = useState(tmpDataSource);
  const [queryFormValues, setQueryFormValues] = useState<any>({});
  const [isQueryInit, setQueryInit] = useState(false);
  const {
    showFilter = true,
    showSearch = true,
    showReloadBtn = true,
    showQueryOptionBtns = true,
    showQueryCollapseButton = true,
    queryFormProps = {},
    isQuerySearchOnChange = true,
    loading = false,
    showBodyBg = false,
    queryFormColumns,
    queryMode = 'default',
    searchPos = 'full',
    reloadBtnPos = 'right',
    reloadBtnType = 'icon',
    antProps = {
      rowKey: 'id',
    },
  } = props;
  // 整理搜索区的展示状态
  // 是否展示长条搜索区
  const showFullSearch = showSearch && searchPos === 'full';
  // 搜索按钮展示的位置
  const showReloadBtn2SearchRight =
    searchPos === 'full' && showReloadBtn && reloadBtnPos === 'right';
  const showReloadBtn2FilterRight =
    (!showSearch || searchPos !== 'full') &&
    showReloadBtn &&
    reloadBtnPos === 'right';
  const showReloadBtn2FilterLeft = showReloadBtn && reloadBtnPos === 'left';

  const searchPlaceholder =
    props.searchPlaceholder || t('table.search.placeholder');
  const tableClassName = classNames(prefixCls, props.className);
  const tableContentClassName = classNames({
    [`${prefixCls}-table-content`]: true,
    [`${prefixCls}-table-content-noborder`]: props.hideContentBorder,
  });
  const tableBodyCls = classNames({
    [`${prefixCls}-body`]: !!queryFormColumns || showBodyBg,
    [`${prefixCls}-body-compact`]: queryMode === 'compact',
  });

  const tableQueryCls = classNames({
    [`${prefixCls}-query`]: !!queryFormColumns,
    [`${prefixCls}-query-compact`]: queryMode === 'compact',
  });
  const filterSearchInputRef = useRef({}) as any;
  const clearFiltersRef = useRef({}) as any;
  const [searchQuery, setSearchQuery] = useState('');
  const [showRightHeader, setShowRightHeader] = useState<boolean>(false);
  const [sorterState, sorterDispatch] = useReducer(sorterReducer, {});
  const rowSelection = props.rowSelection;

  const selectRowNum = rowSelection
    ? rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length
    : -1;
  const sorterNames = {
    ascend: t('table.sort.ascend'),
    descend: t('table.sort.descend'),
  };

  const showTotal = (total: number) => {
    return `${t('table.total.prefix')} ${total} ${t('table.total.suffix')}`;
  };
  // 生成列的 dataIndex 数组
  const columnsDataIndexArr =
    props.columns.map((columnItem) => {
      let dataIndex = columnItem.dataIndex;
      if (typeof dataIndex === 'object' && dataIndex !== null) {
        // metadata name
        dataIndex = dataIndex.join(' ');
      }
      return dataIndex;
    }) || [];

  const renderCurTarget = (
    data: Object,
    parentKey: Array<String>,
    curTarget = '',
  ) => {
    let returnItem = '';
    Object.entries(data).forEach(([curKey, curItem]) => {
      // 如果属性的值是对象,继续递归
      if (typeof curItem === 'object' && curItem !== null) {
        returnItem += ' ' + renderCurTarget(curItem, parentKey.concat(curKey));
      }

      columnsDataIndexArr.some((dataIndex) => {
        // 如果data对象的属性匹配到了columnsDataIndexArr中的某一项
        if (dataIndex === parentKey.concat(curKey).join(' ')) {
          // 当值为String|Number|Array类型时,将值加到curTarget中
          if (
            typeof curItem === 'string' ||
            typeof curItem === 'number' ||
            Array.isArray(curItem)
          ) {
            returnItem += ' ' + curItem;
          }
        }
      });
    });
    return `${curTarget} ${returnItem}`;
  };

  const dataSourceMap = useMemo(() => {
    if (!props.dataSource || !Array.isArray(props.dataSource)) {
      return [];
    }
    return props.dataSource.reduce((acc, curVal, curIdx) => {
      // let curTarget = ''
      // Object.entries(curVal).forEach(([curKey, curItem]) => {
      //   if (columnsDataIndexArr.indexOf(curKey) >= 0 && (typeof curItem === 'string' || typeof curItem === 'number')) {
      //     curTarget = `${curTarget} ${curItem}`
      //   }
      // })

      return {
        ...acc,
        [curIdx]: renderCurTarget(curVal, []).toLowerCase(),
      };
    }, {});
  }, [columnsDataIndexArr, props.dataSource]);
  // console.log('basicTable dataSourceMap', dataSourceMap)
  const columnsMap = useMemo(() => {
    const result = {} as any;
    if (props.columns && props.columns.length > 0) {
      props.columns.forEach((columnItem) => {
        if (columnItem.dataIndex) {
          result[columnItem.dataIndex as string] = {
            title: columnItem.title,
            dataIndex: columnItem.dataIndex,
            value: null,
            commonSearch: columnItem.commonSearch,
            commonFilter: columnItem.commonFilter,
            commonSorter: columnItem.commonSorter,
          };
        }
      });
    }
    return result;
  }, [props.columns]);
  // console.log('basicTable columnsMap', columnsMap)
  function columnInit(initColumnState) {
    return initColumnState;
  }

  function columnsReducer(
    state: IColumnsReducerState,
    action: TColumnsReducerAction,
  ) {
    switch (action.type) {
      case 'update':
        return {
          ...state,
          [action.dataIndex]: {
            ...state[action.dataIndex],
            dataIndex: action.dataIndex,
            type: action.updateType,
            value: action.value,
          },
        };
      case 'clear':
        return columnInit(action.initialState);
      default:
        return state;
    }
  }

  const [columnsState, columnsDispatch] = useReducer(
    columnsReducer,
    columnsMap,
    columnInit,
  );
  const debouncedSearchQuery = useDebounce(searchQuery, 300);

  useDeepCompareEffect(() => {
    // 模糊匹配
    let newDataSource = [...tmpDataSource];
    if (showSearch && debouncedSearchQuery && debouncedSearchQuery.trim()) {
      if (props.onSearch) {
        // 使用用户自定义的search回调
        props.onSearch(debouncedSearchQuery.trim());
        return;
      } else {
        // 使用组件默认的search回调

        const debouncedSearchArr = debouncedSearchQuery
          .trim()
          .toLowerCase()
          .split(' ');
        newDataSource = newDataSource.filter((fiterItem, filterIdx) => {
          const filterStr = dataSourceMap[filterIdx];
          return debouncedSearchArr.every(
            (someItem) => filterStr.indexOf(someItem) >= 0,
          );
        });
      }
    }

    if (queryFormColumns && Object.keys(queryFormValues).length > 0) {
      newDataSource = _.filter(newDataSource, (sourceItem) => {
        return Object.entries(queryFormValues).every(
          ([queryKey, queryValue]) => {
            if (
              (!queryValue && queryValue !== 0) ||
              (Array.isArray(queryValue) && queryValue.length === 0)
            ) {
              return true;
            }
            if (typeof queryValue === 'string') {
              return (
                sourceItem[queryKey] &&
                sourceItem[queryKey].indexOf(queryValue) >= 0
              );
            }
            if (typeof queryValue === 'number') {
              return sourceItem[queryKey] === queryValue;
            }
            if (Array.isArray(queryValue) && queryValue.length > 0) {
              return queryValue.indexOf(sourceItem[queryKey]) >= 0;
            }
          },
        );
      });
    }

    setDataSource(newDataSource);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedSearchQuery, queryFormValues, props.dataSource]);

  const handleChange = (
    pagination: any,
    filters: any,
    sorter: any,
    extra: any,
  ) => {
    sorterDispatch({
      type: 'update',
      value: sorter,
    });

    Object.entries(filters).forEach(([filterKey, filterValue]) => {
      if (columnsMap[filterKey].commonFilter) {
        columnsDispatch({
          type: 'update',
          dataIndex: filterKey,
          updateType: 'filter',
          value: filterValue,
        });
      }
    });

    checkRightHeader(filters, sorter);

    if (props.onChange) {
      props.onChange(pagination, filters, sorter, extra);
    }
  };

  const checkRightHeader = (filters?: any, sorter?: any, search?: any) => {
    const checkSorter = sorter || sorterState;
    const checkFilters = filters || columnsState;
    const checkSearch = search || columnsState;
    const isSearch = Object.values(checkSearch).some((columnItem: any) => {
      return !!(columnItem.type === 'search' && columnItem.value);
    });
    let isFilter = false;
    if (filters) {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return columnItem && columnItem.length > 0;
      });
    } else {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return !!(
          columnItem.type === 'filter' &&
          columnItem.value &&
          columnItem.value.length > 0
        );
      });
    }
    const isSorter = !!checkSorter.column;

    const res = isSearch || isFilter || isSorter;
    setShowRightHeader(res);
  };

  const handleFilterSearch = useCallback(
    (
      selectedKeys: React.ReactText[] | undefined,
      confirm: (() => void) | undefined,
      dataIndex: string | number,
    ) => {
      if (confirm) {
        confirm();
      }
      if (selectedKeys && dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
          updateType: 'search',
          value: selectedKeys[0],
        });
      }
    },
    [],
  );

  const handleFilterSearchReset = useCallback(
    (
      clearFilters: ((selectedKeys: string[]) => void) | undefined,
      dataIndex: string | number | undefined,
    ) => {
      if (clearFilters) {
        clearFilters([]);
      }
      if (dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
        });
      }
    },
    [],
  );

  const handleClearAll = () => {
    sorterDispatch({ type: 'clear' });
    columnsDispatch({ type: 'clear', initialState: columnsMap });
    setShowRightHeader(false);
    setTimeout(() => {
      Object.values(clearFiltersRef.current).forEach((filterItem: any) => {
        if (filterItem && filterItem.clearFilters) {
          filterItem.clearFilters([]);
        }
      });
    });
  };

  const handleSortClear = () => {
    sorterDispatch({ type: 'clear' });
    checkRightHeader(null, {});
  };

  const handleFilterClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
      value: [],
    });

    const tmpFilters = Object.values(columnsState).map((filterItem: any) => {
      if (filterItem.dataIndex === columnValue.dataIndex) {
        return {
          [filterItem.dataIndex]: [],
        };
      }
      return {
        [filterItem.dataIndex]: filterItem.value || [],
      };
    });

    checkRightHeader(tmpFilters, sorterState, columnsState);
  };

  const handleFilterSearchClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
    });

    if (
      clearFiltersRef.current[columnValue.dataIndex] &&
      clearFiltersRef.current[columnValue.dataIndex].clearFilters
    ) {
      clearFiltersRef.current[columnValue.dataIndex].clearFilters([]);
    }

    checkRightHeader(null, sorterState, {
      ...columnsState,
      [columnValue.dataIndex]: {
        title: columnsState[columnValue.dataIndex].title,
        dataIndex: columnsState[columnValue.dataIndex].dataIndex,
        value: null,
      },
    });
  };

  const renderColumns = () => {
    // if (!dataSource || (dataSource && dataSource.length === 0)) {
    //   return props.columns;
    // }

    const handledColumns = props.columns.map((columnItem) => {
      const currentItem = _.cloneDeep(columnItem);

      // filter
      if (currentItem.commonFilter && !currentItem.filters) {
        const filters = _.uniq(_.map(dataSource, columnItem.dataIndex));
        currentItem.filters = filters.map((value: string) => {
          return {
            text: value,
            value,
          };
        });

        currentItem.filterIcon = () => <FilterOutlined />;
        currentItem.filteredValue =
          columnsState[columnItem.dataIndex as string].value;
        currentItem.onFilter = (value, record: any) => {
          if (currentItem.dataIndex && record[currentItem.dataIndex]) {
            return record[currentItem.dataIndex] === value;
          }

          return false;
        };
      }

      // sort
      if (currentItem.commonSorter) {
        currentItem.sorter = (aItem: any, bItem: any) => {
          const a = aItem[currentItem.dataIndex as string];
          const b = bItem[currentItem.dataIndex as string];
          // number
          const numA = Number(a);
          const numB = Number(b);
          if (!isNaN(numA) && !isNaN(numB)) {
            return numA - numB;
          }

          // date
          const dateA = +new Date(a);
          const dateB = +new Date(b);
          if (!isNaN(dateA) && !isNaN(dateB)) {
            return dateA - dateB;
          }

          // string
          if (typeof a === 'string' && typeof b === 'string') {
            return a > b ? 1 : -1;
          }

          return 0;
        };
        currentItem.sortOrder =
          sorterState.columnKey === currentItem.dataIndex && sorterState.order;
      }

      // Search
      if (currentItem.commonSearch) {
        currentItem.filterIcon = () => <SearchOutlined />;

        currentItem.onFilterDropdownVisibleChange = (visible: boolean) => {
          if (
            visible &&
            filterSearchInputRef.current &&
            filterSearchInputRef.current.select
          ) {
            setTimeout(() => {
              if (
                filterSearchInputRef.current &&
                filterSearchInputRef.current.select
              ) {
                filterSearchInputRef.current.select();
              }
              return null;
            });
          }
        };

        currentItem.filterDropdown = ({
          setSelectedKeys,
          selectedKeys,
          confirm,
          clearFilters,
        }) => {
          clearFiltersRef.current[currentItem.dataIndex as string] = {
            clearFilters,
          };

          return (
            <div style={{ padding: 8 }}>
              <Input
                data-testid='filter-search-input'
                autoFocus={true}
                ref={(node) => {
                  filterSearchInputRef.current = node;
                }}
                placeholder={t('table.filter.search.placeholder')}
                value={selectedKeys && selectedKeys[0]}
                onChange={(e) => {
                  if (setSelectedKeys) {
                    return setSelectedKeys(
                      e.target.value ? [e.target.value] : [],
                    );
                  }
                  return [];
                }}
                onPressEnter={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                style={{ width: 188, marginBottom: 8, display: 'block' }}
              />
              <Button
                type='primary'
                onClick={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                icon='search'
                size='small'
                data-testid='search-btn-ok'
                style={{ width: 90, marginRight: 8 }}
              >
                {t('table.filter.search.btn.ok')}
              </Button>
              <Button
                onClick={() =>
                  handleFilterSearchReset(clearFilters, currentItem.dataIndex)
                }
                size='small'
                style={{ width: 90 }}
              >
                {t('table.filter.search.btn.cancel')}
              </Button>
            </div>
          );
        };

        let searchWords: any[] = [];

        const tmpStateValue =
          columnsState[currentItem.dataIndex as string].value;
        if (typeof tmpStateValue === 'string') {
          searchWords = [tmpStateValue];
        }

        if (
          Array.isArray(tmpStateValue) &&
          typeof tmpStateValue[0] === 'string'
        ) {
          searchWords = [tmpStateValue[0]];
        }

        currentItem.onFilter = (value, record: any) => {
          return record[currentItem.dataIndex as string]
            .toString()
            .toLowerCase()
            .includes(value.toLowerCase());
        };

        if (!currentItem.render) {
          currentItem.render = (value, row, index) => {
            if (currentItem.searchRender) {
              return currentItem.searchRender(
                value,
                row,
                index,
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />,
              );
            } else {
              return (
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />
              );
            }
          };
        }
      }
      return currentItem;
    });
    // console.log('basicTable handledColumns', handledColumns)
    return handledColumns;
  };

  const isSearch = Object.values(columnsState).some((columnItem: any) => {
    return !!(columnItem.type === 'search' && columnItem.value);
  });

  const isFilter = Object.values(columnsState).some((columnItem: any) => {
    return !!(
      columnItem.type === 'filter' &&
      columnItem.value &&
      columnItem.value.length > 0
    );
  });

  const handleReload = () => {
    if (props.onReload) {
      props.onReload();
    }
  };

  const handleSearchChange = (e) => {
    const query = e.target.value;
    setSearchQuery(query);
  };

  const handleQueryFormInit = (data) => {
    if (!isQueryInit) {
      setQueryFormValues(data);
      setQueryInit(true);
    }
  };

  const handleQueryFormChange = (data) => {
    setQueryFormValues(data);
  };

  const handleQueryFormReset = (data) => {
    setQueryFormValues(data);
  };

  const renderRightHeader = (params) => {
    if (!showFilter) {
      return null;
    }
    return (
      <>
        <div>
          <b
            style={{
              display: 'inline-block',
              marginTop: 3,
            }}
          >
            {t('table.filter.header.title')}
          </b>
        </div>
        {(isSearch || isFilter) &&
          Object.values(columnsState as IColumnsReducerState).map(
            (columnValue) => {
              if (columnValue.type === 'search' && columnValue.value) {
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterSearchClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }

              if (
                columnValue.type === 'filter' &&
                columnValue.value &&
                columnValue.value.length > 0
              ) {
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value.join(',')}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value.join(',')}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }
              return null;
            },
          )}
        {sorterState.columnKey && sorterState.column && (
          <div className={`${params.headerClsPrefix}-item`}>
            <Tooltip
              title={
                <span>
                  {sorterState.column.title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
              }
            >
              <Button
                size='small'
                className='table-header-item-btn'
                onClick={handleSortClear}
              >
                <span className='table-header-item-btn-content'>
                  {sorterState.column.title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
                <CloseOutlined />
              </Button>
            </Tooltip>
          </div>
        )}
        <div className={`${params.headerClsPrefix}-item`}>
          <Button
            size='small'
            type='link'
            data-testid='btn-clearall'
            onClick={handleClearAll}
          >
            {t('table.filter.header.btn.clear')}
          </Button>
        </div>
      </>
    );
  };

  const renderSearch = () => {
    return (
      <Tooltip placement='topLeft' title={searchPlaceholder}>
        <Input
          data-testid='search-input'
          prefix={
            reloadBtnPos === 'right' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          suffix={
            reloadBtnPos === 'left' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          allowClear={true}
          value={searchQuery}
          onChange={handleSearchChange}
          placeholder={searchPlaceholder}
        />
      </Tooltip>
    );
  };

  const renderReloadBtn = () => {
    if (reloadBtnType === 'icon') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-icon`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <ReloadOutlined
          onClick={handleReload}
          spin={loading}
          className={reloadBtnCls}
        />
      );
    }

    if (reloadBtnType === 'btn') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-btn`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <Button
          className={reloadBtnCls}
          loading={loading}
          icon={<ReloadOutlined />}
          data-testid='reload-btn'
          onClick={handleReload}
        />
      );
    }
  };

  return (
    <ConfigProvider {...props.antConfig}>
      <div className={tableClassName} style={props.style}>
        {queryFormColumns && (
          <div className={tableQueryCls}>
            <QueryForm
              onChange={
                isQuerySearchOnChange
                  ? handleQueryFormChange
                  : handleQueryFormInit
              }
              onReset={handleQueryFormReset}
              onSearch={handleQueryFormChange}
              columns={queryFormColumns}
              showOptionBtns={showQueryOptionBtns}
              showCollapseButton={showQueryCollapseButton}
              {...queryFormProps}
            />
          </div>
        )}
        <div className={tableBodyCls}>
          {!!props.tableTitle && <h3> {props.tableTitle} </h3>}

          {showFullSearch && (
            <Row className={`${prefixCls}-header-search`}>
              {showSearch ? renderSearch() : <div></div>}
              {showReloadBtn2SearchRight && renderReloadBtn()}
            </Row>
          )}
          {/* 自定义格式 */}
          {filterType === 'flex' && (
            <div
              style={{
                display: 'flex',
                justifyContent: 'flex-start',
                marginBottom: '10px',
              }}
            >
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              {props.leftHeader}
            </div>
          )}
          {filterType === 'none' && searchPos !== 'right' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
              >
                {props.leftHeader}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'none' && showSearch && searchPos === 'right' && (
            <Row className={`${prefixCls}-header-filter`} align='middle'>
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={13}
              >
                {showReloadBtn2FilterLeft && renderReloadBtn()}
                {props.leftHeader}
              </Col>
              <Col
                span={6}
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
              >
                {props.customHeader && (
                  <div data-testid='custom-header'>{props.customHeader}</div>
                )}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={5}
              >
                {renderSearch()}
              </Col>

              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'line' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-line`}
                span={24}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-line`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'half' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={12}
              >
                {props.leftHeader}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={12}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-right`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          <div className={`${prefixCls}-table-wrapper`}>
            <div className={tableContentClassName}>
              <Table
                loading={loading}
                columns={renderColumns()}
                dataSource={dataSource}
                bordered={false}
                rowSelection={rowSelection}
                onChange={handleChange}
                pagination={{
                  showTotal: showTotal,
                  showSizeChanger: true,
                  showQuickJumper: true,
                  pageSizeOptions: pageSizeOptions,
                  ...props.pagination,
                }}
                {...antProps}
              />
              {rowSelection && selectRowNum !== undefined && selectRowNum > 0 && (
                <div className={`${prefixCls}-table-content-select-num`}>
                  {t('table.select.num')}
                  {selectRowNum}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </ConfigProvider>
  );
}
Example #7
Source File: DataTable.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
function DataTable<T>(props: IDataTableProps<T>, ref: any) {
  const prefixCls = `${props.prefixCls || 'dantd'}-data-table`;
  const filterType = props.filterType || 'half';
  const isUrlLoad = useRef<string>();
  const t = intlZhMap;
  const {
    fetchOptions = {},
    searchParams,
    showFilter = true,
    showReloadBtn = true,
    showQueryOptionBtns = true,
    showQueryCollapseButton = true,
    isQuerySearchOnChange = true,
    showBodyBg = false,
    queryFormProps = {},
    url,
    queryMode = 'default',
    reloadBtnPos = 'right',
    searchPos = 'full',
    reloadBtnType = 'icon',
    queryFormColumns,
    pageParams,
    antProps = {
      rowKey: 'id',
    },
  } = props;

  const tableClassName = classNames(prefixCls, props.className);
  const tableContentClassName = classNames({
    [`${prefixCls}-table-content`]: true,
    [`${prefixCls}-table-content-noborder`]: props.hideContentBorder,
  });
  const tableBodyCls = classNames({
    [`${prefixCls}-body`]: !!queryFormColumns || showBodyBg,
    [`${prefixCls}-body-compact`]: queryMode === 'compact',
  });

  const tableQueryCls = classNames({
    [`${prefixCls}-query`]: !!queryFormColumns,
    [`${prefixCls}-query-compact`]: queryMode === 'compact',
  });

  // hooks
  const columnsMap = useMemo(() => {
    const result = {} as any;
    if (props.columns && props.columns.length > 0) {
      props.columns.forEach((columnItem) => {
        if (columnItem.dataIndex) {
          result[columnItem.dataIndex as string] = {
            ...columnItem,
            value: null,
          };
        }
      });
    }
    return result;
  }, [props.columns]);

  const [queryFormValues, setQueryFormValues] = useState<any>({});
  const [isQueryInit, setQueryInit] = useState(false);
  const isPageChangeNoSearch = useRef<boolean>(false);
  const filterSearchInputRef = useRef({}) as any;
  const clearFiltersRef = useRef({}) as any;
  const [dataSource, setDataSource] = useState([]);
  const [loading, setLoading] = useState(false);
  const [sorterState, sorterDispatch] = useReducer(sorterReducer, {});
  const [showRightHeader, setShowRightHeader] = useState<boolean>(false);
  const [searchQuery, setSearchQuery] = useState();
  const rowSelection = props.rowSelection;

  const selectRowNum = rowSelection
    ? rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length
    : -1;

  const sorterNames = {
    ascend: t('table.sort.ascend'),
    descend: t('table.sort.descend'),
  };

  const showTotal = (total: number) => {
    return `${t('table.total.prefix')} ${total} ${t('table.total.suffix')}`;
  };
  const showSearch = !!searchParams;
  // 是否展示长条搜索区
  const showFullSearch = showSearch && searchPos === 'full';
  // 搜索按钮展示的位置
  const showReloadBtn2SearchRight =
    searchPos === 'full' && showReloadBtn && reloadBtnPos === 'right';
  const showReloadBtn2FilterRight =
    (!showSearch || searchPos !== 'full') &&
    showReloadBtn &&
    reloadBtnPos === 'right';
  const showReloadBtn2FilterLeft = showReloadBtn && reloadBtnPos === 'left';
  const searchPlaceholder = searchParams
    ? searchParams.placeholder
    : t('table.search.placeholder');
  const [paginationState, setPagination] = useState({
    current: 1,
    pageSize: 10,
    total: 0,
    ...pageParams,
  });
  const [columnsState, columnsDispatch] = useReducer(
    columnsReducer,
    columnsMap,
  );
  const debouncedSearchQuery = useDebounce(searchQuery, 300);
  const debouncedQueryFormValues = useDebounce(
    JSON.stringify(queryFormValues),
    300,
  );
  const pageStr = JSON.stringify({
    pageSize: paginationState.pageSize,
    current: paginationState.current,
  });
  const tableStateStr = JSON.stringify({
    ...columnsState,
    ...sorterState,
  });
  // TODO 支持监听 customQuery
  useEffect(() => {
    if (url && isUrlLoad.current !== url) {
      let fetchParams = getAllFetchParams();
      fetchData(fetchParams);

      setTimeout(() => {
        isUrlLoad.current = url;
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [url]);

  useEffect(() => {
    if (!isUrlLoad.current) {
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchParams[paginationState.curPageName] = 1;
    if (searchParams && debouncedSearchQuery && debouncedSearchQuery.trim()) {
      fetchParams[searchParams.apiName] = debouncedSearchQuery.trim();
    }

    if (props.onSearch) {
      // 使用用户自定义的search回调
      props.onSearch(
        debouncedSearchQuery
          ? debouncedSearchQuery.trim()
          : debouncedSearchQuery,
      );
    }

    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedSearchQuery]);

  // 监听queryform
  useEffect(() => {
    if (!isUrlLoad.current) {
      return;
    }

    if (
      !queryFormColumns ||
      (queryFormValues && Object.keys(queryFormValues).length === 0)
    ) {
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchParams[paginationState.curPageName] = 1;

    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedQueryFormValues]);

  useEffect(() => {
    if (!isUrlLoad.current) {
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tableStateStr]);

  useEffect(() => {
    if (!isUrlLoad.current || !paginationState.pageSize) {
      return;
    }

    if (isPageChangeNoSearch.current) {
      isPageChangeNoSearch.current = false;
      return;
    }
    let fetchParams = getAllFetchParams();
    fetchData(fetchParams);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pageStr]);

  async function fetchData(params?: any) {
    let fetchUrl = url;
    let customFetchParams = null as any;
    if (fetchUrl.indexOf('?') !== -1) {
      const fetchArr = fetchUrl.split('?');
      fetchUrl = fetchArr[0];
      customFetchParams = queryString.parse(fetchArr[1], {
        arrayFormat: 'comma',
      });
    }

    let fetchParams = {
      [pageParams.curPageName]: params.current,
      [pageParams.pageSizeName]: params.pageSize,
    };

    if (customFetchParams) {
      fetchParams = {
        ...customFetchParams,
        ...fetchParams,
      };
    }

    // api起始页从0开始,参数需要减1
    if (pageParams.startPage === 0) {
      fetchParams[pageParams.curPageName] -= 1;
    }

    props.columns.forEach((columnItem) => {
      if (columnItem.dataIndex && params[columnItem.dataIndex]) {
        fetchParams[columnItem.dataIndex] = params[columnItem.dataIndex];
      }
      if (columnItem.apiSearch && params[columnItem.apiSearch.name]) {
        fetchParams[columnItem.apiSearch.name] =
          params[columnItem.apiSearch.name];
      }
      if (columnItem.apiFilter && params[columnItem.apiFilter.name]) {
        fetchParams[columnItem.apiFilter.name] =
          params[columnItem.apiFilter.name];
      }
      if (columnItem.apiSorter && params[columnItem.apiSorter.name]) {
        fetchParams[columnItem.apiSorter.name] =
          params[columnItem.apiSorter.name];
      }
    });

    if (searchParams && params[searchParams.apiName]) {
      fetchParams[searchParams.apiName] = params[searchParams.apiName];
    }

    if (
      !!queryFormColumns &&
      queryFormValues &&
      Object.keys(queryFormValues).length > 0
    ) {
      fetchParams = {
        ...queryFormValues,
        ...fetchParams,
      };
    }

    if (props.customQueryCallback) {
      fetchParams = props.customQueryCallback({
        ...fetchParams,
      });
    }

    fetchUrl = `${fetchUrl}?${queryString.stringify(fetchParams, {
      arrayFormat: 'comma',
    })}`;

    if (props.customFetchUrlCallback) {
      fetchUrl = props.customFetchUrlCallback(fetchUrl);
    }

    setLoading(true);
    let transportOptions = {
      ...fetchOptions,
    };
    if (fetchOptions.method && fetchOptions.method.toLowerCase() === 'post') {
      transportOptions.data = fetchParams;
    }
    const res = request(fetchUrl, transportOptions);
    props.rowSelection &&
      props.rowSelection.onChange &&
      props.rowSelection.onChange([]);
    if ((res.status < 200 || res.status >= 300) && props.apiErrorCallback) {
      props.apiErrorCallback(res);
      setLoading(false);
    } else {
      res
        .then(async (res) => {
          let callbackData = res;
          if (props.apiCallback) {
            callbackData = props.apiCallback(res);
          }
          if (props.apiAsyncCallback) {
            callbackData = await props.apiAsyncCallback(res);
          }
          setDataSource(callbackData.data);
          const tmpPagination = {
            ...paginationState,
            total: callbackData.total,
          };
          setPagination(tmpPagination);
          setLoading(false);
        })
        .catch(() => {
          if (props.apiErrorCallback) {
            props.apiErrorCallback(res);
          }
          setLoading(false);
        });
    }
  }

  const handleTableChange = (pagination: any, filters: any, sorter: any) => {
    if (!isUrlLoad.current) {
      return;
    }

    if (Object.keys(sorter).length > 0 && sorter.column) {
      sorterDispatch({
        type: 'update',
        value: {
          ...sorter,
          column: {
            apiSorter: sorter.column.apiSorter,
          },
        },
      });
    } else {
      sorterDispatch({
        type: 'update',
        value: {},
      });
    }

    Object.entries(filters).forEach(([filterKey, filterValue]) => {
      if (columnsMap[filterKey].filters) {
        columnsDispatch({
          type: 'update',
          dataIndex: filterKey,
          updateType: 'filter',
          value: filterValue,
        });
      }
    });

    isPageChangeNoSearch.current = false;

    setPagination(pagination);

    checkRightHeader(filters, sorter);
  };

  const handleFilterSearch = useCallback(
    (
      selectedKeys: React.ReactText[] | undefined,
      confirm: (() => void) | undefined,
      dataIndex: string | number,
    ) => {
      if (confirm) {
        confirm();
      }
      if (selectedKeys && dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
          updateType: 'search',
          value: selectedKeys[0],
        });
      }
    },
    [],
  );

  const handleFilterSearchReset = useCallback(
    (
      clearFilters: ((selectedKeys: string[]) => void) | undefined,
      dataIndex: string | number | undefined,
    ) => {
      if (clearFilters) {
        clearFilters([]);
      }
      if (dataIndex) {
        columnsDispatch({
          type: 'update',
          dataIndex,
        });
      }
    },
    [],
  );

  const handleFilterClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
      value: [],
    });

    const tmpFilters = Object.values(columnsState).map((filterItem: any) => {
      if (filterItem.dataIndex === columnValue.dataIndex) {
        return {
          [filterItem.dataIndex]: [],
        };
      }
      return {
        [filterItem.dataIndex]: filterItem.value || [],
      };
    });

    checkRightHeader(tmpFilters, sorterState, columnsState);
  };

  const handleFilterSearchClear = (columnValue: IColumnsReducerValue) => {
    columnsDispatch({
      type: 'update',
      dataIndex: columnValue.dataIndex,
    });

    if (
      clearFiltersRef.current[columnValue.dataIndex] &&
      clearFiltersRef.current[columnValue.dataIndex].clearFilters
    ) {
      clearFiltersRef.current[columnValue.dataIndex].clearFilters([]);
    }

    checkRightHeader(null, sorterState, {
      ...columnsState,
      [columnValue.dataIndex]: {
        title: columnsState[columnValue.dataIndex].title,
        dataIndex: columnsState[columnValue.dataIndex].dataIndex,
        value: null,
      },
    });
  };

  const handleClearAll = () => {
    sorterDispatch({ type: 'clear' });
    columnsDispatch({ type: 'clear', initialState: columnsMap });
    setShowRightHeader(false);

    setTimeout(() => {
      Object.values(clearFiltersRef.current).forEach((filterItem: any) => {
        if (filterItem && filterItem.clearFilters) {
          filterItem.clearFilters([]);
        }
      });
    });
  };

  const handleSortClear = () => {
    sorterDispatch({ type: 'clear' });
    checkRightHeader(null, {});
  };

  const handleQueryFormInit = (data) => {
    if (!isQueryInit) {
      setQueryFormValues(data);
      setQueryInit(true);
    }
  };

  const handleQueryFormChange = (data) => {
    isPageChangeNoSearch.current = true;
    setPagination({
      ...paginationState,
      current: 1,
    });
    setQueryFormValues(data);
  };

  const handleQueryFormReset = (data) => {
    isPageChangeNoSearch.current = true;
    setPagination({
      ...paginationState,
      current: 1,
    });
    setQueryFormValues(data);
  };

  const handleQuerySearch = (data) => {
    if (paginationState.current > 1) {
      // 这次修改分页参数
      isPageChangeNoSearch.current = true;

      setPagination({
        ...paginationState,
        current: 1,
      });
      setQueryFormValues(data);
    } else {
      setQueryFormValues(data);
      // let fetchParams = getAllFetchParams();
      // fetchData(fetchParams);
    }
  };

  const getAllFetchParams = () => {
    let fetchParams = {
      ...paginationState,
    };

    // columns sort
    if (sorterState && sorterState.order) {
      fetchParams[sorterState.column.apiSorter.name] =
        sorterState.column.apiSorter[sorterState.order];
    }

    Object.values(columnsState).forEach((column: any) => {
      const filterKey = column.dataIndex;
      const filterVal = column.value;
      // columns filter
      if (column.apiFilter && filterVal) {
        let filterName = columnsMap[filterKey].apiFilter
          ? columnsMap[filterKey].apiFilter.name
          : filterKey;
        fetchParams[filterName] = filterVal;
        if (column.apiFilter.callback) {
          fetchParams[filterName] =
            columnsMap[filterKey].apiFilter.callback(filterVal);
        }
      }
      // columns search
      if (column.apiSearch && filterVal) {
        const filterName = columnsMap[filterKey].apiSearch
          ? columnsMap[filterKey].apiSearch.name
          : filterKey;
        fetchParams[filterName] = Array.isArray(filterVal)
          ? filterVal[0]
          : filterVal;
      }
    });

    // query search
    if (searchParams && searchQuery) {
      fetchParams[searchParams.apiName] = searchQuery;
    }

    // queryform
    if (
      queryFormColumns ||
      (queryFormValues && Object.keys(queryFormValues).length > 0)
    ) {
      fetchParams = {
        ...queryFormValues,
        ...fetchParams,
      };
    }

    return fetchParams;
  };

  const handleReload = () => {
    // pages
    let fetchParams = getAllFetchParams();
    fetchData(fetchParams);
  };

  const handleSearchChange = (e) => {
    const query = e.target.value;
    setSearchQuery(query);
    // 使用组件默认的search回调
    setPagination({
      ...paginationState,
      current: 1,
    });
  };

  const checkRightHeader = (filters?: any, sorter?: any, search?: any) => {
    const checkSorter = sorter || sorterState;
    const checkFilters = filters || columnsState;
    const checkSearch = search || columnsState;
    const isSearch = Object.values(checkSearch).some((columnItem: any) => {
      return !!(columnItem.type === 'search' && columnItem.value);
    });
    let isFilter = false;
    if (filters) {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return columnItem && columnItem.length > 0;
      });
    } else {
      isFilter = Object.values(checkFilters).some((columnItem: any) => {
        return !!(
          columnItem.type === 'filter' &&
          columnItem.value &&
          columnItem.value.length > 0
        );
      });
    }
    const isSorter = !!checkSorter.column;

    const res = isSearch || isFilter || isSorter;
    setShowRightHeader(res);
  };

  const isSearch = Object.values(columnsState).some((columnItem: any) => {
    return !!(columnItem.type === 'search' && columnItem.value);
  });

  const isFilter = Object.values(columnsState).some((columnItem: any) => {
    return !!(
      columnItem.type === 'filter' &&
      columnItem.value &&
      columnItem.value.length > 0
    );
  });

  useImperativeHandle(ref, () => ({
    handleReload,
    goToFirstPage: () => {
      setPagination({
        ...paginationState,
        current: 1,
      });
    },
  }));

  const renderColumns = () => {
    return props.columns.map((columnItem) => {
      const currentItem = _.cloneDeep(columnItem);

      // filter
      if (currentItem.apiFilter) {
        currentItem.filterIcon = () => <FilterOutlined />;
        currentItem.filteredValue =
          columnsState[columnItem.dataIndex as string].value;
      }

      // // sort
      if (currentItem.apiSorter) {
        currentItem.sortOrder =
          sorterState.columnKey === currentItem.dataIndex && sorterState.order;
      }

      // Search
      if (currentItem.apiSearch) {
        currentItem.filterIcon = () => <SearchOutlined />;

        currentItem.onFilterDropdownVisibleChange = (visible: boolean) => {
          if (
            visible &&
            filterSearchInputRef.current &&
            filterSearchInputRef.current.select
          ) {
            setTimeout(() => {
              if (
                filterSearchInputRef.current &&
                filterSearchInputRef.current.select
              ) {
                filterSearchInputRef.current.select();
              }
              return null;
            });
          }
        };

        currentItem.filterDropdown = ({
          setSelectedKeys,
          selectedKeys,
          confirm,
          clearFilters,
        }) => {
          clearFiltersRef.current[currentItem.dataIndex as string] = {
            clearFilters,
          };

          return (
            <div style={{ padding: 8 }}>
              <Input
                data-testid='filter-search-input'
                autoFocus={true}
                ref={(node) => {
                  filterSearchInputRef.current = node;
                }}
                placeholder={t('table.filter.search.placeholder')}
                value={selectedKeys && selectedKeys[0]}
                onChange={(e) => {
                  if (setSelectedKeys) {
                    return setSelectedKeys(
                      e.target.value ? [e.target.value] : [],
                    );
                  }
                  return [];
                }}
                onPressEnter={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                style={{ width: 188, marginBottom: 8, display: 'block' }}
              />
              <Button
                type='primary'
                onClick={() =>
                  handleFilterSearch(
                    selectedKeys,
                    confirm,
                    currentItem.dataIndex as string,
                  )
                }
                icon='search'
                size='small'
                data-testid='search-btn-ok'
                style={{ width: 90, marginRight: 8 }}
              >
                {t('table.filter.search.btn.ok')}
              </Button>
              <Button
                onClick={() =>
                  handleFilterSearchReset(clearFilters, currentItem.dataIndex)
                }
                size='small'
                style={{ width: 90 }}
              >
                {t('table.filter.search.btn.cancel')}
              </Button>
            </div>
          );
        };

        let searchWords: any[] = [];

        const tmpStateValue =
          columnsState[currentItem.dataIndex as string].value;
        if (typeof tmpStateValue === 'string') {
          searchWords = [tmpStateValue];
        }

        if (
          Array.isArray(tmpStateValue) &&
          typeof tmpStateValue[0] === 'string'
        ) {
          searchWords = [tmpStateValue[0]];
        }

        currentItem.onFilter = (value, record: any) => {
          return record[currentItem.dataIndex as string]
            .toString()
            .toLowerCase()
            .includes(value.toLowerCase());
        };

        if (!currentItem.render) {
          currentItem.render = (value, row, index) => {
            if (currentItem.searchRender) {
              return currentItem.searchRender(
                value,
                row,
                index,
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />,
              );
            } else {
              return (
                <Highlighter
                  highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
                  searchWords={searchWords}
                  autoEscape
                  textToHighlight={String(value)}
                />
              );
            }
          };
        }
      }

      return currentItem;
    });
  };

  const renderRightHeader = (params) => {
    if (!showFilter) {
      return null;
    }
    return (
      <>
        <div>
          <b
            style={{
              marginTop: 4,
              display: 'inline-block',
            }}
          >
            {t('table.filter.header.title')}
          </b>
        </div>
        {(isSearch || isFilter) &&
          Object.values(columnsState as IColumnsReducerState).map(
            (columnValue) => {
              if (columnValue.type === 'search' && columnValue.value) {
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterSearchClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {columnValue.value}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }

              if (
                columnValue.type === 'filter' &&
                columnValue.value &&
                columnValue.value.length > 0
              ) {
                const mappings = columnsMap[columnValue.dataIndex]
                  ? columnsMap[columnValue.dataIndex].filters
                  : [];
                const mapping = mappings.reduce((acc, cur) => {
                  return {
                    ...acc,
                    [cur.value]: [cur.text],
                  };
                }, {});
                const newValues = columnValue.value.map((valItem) => {
                  return mapping && mapping[valItem]
                    ? mapping[valItem]
                    : valItem;
                });
                return (
                  <div
                    key={`search-header-${columnValue.dataIndex}`}
                    className={`${params.headerClsPrefix}-item`}
                  >
                    <Tooltip
                      title={
                        <span>
                          {columnValue.title}
                          {':'}&nbsp;
                          {newValues.join(',')}
                        </span>
                      }
                    >
                      <Button
                        size='small'
                        className='table-header-item-btn'
                        onClick={() => handleFilterClear(columnValue)}
                      >
                        <span className='table-header-item-btn-content'>
                          {columnValue.title}
                          {':'}&nbsp;
                          {newValues.join(',')}
                        </span>
                        <CloseOutlined />
                      </Button>
                    </Tooltip>
                  </div>
                );
              }
              return null;
            },
          )}
        {sorterState.columnKey && sorterState.column && (
          <div className={`${params.headerClsPrefix}-item`}>
            <Tooltip
              title={
                <span>
                  {columnsMap[sorterState.columnKey].title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
              }
            >
              <Button
                size='small'
                className='table-header-item-btn'
                onClick={handleSortClear}
              >
                <span className='table-header-item-btn-content'>
                  {columnsMap[sorterState.columnKey].title}
                  {`: ${sorterNames[sorterState.order as TSorterNames]}`}
                </span>
                <CloseOutlined />
              </Button>
            </Tooltip>
          </div>
        )}
        <div className={`${params.headerClsPrefix}-item`}>
          <Button
            size='small'
            type='link'
            data-testid='btn-clearall'
            onClick={handleClearAll}
          >
            {t('table.filter.header.btn.clear')}
          </Button>
        </div>
      </>
    );
  };

  const renderSearch = () => {
    return (
      <Tooltip placement='topLeft' title={searchPlaceholder}>
        <Input
          data-testid='search-input'
          prefix={
            reloadBtnPos === 'right' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          suffix={
            reloadBtnPos === 'left' && (
              <SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
            )
          }
          allowClear={true}
          value={searchQuery}
          onChange={handleSearchChange}
          placeholder={searchPlaceholder}
          // suffix={<Icon onClick={handleSearchClick} type="search" />}
        />
      </Tooltip>
    );
  };

  const renderReloadBtn = () => {
    if (reloadBtnType === 'icon') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-icon`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <ReloadOutlined
          onClick={handleReload}
          spin={loading}
          className={reloadBtnCls}
          type='reload'
        />
      );
    }

    if (reloadBtnType === 'btn') {
      const reloadBtnCls = classNames({
        [`${prefixCls}-header-loadbtn`]: true,
        [`${prefixCls}-header-loadbtn-btn`]: true,
        [`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
        [`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
      });
      return (
        <Button
          className={reloadBtnCls}
          loading={loading}
          icon={<ReloadOutlined />}
          data-testid='reload-btn'
          onClick={handleReload}
        />
      );
    }
  };

  return (
    <ConfigProvider {...props.antConfig}>
      <div className={tableClassName} style={props.style}>
        {queryFormColumns && (
          <div className={tableQueryCls}>
            <QueryForm
              onChange={
                isQuerySearchOnChange
                  ? handleQueryFormChange
                  : handleQueryFormInit
              }
              onReset={handleQueryFormReset}
              onSearch={handleQuerySearch}
              columns={queryFormColumns}
              showOptionBtns={showQueryOptionBtns}
              showCollapseButton={showQueryCollapseButton}
              {...queryFormProps}
            />
          </div>
        )}
        <div className={tableBodyCls}>
          {!!props.tableTitle && <h3> {props.tableTitle} </h3>}
          {props.customHeader && (
            <div
              data-testid='custom-header'
              className={`${prefixCls}-header-custom`}
            >
              {props.customHeader}
            </div>
          )}
          {showFullSearch && (
            <Row type='flex' className={`${prefixCls}-header-search`}>
              {showSearch ? renderSearch() : <div></div>}
              {showReloadBtn2SearchRight && renderReloadBtn()}
            </Row>
          )}
          {/* 自定义格式 */}
          {filterType === 'flex' && (
            <div
              style={{
                display: 'flex',
                justifyContent: 'flex-start',
                marginBottom: '10px',
              }}
            >
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              {props.leftHeader}
            </div>
          )}
          {filterType === 'none' && searchPos !== 'right' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
              >
                {props.leftHeader}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'none' && showSearch && searchPos === 'right' && (
            <Row className={`${prefixCls}-header-filter`}>
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={14}
              >
                {showReloadBtn2FilterLeft && renderReloadBtn()}
                {props.leftHeader}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={10}
              >
                {renderSearch()}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'line' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-line`}
                span={24}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-line`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          {filterType === 'half' && (
            <Row className={`${prefixCls}-header-filter`}>
              {showReloadBtn2FilterLeft && renderReloadBtn()}
              <Col
                data-testid='left-header'
                className={classNames(
                  `${prefixCls}-header-filter-left`,
                  props.leftHeader !== undefined &&
                    `${prefixCls}-header-filter-left-minh`,
                )}
                span={12}
              >
                {props.leftHeader}
              </Col>
              <Col
                data-testid='right-header'
                className={`${prefixCls}-header-filter-right`}
                span={12}
              >
                {showRightHeader &&
                  renderRightHeader({
                    headerClsPrefix: `${prefixCls}-header-filter-right`,
                  })}
              </Col>
              {showReloadBtn2FilterRight && renderReloadBtn()}
            </Row>
          )}
          <div className={`${prefixCls}-table-wrapper`}>
            <div className={tableContentClassName}>
              <Table
                loading={loading}
                columns={renderColumns()}
                dataSource={dataSource}
                bordered={false}
                rowSelection={rowSelection}
                onChange={handleTableChange}
                pagination={{
                  showTotal: showTotal,
                  showSizeChanger: true,
                  showQuickJumper: true,
                  pageSizeOptions: pageSizeOptions,
                  ...props.pagination,
                  ...paginationState,
                }}
                {...antProps}
              />
              {rowSelection && selectRowNum !== undefined && selectRowNum > 0 && (
                <div className={`${prefixCls}-table-content-select-num`}>
                  {t('table.select.num')}
                  {selectRowNum}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </ConfigProvider>
  );
}
Example #8
Source File: ChartDraggableElementField.tsx    From datart with Apache License 2.0 4 votes vote down vote up
ChartDraggableElementField: FC<{
  modalSize;
  config;
  columnConfig;
  ancestors;
  aggregation;
  availableSourceFunctions;
  onConfigChanged;
  handleOpenActionModal;
}> = memo(
  ({
    modalSize,
    config,
    columnConfig,
    ancestors,
    aggregation,
    availableSourceFunctions,
    onConfigChanged,
    handleOpenActionModal,
  }) => {
    const renderActionExtensionMenu = (uid: string, type: string, category) => {
      return (
        <ChartDataConfigSectionActionMenu
          uid={uid}
          type={type}
          category={category}
          ancestors={ancestors}
          config={config}
          modalSize={modalSize}
          availableSourceFunctions={availableSourceFunctions}
          onConfigChanged={onConfigChanged}
          onOpenModal={handleOpenActionModal}
        />
      );
    };

    const enableActionsIcons = col => {
      const icons = [] as any;
      if (col.alias) {
        icons.push(<DiffOutlined key="alias" />);
      }
      if (col.sort) {
        if (col.sort.type === SortActionType.ASC) {
          icons.push(<SortAscendingOutlined key="sort" />);
        }
        if (col.sort.type === SortActionType.DESC) {
          icons.push(<SortDescendingOutlined key="sort" />);
        }
      }
      if (col.format) {
        icons.push(<FormatPainterOutlined key="format" />);
      }
      if (col.aggregate) {
        icons.push(<GroupOutlined key="aggregate" />);
      }
      if (col.filter) {
        icons.push(<FilterOutlined key="filter" />);
      }
      if (col.color) {
        icons.push(<BgColorsOutlined key="color" />);
      }
      if (col.size) {
        icons.push(<FontSizeOutlined key="size" />);
      }
      return icons;
    };

    return (
      <Dropdown
        key={columnConfig.uid}
        disabled={!config?.actions}
        destroyPopupOnHide={true}
        overlay={renderActionExtensionMenu(
          columnConfig.uid!,
          columnConfig.type,
          columnConfig.category,
        )}
        overlayClassName="datart-data-section-dropdown"
        trigger={['click']}
      >
        <div>
          {config?.actions && <DownOutlined style={{ marginRight: '10px' }} />}
          <span>
            {aggregation === false
              ? columnConfig.colName
              : getColumnRenderName(columnConfig)}
          </span>
          <div style={{ display: 'inline-block', marginLeft: '5px' }}>
            {enableActionsIcons(columnConfig)}
          </div>
        </div>
      </Dropdown>
    );
  },
)
Example #9
Source File: index.tsx    From amiya with MIT License 4 votes vote down vote up
export default function Demo(props: IProps) {
  const {
    api,
    fields,
    topFields = [],
    extendFields = [],
    tabs = [],
    tabsApi,
    children,
    tableRef: otherTableRef,
    topFormRef: otherTopFormRef,
    searchValues: otherSearchValues,
    ...otherProps
  } = props
  const tableRef = useRef<any>()
  const topFormRef = useRef<any>()

  /** 筛选弹窗是否可见 */
  const [extendVisible, setExtendVisible] = useState(false)
  /** 筛选弹窗的值 */
  const [extendValues, setExtendValues] = useState<FormValues>({})
  /** 顶部 Tab 选项 */
  const [tabOptions, setTabOptions] = useState(tabs || [])
  /** 当前命中的 Tab */
  const [activeTab, setActiveTab] = useState<string>(tabOptions[0].value)
  /** 查询数据 */
  const [searchValues, setSearchValues] = useState<FormValues>({})

  if (otherTableRef) {
    otherTableRef.current = tableRef.current
  }

  if (otherTopFormRef) {
    otherTopFormRef.current = topFormRef.current
  }

  const beforeSearch = (values: FormValues) => {
    setSearchValues({ ...values.search })
    return values
  }

  const handleLoad = () => {
    loadTabCount()
  }
  const loadTabCount = () => {
    if (!tabsApi) {
      return
    }
    tabsApi().then((data: any) => {
      let newTabOptions = [...tabs]
      data.forEach((row: AnyKeyProps) => {
        let option = newTabOptions.find(option => option.value === row.status)
        if (option) {
          option.count = row.count
        }
      })

      setTabOptions(newTabOptions)
    })
  }

  return (
    <div className="table">
      <AySearchTable
        fields={fields}
        api={api}
        filterData={filterData}
        useOriginPagination={false}
        rowKey="key"
        onLoad={handleLoad}
        tableExtend={{ bordered: false }}
        searchExtend={{ inline: true }}
        beforeSearch={beforeSearch}
        extendSearchParams={{
          ...extendValues,
          ...otherSearchValues,
          activeTab
        }}
        before={
          <Card className="top-form-card" bodyStyle={{ padding: 0 }}>
            <AyForm style={{ marginTop: 20 }} fields={topFields} ref={topFormRef} />
          </Card>
        }
        center={
          <div style={{ marginBottom: 12 }}>
            <AyButton icon={<FilterOutlined />} onClick={() => setExtendVisible(true)}>
              筛选
            </AyButton>
            <div style={{ marginTop: 12 }}>
              <SearchData
                extendValues={extendValues}
                searchValues={searchValues}
                keyMap={{
                  keyword: searchValues['keywordType'] === 1 ? '商品名称' : 'SKU'
                }}
                searchFields={fields}
                extendFields={extendFields}
                noVisibleKeys={['keywordType', 'activeTab', ...Object.keys(otherSearchValues)]}
                onRemoveSearchValue={key => {
                  tableRef.current.getSearchRef().setFieldsValue({
                    [key]: undefined
                  })
                  tableRef.current.reset()
                }}
                onRemoveExtendValue={key => {
                  setExtendValues({
                    ...extendValues,
                    [key]: undefined
                  })
                  tableRef.current.reset()
                }}
                clearAll={() => {
                  tableRef.current.getSearchRef().resetFields()
                  setExtendValues({})
                  tableRef.current.reset()
                }}
              />
            </div>
          </div>
        }
        scrollX={1600}
        ref={tableRef}
        title={
          <Tabs
            value={activeTab}
            options={tabOptions}
            onChange={value => {
              setActiveTab(value)
              tableRef.current.reset()
            }}
          />
        }
        {...(otherProps as any)}
      >
        {children}
      </AySearchTable>
      <AyDialogForm
        title="筛选"
        visible={extendVisible}
        onClose={() => setExtendVisible(false)}
        onSuccess={({ values }) => {
          setExtendValues(values)
          tableRef.current.reset()
        }}
        fields={extendFields}
        initialValues={{
          ...extendValues
        }}
        addApi={extendData => Promise.resolve(extendData) as any}
        formExtend={{ className: 'full-width' }}
      />
    </div>
  )
}