From 2ee4773f4faa7c2951f67be3b134ef8831c41b6d Mon Sep 17 00:00:00 2001 From: rayd1o Date: Sun, 12 Apr 2026 09:27:01 +0800 Subject: [PATCH] release: bump version to 0.26.2 --- VERSION | 2 +- docs/CHANGELOG.md | 20 ++ docs/version-history.md | 3 +- frontend/package.json | 2 +- .../src/components/AppLayout/AppLayout.tsx | 2 +- .../src/components/Scrollbar/Scrollbar.tsx | 184 +++++----- .../components/Scrollbar/ScrollbarOverlay.tsx | 327 ++++++++++++++++++ frontend/src/index.css | 86 ++--- .../src/pages/DataSources/DataSources.tsx | 3 + pyproject.toml | 2 +- uv.lock | 2 +- 11 files changed, 494 insertions(+), 139 deletions(-) create mode 100644 frontend/src/components/Scrollbar/ScrollbarOverlay.tsx diff --git a/VERSION b/VERSION index 30f6cf8d..894542aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.26.1 +0.26.2 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e66fdf5e..122f5ec9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -23,6 +23,26 @@ Released: 2026-04-12 - Fixed [frontend/public/earth/js/tv.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/tv.js) by refactoring repeated iframe/video reset paths into shared helpers, so TV source switching, empty-state fallback, and playback retry handling no longer duplicate cleanup logic across multiple branches. +## 0.26.2 + +Released: 2026-04-12 + +### Highlights + +- Stabilized the new reusable scrollbar work by restoring reliable sidebar visibility and extending the same floating scrollbar language to the Data Sources tables without letting scrollbars squeeze layout width or regress the console navigation. + +### Added + +- Added [frontend/src/components/Scrollbar/ScrollbarOverlay.tsx](/home/ray/dev/linkong/planet/frontend/src/components/Scrollbar/ScrollbarOverlay.tsx) as an overlay variant that binds to existing scroll containers such as Ant Table bodies, so heavy data grids can adopt the new scrollbar visuals without replacing their built-in scrolling mechanics. + +### Improved + +- Improved [frontend/src/components/Scrollbar/Scrollbar.tsx](/home/ray/dev/linkong/planet/frontend/src/components/Scrollbar/Scrollbar.tsx), [frontend/src/components/AppLayout/AppLayout.tsx](/home/ray/dev/linkong/planet/frontend/src/components/AppLayout/AppLayout.tsx), and [frontend/src/index.css](/home/ray/dev/linkong/planet/frontend/src/index.css) by reverting the sidebar to a reliable vertical-first scrollbar path, then reintroducing automatic dual-axis support with independent floating tracks that no longer hide the thumb when only the sidebar needs vertical scrolling. + +### Fixed + +- Fixed [frontend/src/pages/DataSources/DataSources.tsx](/home/ray/dev/linkong/planet/frontend/src/pages/DataSources/DataSources.tsx) and [frontend/src/index.css](/home/ray/dev/linkong/planet/frontend/src/index.css) so the built-in and custom datasource tables now use the new overlay scrollbar instead of native table scrollbars, keeping horizontal and vertical scrolling available without changing Ant Table’s internal layout behavior. + ## 0.26.0 Released: 2026-04-12 diff --git a/docs/version-history.md b/docs/version-history.md index 633b4a58..566fafe2 100644 --- a/docs/version-history.md +++ b/docs/version-history.md @@ -16,7 +16,7 @@ ## Current Version - `main` 当前主线历史推导到:`0.16.5` -- `dev` 当前开发分支历史推导到:`0.26.1` +- `dev` 当前开发分支历史推导到:`0.26.2` ## Timeline @@ -87,6 +87,7 @@ | `0.25.3` | bugfix | `dev` | `pending` | refactor the Earth HUD visual system, extract the top-left Earth brand into a reusable language-driven component, and consolidate duplicated brand assets into a single canonical set | | `0.26.0` | feature | `dev` | `pending` | add the Earth TV live module with backend-configurable sources, a draggable/resizable TV HUD window, a first curated news channel catalog, and TV source management hooks in system settings | | `0.26.1` | bugfix | `dev` | `pending` | extract the dashboard sidebar scrollbar into a reusable component and clean duplicated Earth TV player reset logic after the first live-module rollout | +| `0.26.2` | bugfix | `dev` | `pending` | stabilize the reusable sidebar scrollbar, restore automatic dual-axis floating tracks safely, and apply the same overlay scrollbar system to datasource tables | ## Maintenance Commits Not Counted as Version Bumps diff --git a/frontend/package.json b/frontend/package.json index 4c79e57d..3d6e099c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "planet-frontend", - "version": "0.26.1", + "version": "0.26.2", "private": true, "packageManager": "bun@1", "dependencies": { diff --git a/frontend/src/components/AppLayout/AppLayout.tsx b/frontend/src/components/AppLayout/AppLayout.tsx index 515372dd..c0daa9f3 100644 --- a/frontend/src/components/AppLayout/AppLayout.tsx +++ b/frontend/src/components/AppLayout/AppLayout.tsx @@ -133,7 +133,7 @@ function AppLayout({ children }: AppLayoutProps) { 智能星球 ) : null} - + (null) const trackXRef = useRef(null) @@ -78,35 +71,31 @@ function Scrollbar({ scrollTop, } = viewport - const enableX = axis === 'x' || axis === 'both' - const enableY = axis === 'y' || axis === 'both' - const hasOverflowX = enableX && !suppressScrollX && scrollWidth - clientWidth > 1 - const hasOverflowY = enableY && !suppressScrollY && scrollHeight - clientHeight > 1 + const hasOverflowX = scrollWidth - clientWidth > 1 + const hasOverflowY = scrollHeight - clientHeight > 1 - const nextX: AxisScrollbarState = hasOverflowX && clientWidth > 0 + const nextX: AxisState = hasOverflowX && clientWidth > 0 && trackX.clientWidth > 0 ? (() => { - const trackSize = trackX.clientWidth - const thumbSize = Math.max(minThumbSize, (clientWidth / scrollWidth) * trackSize) - const maxThumbOffset = Math.max(0, trackSize - thumbSize) + const thumbSize = Math.max(minThumbSize, (clientWidth / scrollWidth) * trackX.clientWidth) + const maxThumbOffset = Math.max(0, trackX.clientWidth - thumbSize) const scrollRange = Math.max(1, scrollWidth - clientWidth) return { visible: true, - dragging: scrollbar.x.dragging && dragStateRef.current?.axis === 'x', + dragging: dragStateRef.current?.axis === 'x', thumbSize, thumbOffset: (scrollLeft / scrollRange) * maxThumbOffset, } })() : { ...INITIAL_AXIS_STATE } - const nextY: AxisScrollbarState = hasOverflowY && clientHeight > 0 + const nextY: AxisState = hasOverflowY && clientHeight > 0 && trackY.clientHeight > 0 ? (() => { - const trackSize = trackY.clientHeight - const thumbSize = Math.max(minThumbSize, (clientHeight / scrollHeight) * trackSize) - const maxThumbOffset = Math.max(0, trackSize - thumbSize) + const thumbSize = Math.max(minThumbSize, (clientHeight / scrollHeight) * trackY.clientHeight) + const maxThumbOffset = Math.max(0, trackY.clientHeight - thumbSize) const scrollRange = Math.max(1, scrollHeight - clientHeight) return { visible: true, - dragging: scrollbar.y.dragging && dragStateRef.current?.axis === 'y', + dragging: dragStateRef.current?.axis === 'y', thumbSize, thumbOffset: (scrollTop / scrollRange) * maxThumbOffset, } @@ -114,7 +103,7 @@ function Scrollbar({ : { ...INITIAL_AXIS_STATE } setScrollbar({ x: nextX, y: nextY }) - }, [minThumbSize, scrollbar.x.dragging, scrollbar.y.dragging, suppressScrollX, suppressScrollY]) + }, [minThumbSize]) useEffect(() => { const viewport = viewportRef.current @@ -122,23 +111,45 @@ function Scrollbar({ const trackY = trackYRef.current if (!viewport || !trackX || !trackY) return + let rafId = 0 + const scheduleUpdate = () => { + cancelAnimationFrame(rafId) + rafId = window.requestAnimationFrame(() => { + updateScrollbar() + }) + } + updateScrollbar() + scheduleUpdate() const resizeObserver = new ResizeObserver(() => { - updateScrollbar() + scheduleUpdate() + }) + const mutationObserver = new MutationObserver(() => { + scheduleUpdate() }) resizeObserver.observe(viewport) resizeObserver.observe(trackX) resizeObserver.observe(trackY) Array.from(viewport.children).forEach((child) => resizeObserver.observe(child)) - viewport.addEventListener('scroll', updateScrollbar, { passive: true }) - window.addEventListener('resize', updateScrollbar) + + mutationObserver.observe(viewport, { + childList: true, + subtree: true, + attributes: true, + characterData: true, + }) + + viewport.addEventListener('scroll', scheduleUpdate, { passive: true }) + window.addEventListener('resize', scheduleUpdate) return () => { + cancelAnimationFrame(rafId) resizeObserver.disconnect() - viewport.removeEventListener('scroll', updateScrollbar) - window.removeEventListener('resize', updateScrollbar) + mutationObserver.disconnect() + viewport.removeEventListener('scroll', scheduleUpdate) + window.removeEventListener('resize', scheduleUpdate) } }, [children, updateScrollbar]) @@ -177,19 +188,16 @@ function Scrollbar({ const progress = Math.max(0, Math.min(nextThumbOffset, maxThumbOffset)) / maxThumbOffset if (axis === 'x') { - const scrollRange = Math.max(0, viewport.scrollWidth - viewport.clientWidth) - viewport.scrollLeft = progress * scrollRange + viewport.scrollLeft = progress * Math.max(0, viewport.scrollWidth - viewport.clientWidth) return } - const scrollRange = Math.max(0, viewport.scrollHeight - viewport.clientHeight) - viewport.scrollTop = progress * scrollRange + viewport.scrollTop = progress * Math.max(0, viewport.scrollHeight - viewport.clientHeight) } const handleThumbPointerDown = (axis: Axis) => (event: ReactPointerEvent) => { - const rect = event.currentTarget.getBoundingClientRect() - const axisState = scrollbar[axis] - if (!axisState.visible) return + if (!scrollbar[axis].visible) return + const rect = event.currentTarget.getBoundingClientRect() dragStateRef.current = { axis, pointerId: event.pointerId, @@ -207,9 +215,9 @@ function Scrollbar({ } const handleThumbPointerMove = (axis: Axis) => (event: ReactPointerEvent) => { - const track = axis === 'x' ? trackXRef.current : trackYRef.current const dragState = dragStateRef.current - if (!track || !dragState || dragState.pointerId !== event.pointerId || dragState.axis !== axis) return + const track = axis === 'x' ? trackXRef.current : trackYRef.current + if (!dragState || dragState.axis !== axis || dragState.pointerId !== event.pointerId || !track) return const rect = track.getBoundingClientRect() const nextThumbOffset = axis === 'x' @@ -219,7 +227,9 @@ function Scrollbar({ } const handleThumbPointerUp = (axis: Axis) => (event: ReactPointerEvent) => { - if (dragStateRef.current?.pointerId !== event.pointerId || dragStateRef.current.axis !== axis) return + const dragState = dragStateRef.current + if (!dragState || dragState.axis !== axis || dragState.pointerId !== event.pointerId) return + dragStateRef.current = null document.body.style.userSelect = '' setScrollbar((previous) => ({ @@ -230,70 +240,60 @@ function Scrollbar({ } const handleTrackPointerDown = (axis: Axis) => (event: ReactPointerEvent) => { - const axisState = scrollbar[axis] - if (!axisState.visible || event.target !== event.currentTarget) return + if (!scrollbar[axis].visible || event.target !== event.currentTarget) return const rect = event.currentTarget.getBoundingClientRect() const nextThumbOffset = axis === 'x' - ? event.clientX - rect.left - axisState.thumbSize / 2 - : event.clientY - rect.top - axisState.thumbSize / 2 + ? event.clientX - rect.left - scrollbar.x.thumbSize / 2 + : event.clientY - rect.top - scrollbar.y.thumbSize / 2 syncScrollFromThumbOffset(axis, nextThumbOffset) } - const showCorner = scrollbar.x.visible && scrollbar.y.visible - const scrollbarClassName = [ - 'scrollbar', - `scrollbar--${axis}`, - className, - ].filter(Boolean).join(' ') + const hasCorner = scrollbar.x.visible && scrollbar.y.visible return ( -
+
{children}
- {(axis === 'y' || axis === 'both') ? ( -
- {scrollbar.y.visible ? ( -
- ) : null} -
- ) : null} +
+ {scrollbar.y.visible ? ( +
+ ) : null} +
- {(axis === 'x' || axis === 'both') ? ( -
- {scrollbar.x.visible ? ( -
- ) : null} -
- ) : null} +
+ {scrollbar.x.visible ? ( +
+ ) : null} +
) } diff --git a/frontend/src/components/Scrollbar/ScrollbarOverlay.tsx b/frontend/src/components/Scrollbar/ScrollbarOverlay.tsx new file mode 100644 index 00000000..a7b0f23e --- /dev/null +++ b/frontend/src/components/Scrollbar/ScrollbarOverlay.tsx @@ -0,0 +1,327 @@ +import { + type PointerEvent as ReactPointerEvent, + type RefObject, + useCallback, + useEffect, + useRef, + useState, +} from 'react' + +type Axis = 'x' | 'y' + +interface AxisState { + visible: boolean + dragging: boolean + thumbSize: number + thumbOffset: number +} + +interface ScrollbarState { + x: AxisState + y: AxisState +} + +interface ScrollbarOverlayProps { + containerRef: RefObject + targetSelector: string + minThumbSize?: number +} + +interface DragState { + axis: Axis + pointerId: number + pointerOffset: number +} + +const INITIAL_AXIS_STATE: AxisState = { + visible: false, + dragging: false, + thumbSize: 0, + thumbOffset: 0, +} + +const INITIAL_STATE: ScrollbarState = { + x: INITIAL_AXIS_STATE, + y: INITIAL_AXIS_STATE, +} + +function ScrollbarOverlay({ + containerRef, + targetSelector, + minThumbSize = 28, +}: ScrollbarOverlayProps) { + const trackXRef = useRef(null) + const trackYRef = useRef(null) + const dragStateRef = useRef(null) + const targetRef = useRef(null) + const [scrollbar, setScrollbar] = useState(INITIAL_STATE) + + const bindTarget = useCallback(() => { + const container = containerRef.current + if (!container) { + targetRef.current = null + return null + } + + const target = container.querySelector(targetSelector) + targetRef.current = target + return target + }, [containerRef, targetSelector]) + + const updateScrollbar = useCallback(() => { + const target = targetRef.current ?? bindTarget() + const trackX = trackXRef.current + const trackY = trackYRef.current + if (!target || !trackX || !trackY) return + + const { + clientWidth, + clientHeight, + scrollWidth, + scrollHeight, + scrollLeft, + scrollTop, + } = target + + const hasOverflowX = scrollWidth - clientWidth > 1 + const hasOverflowY = scrollHeight - clientHeight > 1 + + const nextX: AxisState = hasOverflowX && clientWidth > 0 && trackX.clientWidth > 0 + ? (() => { + const thumbSize = Math.max(minThumbSize, (clientWidth / scrollWidth) * trackX.clientWidth) + const maxThumbOffset = Math.max(0, trackX.clientWidth - thumbSize) + const scrollRange = Math.max(1, scrollWidth - clientWidth) + return { + visible: true, + dragging: dragStateRef.current?.axis === 'x', + thumbSize, + thumbOffset: (scrollLeft / scrollRange) * maxThumbOffset, + } + })() + : { ...INITIAL_AXIS_STATE } + + const nextY: AxisState = hasOverflowY && clientHeight > 0 && trackY.clientHeight > 0 + ? (() => { + const thumbSize = Math.max(minThumbSize, (clientHeight / scrollHeight) * trackY.clientHeight) + const maxThumbOffset = Math.max(0, trackY.clientHeight - thumbSize) + const scrollRange = Math.max(1, scrollHeight - clientHeight) + return { + visible: true, + dragging: dragStateRef.current?.axis === 'y', + thumbSize, + thumbOffset: (scrollTop / scrollRange) * maxThumbOffset, + } + })() + : { ...INITIAL_AXIS_STATE } + + setScrollbar({ x: nextX, y: nextY }) + }, [bindTarget, minThumbSize]) + + useEffect(() => { + const container = containerRef.current + if (!container) return + + let cleanupTargetScroll: (() => void) | null = null + let resizeObserver: ResizeObserver | null = null + let mutationObserver: MutationObserver | null = null + let rafId = 0 + + const scheduleUpdate = () => { + cancelAnimationFrame(rafId) + rafId = window.requestAnimationFrame(() => { + updateScrollbar() + }) + } + + const attachTarget = () => { + cleanupTargetScroll?.() + resizeObserver?.disconnect() + + const target = bindTarget() + if (!target) { + targetRef.current = null + return + } + + const handleScroll = () => { + scheduleUpdate() + } + + target.addEventListener('scroll', handleScroll, { passive: true }) + cleanupTargetScroll = () => { + target.removeEventListener('scroll', handleScroll) + } + + resizeObserver = new ResizeObserver(() => { + scheduleUpdate() + }) + resizeObserver.observe(target) + Array.from(target.children).forEach((child) => resizeObserver?.observe(child)) + scheduleUpdate() + } + + attachTarget() + + mutationObserver = new MutationObserver(() => { + attachTarget() + scheduleUpdate() + }) + mutationObserver.observe(container, { + childList: true, + subtree: true, + attributes: true, + }) + + window.addEventListener('resize', scheduleUpdate) + + return () => { + cancelAnimationFrame(rafId) + cleanupTargetScroll?.() + resizeObserver?.disconnect() + mutationObserver?.disconnect() + window.removeEventListener('resize', scheduleUpdate) + } + }, [bindTarget, containerRef, updateScrollbar]) + + useEffect(() => { + const stopDragging = () => { + dragStateRef.current = null + document.body.style.userSelect = '' + setScrollbar((previous) => ({ + x: { ...previous.x, dragging: false }, + y: { ...previous.y, dragging: false }, + })) + } + + window.addEventListener('pointerup', stopDragging) + window.addEventListener('pointercancel', stopDragging) + + return () => { + window.removeEventListener('pointerup', stopDragging) + window.removeEventListener('pointercancel', stopDragging) + } + }, []) + + const syncScrollFromThumbOffset = (axis: Axis, nextThumbOffset: number) => { + const target = targetRef.current + const track = axis === 'x' ? trackXRef.current : trackYRef.current + const axisState = scrollbar[axis] + if (!target || !track) return + + const trackSize = axis === 'x' ? track.clientWidth : track.clientHeight + const maxThumbOffset = Math.max(0, trackSize - axisState.thumbSize) + if (maxThumbOffset <= 0) { + if (axis === 'x') target.scrollLeft = 0 + else target.scrollTop = 0 + return + } + + const progress = Math.max(0, Math.min(nextThumbOffset, maxThumbOffset)) / maxThumbOffset + if (axis === 'x') { + target.scrollLeft = progress * Math.max(0, target.scrollWidth - target.clientWidth) + return + } + target.scrollTop = progress * Math.max(0, target.scrollHeight - target.clientHeight) + } + + const handleThumbPointerDown = (axis: Axis) => (event: ReactPointerEvent) => { + if (!scrollbar[axis].visible) return + + const rect = event.currentTarget.getBoundingClientRect() + dragStateRef.current = { + axis, + pointerId: event.pointerId, + pointerOffset: axis === 'x' ? event.clientX - rect.left : event.clientY - rect.top, + } + + setScrollbar((previous) => ({ + x: { ...previous.x, dragging: axis === 'x' }, + y: { ...previous.y, dragging: axis === 'y' }, + })) + document.body.style.userSelect = 'none' + event.currentTarget.setPointerCapture(event.pointerId) + event.stopPropagation() + event.preventDefault() + } + + const handleThumbPointerMove = (axis: Axis) => (event: ReactPointerEvent) => { + const dragState = dragStateRef.current + const track = axis === 'x' ? trackXRef.current : trackYRef.current + if (!dragState || dragState.axis !== axis || dragState.pointerId !== event.pointerId || !track) return + + const rect = track.getBoundingClientRect() + const nextThumbOffset = axis === 'x' + ? event.clientX - rect.left - dragState.pointerOffset + : event.clientY - rect.top - dragState.pointerOffset + syncScrollFromThumbOffset(axis, nextThumbOffset) + } + + const handleThumbPointerUp = (axis: Axis) => (event: ReactPointerEvent) => { + const dragState = dragStateRef.current + if (!dragState || dragState.axis !== axis || dragState.pointerId !== event.pointerId) return + + dragStateRef.current = null + document.body.style.userSelect = '' + setScrollbar((previous) => ({ + x: { ...previous.x, dragging: false }, + y: { ...previous.y, dragging: false }, + })) + event.currentTarget.releasePointerCapture?.(event.pointerId) + } + + const handleTrackPointerDown = (axis: Axis) => (event: ReactPointerEvent) => { + if (!scrollbar[axis].visible || event.target !== event.currentTarget) return + + const rect = event.currentTarget.getBoundingClientRect() + const nextThumbOffset = axis === 'x' + ? event.clientX - rect.left - scrollbar.x.thumbSize / 2 + : event.clientY - rect.top - scrollbar.y.thumbSize / 2 + syncScrollFromThumbOffset(axis, nextThumbOffset) + } + + const hasCorner = scrollbar.x.visible && scrollbar.y.visible + + return ( +
+
+ {scrollbar.y.visible ? ( +
+ ) : null} +
+ +
+ {scrollbar.x.visible ? ( +
+ ) : null} +
+
+ ) +} + +export default ScrollbarOverlay diff --git a/frontend/src/index.css b/frontend/src/index.css index e72973c6..c706dd9a 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -91,11 +91,6 @@ body { overflow: hidden; } -.scrollbar--y .scrollbar__track--x, -.scrollbar--x .scrollbar__track--y { - display: none; -} - .scrollbar__viewport { height: 100%; overflow: auto; @@ -117,29 +112,35 @@ body { .scrollbar__track--y { top: 0; - right: 8px; - width: 0; + right: 0; + width: 12px; height: 100%; } .scrollbar__track--x { left: 0; - bottom: 8px; + bottom: 0; width: 100%; - height: 0; + height: 12px; +} + +.scrollbar__track--y.scrollbar__track--has-corner { + height: calc(100% - 12px); +} + +.scrollbar__track--x.scrollbar__track--has-corner { + width: calc(100% - 12px); } .scrollbar__thumb { position: absolute; - min-height: 28px; - min-width: 28px; border-radius: 999px; background: rgba(196, 208, 224, 0.46); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08); opacity: 0; cursor: pointer; pointer-events: auto; - transition: opacity 0.18s ease, background 0.18s ease, width 0.18s ease; + transition: opacity 0.18s ease, background 0.18s ease; } .scrollbar__thumb::after { @@ -149,24 +150,26 @@ body { .scrollbar__thumb--y { top: 8px; - right: 0; - width: 6px; - min-width: 6px; + right: 3px; + width: 4px; + min-width: 4px; + min-height: 28px; } .scrollbar__thumb--y::after { - inset: 0 -6px; + inset: 0 -4px; } .scrollbar__thumb--x { left: 8px; - bottom: 0; - height: 6px; - min-height: 6px; + bottom: 3px; + height: 4px; + min-height: 4px; + min-width: 28px; } .scrollbar__thumb--x::after { - inset: -6px 0; + inset: -4px 0; } .scrollbar:hover .scrollbar__track--visible .scrollbar__thumb, @@ -175,6 +178,10 @@ body { opacity: 1; } +.scrollbar__track--visible { + pointer-events: auto; +} + .scrollbar__thumb:hover { background: rgba(216, 226, 240, 0.68); } @@ -1555,6 +1562,7 @@ body { } .table-scroll-region { + position: relative; width: 100%; max-width: 100%; min-width: 0; @@ -1575,35 +1583,31 @@ body { padding: 10px 12px !important; } -.table-scroll-region .ant-table-body, -.table-scroll-region .ant-table-content { - scrollbar-width: thin; - scrollbar-color: rgba(148, 163, 184, 0.88) transparent; +.data-source-table-region .ant-table-body, +.data-source-table-region .ant-table-content { + scrollbar-width: none; + -ms-overflow-style: none; } -.table-scroll-region .ant-table-body::-webkit-scrollbar, -.table-scroll-region .ant-table-content::-webkit-scrollbar { - width: 8px; - height: 8px; +.data-source-table-region .ant-table-body::-webkit-scrollbar, +.data-source-table-region .ant-table-content::-webkit-scrollbar { + width: 0; + height: 0; } -.table-scroll-region .ant-table-body::-webkit-scrollbar-thumb, -.table-scroll-region .ant-table-content::-webkit-scrollbar-thumb { - background: rgba(148, 163, 184, 0.82); - border-radius: 999px; - border: 2px solid transparent; - background-clip: padding-box; +.scrollbar--overlay { + position: absolute; + inset: 0; + z-index: 3; + pointer-events: none; } -.table-scroll-region .ant-table-body::-webkit-scrollbar-thumb:hover, -.table-scroll-region .ant-table-content::-webkit-scrollbar-thumb:hover { - background: rgba(100, 116, 139, 0.9); - background-clip: padding-box; +.scrollbar--overlay .scrollbar__track--y { + right: 2px; } -.table-scroll-region .ant-table-body::-webkit-scrollbar-track, -.table-scroll-region .ant-table-content::-webkit-scrollbar-track { - background: transparent; +.scrollbar--overlay .scrollbar__track--x { + bottom: 2px; } .bgp-page { diff --git a/frontend/src/pages/DataSources/DataSources.tsx b/frontend/src/pages/DataSources/DataSources.tsx index 9a667978..6c07933b 100644 --- a/frontend/src/pages/DataSources/DataSources.tsx +++ b/frontend/src/pages/DataSources/DataSources.tsx @@ -11,6 +11,7 @@ import { } from '@ant-design/icons' import axios, { type AxiosResponse } from 'axios' import AppLayout from '../../components/AppLayout/AppLayout' +import ScrollbarOverlay from '../../components/Scrollbar/ScrollbarOverlay' import { formatDateTimeZhCN } from '../../utils/datetime' import { useWebSocket } from '../../hooks/useWebSocket' @@ -1050,6 +1051,7 @@ function DataSources() { tableLayout="fixed" size="small" /> +
), @@ -1084,6 +1086,7 @@ function DataSources() { tableLayout="fixed" size="small" /> +
)}
diff --git a/pyproject.toml b/pyproject.toml index 8341bf27..55681f9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "planet" -version = "0.26.1" +version = "0.26.2" description = "智能星球计划 - 态势感知系统" requires-python = ">=3.14" dependencies = [ diff --git a/uv.lock b/uv.lock index 83aca449..3ffd1d32 100644 --- a/uv.lock +++ b/uv.lock @@ -475,7 +475,7 @@ wheels = [ [[package]] name = "planet" -version = "0.26.1" +version = "0.26.2" source = { virtual = "." } dependencies = [ { name = "aiofiles" },