Repository: ant-design
Issue: 虚拟表格和普通表格样式不一致
Labels: unconfirmed

### Reproduction link

[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/run?file=demo.tsx)

### Steps to reproduce

通过 virtual 属性来回切换虚拟表格时可以看得有明显的样式差异，最明显的便是滚动条样式，给用户带来了不一致的体验

### What is expected?

重写虚拟滚动逻辑
![image](https://github.com/user-attachments/assets/4f5c1651-5123-43d7-ab08-3de757708b77)


```tsx
import type { DOMAttributes } from 'react'
import { forwardRef, memo, useImperativeHandle, useMemo, useRef } from 'react'
import type { Scroll } from '../types'
import { useTableContext } from '../context'
import { useFlattenRecords } from '../hooks'
import type { VirtaulScrollProps } from './virtual-scroll'
import VirtualScroll from './virtual-scroll'
import ColGroup from '../col-group'
import Tbody from './tbody'

export interface BodyProps<RecordType> {
  data: readonly RecordType[]
  colWidths: number[]
  isVirtual?: boolean
  scrollX?: Scroll['x']
  scrollY?: Scroll['y']
  onScroll?: DOMAttributes<HTMLDivElement>['onScroll']
}

const BodyScroll = forwardRef<HTMLDivElement, VirtaulScrollProps<any>>(
  ({ children, data, onScroll, ...restProps }, ref) => (
    <div {...restProps} ref={ref} onScroll={onScroll}>
      {children?.({ data })}
    </div>
  )
)

const Body = forwardRef<HTMLDivElement, BodyProps<any>>((props, ref) => {
  const { isVirtual, scrollX, scrollY, onScroll, data, colWidths, ...restProps } = props

  const { prefixCls, expandColumnName, expandedKeys, xScroll, yScroll, getRowKey, getComponent } =
    useTableContext(
      ({
        prefixCls,
        expandColumnName,
        expandedKeys,
        xScroll,
        yScroll,
        getRowKey,
        getComponent
      }) => ({
        prefixCls,
        expandColumnName,
        expandedKeys,
        xScroll,
        yScroll,
        getRowKey,
        getComponent
      })
    )

  const flattenData = useFlattenRecords(data, expandColumnName, expandedKeys, getRowKey)

  const TableComponent = getComponent(['table'], 'table')

  const elRef = useRef<HTMLDivElement>(null)

  useImperativeHandle(ref, () => elRef.current!)

  const ScrollBody = useMemo(() => {
    if (isVirtual) return VirtualScroll
    return BodyScroll
  }, [isVirtual])

  const mergedTableStyle = useMemo(
    () => ({ width: scrollX, minWidth: '100%', tableLayout: 'fixed' }),
    [scrollX]
  )

  return (
    <ScrollBody
      ref={elRef}
      data={flattenData}
      style={{
        maxHeight: scrollY,
        // HACK: webkit scrollbar shaking
        overflow: `${xScroll ? 'scroll' : 'visible'} ${yScroll ? 'scroll' : 'visible'}`
      }}
      className={`${prefixCls}-body`}
      onScroll={onScroll}
    >
      {(props) => (
        <TableComponent {...restProps} style={mergedTableStyle}>
          <ColGroup colWidths={colWidths.map((width) => width)} />
          <Tbody {...props} />
        </TableComponent>
      )}
    </ScrollBody>
  )
})

export default memo(Body)

```


// virtual scroll 逻辑
```tsx
import type { CSSProperties, DOMAttributes, ReactNode, TdHTMLAttributes, UIEvent } from 'react'
import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { flushSync } from 'react-dom'
import { useIsomorphicLayoutEffect } from '../../../_hooks'
import { useTableContext } from '../context'
import type { ColumnType } from '../types'
import type { TbodyProps } from './tbody'

export interface VirtaulScrollProps<RecordType> {
  className?: string
  style?: CSSProperties
  originData?: TbodyProps<RecordType>['data']
  data: TbodyProps<RecordType>['data']
  children?: (params: TbodyProps<RecordType>) => ReactNode
  onScroll?: DOMAttributes<HTMLDivElement>['onScroll']
}

const VirtualScroll = forwardRef<HTMLDivElement, VirtaulScrollProps<any>>((props, ref) => {
  const { data, children, onScroll, ...restProps } = props

  const { flattenColumns, columnKeys, componentHeight, rowHeight, onRowSpanChange } =
    useTableContext(
      ({ flattenColumns, columnKeys, componentHeight, rowHeight, onRowSpanChange }) => ({
        flattenColumns,
        columnKeys,
        componentHeight,
        rowHeight,
        onRowSpanChange
      })
    )

  const elRef = useRef<HTMLDivElement>(null)

  const [offsetTop, setOffsetTop] = useState<number>(0)

  // TODO: read height from cahceHeights
  const { scrollHeight, start, end, offset } = useMemo(() => {
    let itemTop = 0
    let startIndex: number | undefined
    let startOffset: number | undefined
    let endIndex: number | undefined

    for (let i = 0; i < data.length; i += 1) {
      // const item = mergedData[i]
      // const key = getKey(item)

      // const cacheHeight = heights.get(key)
      // const currentItemBottom = itemTop + (cacheHeight === undefined ? itemHeight : cacheHeight)
      const currentItemBottom = itemTop + rowHeight

      if (currentItemBottom > offsetTop && startIndex == null) {
        startIndex = i
        startOffset = itemTop
      }

      if (currentItemBottom > offsetTop + componentHeight && endIndex == null) {
        endIndex = i
      }

      itemTop = currentItemBottom
    }

    if (startIndex == null) {
      startIndex = 0
      startOffset = 0
      endIndex = Math.ceil(componentHeight / rowHeight)
    }

    if (endIndex === undefined) {
      endIndex = data.length - 1
    }

    endIndex = Math.min(endIndex + 1, data.length - 1)
    return {
      scrollHeight: itemTop,
      start: startIndex,
      end: endIndex,
      offset: Math.max(startOffset as number, offsetTop)
    }
  }, [data.length, offsetTop, componentHeight, rowHeight])

  useImperativeHandle(ref, () => elRef.current!)

  const handleScroll = ({ target }: { target: Element }) => {
    flushSync(() => {
      const scrollTop = Math.max(Math.min(target.scrollTop, scrollHeight - componentHeight), 0)
      return setOffsetTop(scrollTop)
    })
  }

  const onBodyScroll = (e: UIEvent<HTMLDivElement>) => {
    onScroll?.(e)
    handleScroll?.({ target: e.currentTarget })
  }

  // ====================== RowSpans Effect ======================
  const getRowSpan = (column: ColumnType<any>, index: number): number => {
    const record = data[index]?.record
    const { onCell } = column

    if (onCell) {
      const cellProps = onCell(record, index) as TdHTMLAttributes<HTMLElement>
      return cellProps?.rowSpan ?? 1
    }
    return 1
  }

  // 用以修正在虚拟滚动时，跨行带来的表格错位
  useIsomorphicLayoutEffect(() => {
    if (start <= 0) return
    let restColumns = flattenColumns.filter((column) => getRowSpan(column, start) === 0)
    if (restColumns.length) {
      for (let rowIndex = start - 1; rowIndex >= 0; rowIndex -= 1) {
        restColumns = restColumns.filter((column) => {
          const rowSpan = getRowSpan(column, rowIndex)
          if (start - rowIndex < rowSpan) {
            const colIndex = flattenColumns.findIndex((col) => col === column)
            const key = columnKeys[colIndex]
            key && onRowSpanChange(key, rowIndex, rowSpan)
          }
          return rowSpan === 0
        })
        if (!restColumns.length) {
          break
        }
      }
    }
  }, [flattenColumns, columnKeys, start])

  const style = {
    paddingTop: offset,
    paddingBottom: Math.max(scrollHeight - offset - componentHeight, 0)
  }

  return (
    <div {...restProps} ref={elRef} onScroll={onBodyScroll}>
      <div style={style}>
        {children?.({
          data: data.slice(start, end + 1)
        })}
      </div>
    </div>
  )
})

export default VirtualScroll

```



### What is actually happening?

no response

| Environment | Info |
| --- | --- |
| antd | 5.22.7 |
| React | 18.0.0 |
| System | windows |
| Browser | edge 114.0.1823.37 |

<!-- generated by ant-design-issue-helper. DO NOT REMOVE -->