mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 12:34:22 +08:00
feat: 添加操作弹窗自定义滚动条
This commit is contained in:
@@ -305,6 +305,15 @@ html.platform-macos body {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ss-home-operation-morph-modal .ant-modal-body {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ss-home-operation-morph-modal .ant-modal-body::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ss-home-operation-morph-modal.ss-operation-morph-active .ant-modal-container,
|
.ss-home-operation-morph-modal.ss-operation-morph-active .ant-modal-container,
|
||||||
.ss-home-operation-morph-modal.ss-operation-morph-active .ant-modal-content {
|
.ss-home-operation-morph-modal.ss-operation-morph-active .ant-modal-content {
|
||||||
animation: ss-operation-shell-in 480ms cubic-bezier(0.2, 0, 0, 1) both;
|
animation: ss-operation-shell-in 480ms cubic-bezier(0.2, 0, 0, 1) both;
|
||||||
@@ -373,6 +382,28 @@ html.platform-macos body {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ss-operation-designed-scrollbar {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1002;
|
||||||
|
width: 5px;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: ss-operation-scrollbar-in 160ms ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ss-operation-designed-scrollbar > span {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 5px;
|
||||||
|
min-height: 28px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(38, 38, 38, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ss-operation-scrollbar-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
.ss-home-operation-morph-modal.ss-operation-morph-active .ss-operation-designed-name,
|
.ss-home-operation-morph-modal.ss-operation-morph-active .ss-operation-designed-name,
|
||||||
.ss-home-operation-morph-modal.ss-operation-morph-closing .ss-operation-designed-name {
|
.ss-home-operation-morph-modal.ss-operation-morph-closing .ss-operation-designed-name {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|||||||
+127
-24
@@ -72,6 +72,15 @@ interface operationMorphElementRects {
|
|||||||
surfaceBorderRadius: string | null
|
surfaceBorderRadius: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface operationScrollbarState {
|
||||||
|
visible: boolean
|
||||||
|
left: number
|
||||||
|
top: number
|
||||||
|
height: number
|
||||||
|
thumbTop: number
|
||||||
|
thumbHeight: number
|
||||||
|
}
|
||||||
|
|
||||||
const getOperationElementMorph = (
|
const getOperationElementMorph = (
|
||||||
targetRect: DOMRect,
|
targetRect: DOMRect,
|
||||||
sourceRect: DOMRect
|
sourceRect: DOMRect
|
||||||
@@ -166,6 +175,14 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
const [batchMode, setBatchMode] = useState(false)
|
const [batchMode, setBatchMode] = useState(false)
|
||||||
const [selectedStudentIds, setSelectedStudentIds] = useState<number[]>([])
|
const [selectedStudentIds, setSelectedStudentIds] = useState<number[]>([])
|
||||||
const [operationVisible, setOperationVisible] = useState(false)
|
const [operationVisible, setOperationVisible] = useState(false)
|
||||||
|
const [operationScrollbar, setOperationScrollbar] = useState<operationScrollbarState>({
|
||||||
|
visible: false,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
height: 0,
|
||||||
|
thumbTop: 0,
|
||||||
|
thumbHeight: 0,
|
||||||
|
})
|
||||||
const [operationOriginRect, setOperationOriginRect] = useState<DOMRect | null>(null)
|
const [operationOriginRect, setOperationOriginRect] = useState<DOMRect | null>(null)
|
||||||
const [operationOriginElements, setOperationOriginElements] =
|
const [operationOriginElements, setOperationOriginElements] =
|
||||||
useState<operationMorphElementRects | null>(null)
|
useState<operationMorphElementRects | null>(null)
|
||||||
@@ -205,6 +222,70 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
const operationMorphEasing = "cubic-bezier(0.2, 0, 0, 1)"
|
const operationMorphEasing = "cubic-bezier(0.2, 0, 0, 1)"
|
||||||
const operationMorphCloseEasing = "cubic-bezier(0.4, 0, 0.2, 1)"
|
const operationMorphCloseEasing = "cubic-bezier(0.4, 0, 0.2, 1)"
|
||||||
|
|
||||||
|
const syncOperationScrollbar = useCallback(() => {
|
||||||
|
if (isPortraitMode || !operationVisible) {
|
||||||
|
setOperationScrollbar((previous) => (previous.visible ? { ...previous, visible: false } : previous))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const modalBody = document.querySelector(
|
||||||
|
`.${operationModalClass} .ant-modal-body`
|
||||||
|
) as HTMLElement | null
|
||||||
|
if (!modalBody) return
|
||||||
|
|
||||||
|
const computedStyle = window.getComputedStyle(modalBody)
|
||||||
|
const canScroll =
|
||||||
|
computedStyle.overflowY !== "visible" &&
|
||||||
|
modalBody.scrollHeight - modalBody.clientHeight > 1 &&
|
||||||
|
modalBody.clientHeight > 0
|
||||||
|
if (!canScroll) {
|
||||||
|
setOperationScrollbar((previous) => (previous.visible ? { ...previous, visible: false } : previous))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const bodyRect = modalBody.getBoundingClientRect()
|
||||||
|
const trackHeight = Math.max(0, modalBody.clientHeight - 16)
|
||||||
|
const thumbHeight = Math.min(
|
||||||
|
trackHeight,
|
||||||
|
Math.max(28, (modalBody.clientHeight * trackHeight) / modalBody.scrollHeight)
|
||||||
|
)
|
||||||
|
const thumbTop =
|
||||||
|
(modalBody.scrollTop / Math.max(1, modalBody.scrollHeight - modalBody.clientHeight)) *
|
||||||
|
Math.max(0, trackHeight - thumbHeight)
|
||||||
|
setOperationScrollbar({
|
||||||
|
visible: true,
|
||||||
|
left: bodyRect.right - 12,
|
||||||
|
top: bodyRect.top + 8,
|
||||||
|
height: trackHeight,
|
||||||
|
thumbTop,
|
||||||
|
thumbHeight,
|
||||||
|
})
|
||||||
|
}, [isPortraitMode, operationModalClass, operationVisible])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPortraitMode || !operationVisible) {
|
||||||
|
setOperationScrollbar((previous) => (previous.visible ? { ...previous, visible: false } : previous))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const modalBody = document.querySelector(
|
||||||
|
`.${operationModalClass} .ant-modal-body`
|
||||||
|
) as HTMLElement | null
|
||||||
|
if (!modalBody) return
|
||||||
|
|
||||||
|
const frame = window.requestAnimationFrame(syncOperationScrollbar)
|
||||||
|
const delayedSync = window.setTimeout(syncOperationScrollbar, operationMorphOpenDuration + 24)
|
||||||
|
modalBody.addEventListener("scroll", syncOperationScrollbar, { passive: true })
|
||||||
|
const resizeObserver =
|
||||||
|
typeof ResizeObserver === "undefined" ? null : new ResizeObserver(syncOperationScrollbar)
|
||||||
|
resizeObserver?.observe(modalBody)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.cancelAnimationFrame(frame)
|
||||||
|
window.clearTimeout(delayedSync)
|
||||||
|
modalBody.removeEventListener("scroll", syncOperationScrollbar)
|
||||||
|
resizeObserver?.disconnect()
|
||||||
|
}
|
||||||
|
}, [isPortraitMode, operationMorphOpenDuration, operationVisible, operationModalClass, syncOperationScrollbar])
|
||||||
|
|
||||||
const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => {
|
const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => {
|
||||||
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
||||||
}
|
}
|
||||||
@@ -1019,6 +1100,7 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
operationMorphRafRef.current = null
|
operationMorphRafRef.current = null
|
||||||
}
|
}
|
||||||
operationClosingRef.current = false
|
operationClosingRef.current = false
|
||||||
|
setOperationScrollbar((previous) => (previous.visible ? { ...previous, visible: false } : previous))
|
||||||
setOperationVisible(false)
|
setOperationVisible(false)
|
||||||
setOperationOriginRect(null)
|
setOperationOriginRect(null)
|
||||||
setOperationOriginElements(null)
|
setOperationOriginElements(null)
|
||||||
@@ -1032,6 +1114,7 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
playState: operationMorphAnimationRef.current?.playState,
|
playState: operationMorphAnimationRef.current?.playState,
|
||||||
currentTime: operationMorphAnimationRef.current?.currentTime,
|
currentTime: operationMorphAnimationRef.current?.currentTime,
|
||||||
})
|
})
|
||||||
|
setOperationScrollbar((previous) => (previous.visible ? { ...previous, visible: false } : previous))
|
||||||
if (operationMorphRafRef.current !== null) {
|
if (operationMorphRafRef.current !== null) {
|
||||||
window.cancelAnimationFrame(operationMorphRafRef.current)
|
window.cancelAnimationFrame(operationMorphRafRef.current)
|
||||||
operationMorphRafRef.current = null
|
operationMorphRafRef.current = null
|
||||||
@@ -4181,30 +4264,50 @@ export const Home: React.FC<HomeProps> = ({
|
|||||||
{operationPanelContent}
|
{operationPanelContent}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
) : (
|
) : (
|
||||||
<Modal
|
<>
|
||||||
title={null}
|
<Modal
|
||||||
open={operationVisible}
|
title={null}
|
||||||
onCancel={closeOperationModal}
|
open={operationVisible}
|
||||||
closable={false}
|
onCancel={closeOperationModal}
|
||||||
footer={null}
|
closable={false}
|
||||||
width={820}
|
footer={null}
|
||||||
centered
|
width={820}
|
||||||
forceRender
|
centered
|
||||||
transitionName=""
|
forceRender
|
||||||
maskTransitionName=""
|
transitionName=""
|
||||||
rootClassName={operationModalRootClass}
|
maskTransitionName=""
|
||||||
className={operationModalClass}
|
rootClassName={operationModalRootClass}
|
||||||
styles={{
|
className={operationModalClass}
|
||||||
body: {
|
styles={{
|
||||||
maxHeight: "calc(100vh - 140px)",
|
body: {
|
||||||
padding: "0 28px 34px",
|
maxHeight: "calc(100vh - 140px)",
|
||||||
overflowY: "auto",
|
padding: "0 28px 34px",
|
||||||
},
|
overflowY: "auto",
|
||||||
}}
|
},
|
||||||
destroyOnHidden
|
}}
|
||||||
>
|
destroyOnHidden
|
||||||
{operationPanelContent}
|
>
|
||||||
</Modal>
|
{operationPanelContent}
|
||||||
|
</Modal>
|
||||||
|
{operationScrollbar.visible && (
|
||||||
|
<div
|
||||||
|
className="ss-operation-designed-scrollbar"
|
||||||
|
aria-hidden="true"
|
||||||
|
style={{
|
||||||
|
left: operationScrollbar.left,
|
||||||
|
top: operationScrollbar.top,
|
||||||
|
height: operationScrollbar.height,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
top: operationScrollbar.thumbTop,
|
||||||
|
height: operationScrollbar.thumbHeight,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user