From 96e9dac0ca14f4723839e9bcd6484594ca75bb87 Mon Sep 17 00:00:00 2001 From: dark Date: Sun, 23 Jun 2024 19:50:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=B8=83=E5=B1=80=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useScrollMask.ts | 67 ++++++++++++++++++++++++++++++++++++++++ src/hooks/useScrollStyle.ts | 47 ++++++++++++++++++++++++++++ src/layout/TwoColPageLayout.tsx | 5 +-- src/layout/style.ts | 4 +-- src/pages/system/menus/index.tsx | 2 ++ src/pages/system/menus/style.ts | 12 +++++-- 6 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 src/hooks/useScrollMask.ts diff --git a/src/hooks/useScrollMask.ts b/src/hooks/useScrollMask.ts new file mode 100644 index 0000000..a879348 --- /dev/null +++ b/src/hooks/useScrollMask.ts @@ -0,0 +1,67 @@ +import { useEffect } from 'react' + +type Ref = { current: Element } + +const useScrollMask = (ref: Ref | string) => { + useEffect(() => { + let element: Ref + if (typeof ref === 'string') { + + element = { + current: document.querySelector(ref)! + } + if (!element.current) return + } else { + element = ref as Ref + } + + if (!element.current) return + + const handleScroll = () => { + const { scrollTop, scrollHeight, clientHeight } = element.current + const atTop = scrollTop === 0 + const atBottom = scrollTop + clientHeight === scrollHeight + + if (atTop && atBottom) { + element.current.setAttribute('data-top-bottom-scroll', 'true') + element.current.removeAttribute('data-top-scroll') + element.current.removeAttribute('data-bottom-scroll') + } else if (atTop) { + element.current.setAttribute('data-top-scroll', 'true') + element.current.removeAttribute('data-bottom-scroll') + element.current.removeAttribute('data-top-bottom-scroll') + } else if (atBottom) { + element.current.setAttribute('data-bottom-scroll', 'true') + element.current.removeAttribute('data-top-scroll') + element.current.removeAttribute('data-top-bottom-scroll') + } else { + element.current.setAttribute('data-top-bottom-scroll', 'true') + element.current.removeAttribute('data-bottom-scroll') + element.current.removeAttribute('data-top-scroll') + } + } + + const checkScroll = () => { + const hasVerticalScrollbar = element.current.scrollHeight > element.current.clientHeight + + if (hasVerticalScrollbar) { + element.current.classList.add('scroll-mask') + } else { + element.current.classList.remove('scroll-mask') + } + + handleScroll() // Initial check + } + + element.current.addEventListener('scroll', handleScroll) + window.addEventListener('resize', checkScroll) + checkScroll() + + return () => { + element.current.removeEventListener('scroll', handleScroll) + window.removeEventListener('resize', checkScroll) + } + }, [ ref ]) +} + +export default useScrollMask diff --git a/src/hooks/useScrollStyle.ts b/src/hooks/useScrollStyle.ts index 7020436..6c750f8 100644 --- a/src/hooks/useScrollStyle.ts +++ b/src/hooks/useScrollStyle.ts @@ -114,10 +114,57 @@ export const useScrollStyle = () => { ${darkScrollbar} `; + const scrollbar1 = css ` + + + + /* 通用的 overflow 和 padding */ + .scroll-mask { + --scroll-shadow-size: 40px; + overflow-y: auto; + margin-right: -1.5rem; /* -mr-6 */ + height: 100%; /* h-full */ + max-height: 100%; /* max-h-full */ + padding-top: 1.5rem; /* py-6 */ + padding-bottom: 1.5rem; /* py-6 */ + padding-right: 1.5rem; /* pr-6 */ + } + + /* 顶部遮罩效果 */ + .scroll-mask-top { + mask-image: linear-gradient(0deg, #000 calc(100% - var(--scroll-shadow-size)), transparent); + } + + /* 底部遮罩效果 */ + .scroll-mask-bottom { + mask-image: linear-gradient(180deg, #000 calc(100% - var(--scroll-shadow-size)), transparent); + } + + /* 顶部和底部遮罩效果 */ + .scroll-mask-top-bottom { + mask-image: linear-gradient(#000, #000, transparent 0, #000 var(--scroll-shadow-size), #000 calc(100% - var(--scroll-shadow-size)), transparent); + } + + /* 根据滚动状态动态应用遮罩效果 */ + [data-top-scroll="true"] { + mask-image: linear-gradient(0deg, #000 calc(100% - var(--scroll-shadow-size)), transparent); + } + + [data-bottom-scroll="true"] { + mask-image: linear-gradient(180deg, #000 calc(100% - var(--scroll-shadow-size)), transparent); + } + + [data-top-bottom-scroll="true"] { + mask-image: linear-gradient(#000, #000, transparent 0, #000 var(--scroll-shadow-size), #000 calc(100% - var(--scroll-shadow-size)), transparent); + } + + ` + return { scrollbarBackground, scrollbar, darkScrollbarBackground, darkScrollbar, + scrollbar1, } } \ No newline at end of file diff --git a/src/layout/TwoColPageLayout.tsx b/src/layout/TwoColPageLayout.tsx index 193d3d1..512d225 100644 --- a/src/layout/TwoColPageLayout.tsx +++ b/src/layout/TwoColPageLayout.tsx @@ -9,14 +9,15 @@ interface ITreePageLayoutProps { draggableProps?: DraggablePanelProps pageProps?: PageContainerProps leftPanel?: React.ReactNode + className?: string } -export const TwoColPageLayout: React.FC = (props) => { +export const TwoColPageLayout: React.FC = ({ className, ...props }) => { const { styles, cx } = useStyle({ className: 'two-col' }) return ( diff --git a/src/layout/style.ts b/src/layout/style.ts index f0f850c..38ea469 100644 --- a/src/layout/style.ts +++ b/src/layout/style.ts @@ -48,8 +48,8 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) .ant-pro-card-body{ padding-block-start: 0; overflow: auto; - height: calc(100vh - 100px); - min-height: calc(100vh - 100px); + height: calc(100vh - 110px); + min-height: calc(100vh - 110px); ${scrollbarBackground} } diff --git a/src/pages/system/menus/index.tsx b/src/pages/system/menus/index.tsx index 3765efc..e81e931 100644 --- a/src/pages/system/menus/index.tsx +++ b/src/pages/system/menus/index.tsx @@ -24,6 +24,7 @@ const Menus = () => { const [ currentMenu, setMenuData ] = useAtom(selectedMenuAtom) ?? {} const menuInputRef = useRef(undefined) + useEffect(() => { if (isError) { @@ -42,6 +43,7 @@ const Menus = () => { return ( { const prefix = `${prefixCls}-${token?.proPrefix}-menu-page` - const { scrollbarBackground } = useScrollStyle() + const { scrollbarBackground, scrollbar1 } = useScrollStyle() + const container = css` + ${scrollbar1} + ` const box = css` flex: 1; background: ${token.colorBgContainer}; + ` + + const emptyForm = css` ` @@ -33,7 +39,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { const formButtons = css` width: 500px; .ant-pro-card-body { - overflow: hidden; + //overflow: hidden; } ` @@ -81,7 +87,7 @@ export const useStyle = createStyles(({ token, css, cx, prefixCls }) => { ` return { - container: cx(prefix), + container: cx(prefix, container), box, emptyForm, tree,