mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 21:14:21 +08:00
feat(导航): 添加快速导航滑动功能并优化交互体验
- 在快速导航组件中添加滑动选择功能,提升操作便捷性 - 移除单个导航项的点击事件,改为父容器统一处理 - 禁用全局文本选择,但保留输入框的文本选择功能 - 将滚动行为从平滑滚动改为自动滚动以提高响应速度
This commit is contained in:
@@ -8,6 +8,12 @@ body,
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
user-select: none; /* 禁用文本选择 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 如果某些输入框需要允许选择,可以单独开启 */
|
||||||
|
input, textarea {
|
||||||
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ss-sidebar {
|
.ss-sidebar {
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
const scrollToGroup = (key: string) => {
|
const scrollToGroup = (key: string) => {
|
||||||
const element = groupRefs.current[key]
|
const element = groupRefs.current[key]
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
element.scrollIntoView({ behavior: 'auto', block: 'start' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,12 +398,56 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 快速导航滑动处理
|
||||||
|
const navContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const isNavDragging = useRef(false)
|
||||||
|
|
||||||
|
const handleNavAction = useCallback((clientY: number) => {
|
||||||
|
if (!navContainerRef.current) return
|
||||||
|
const rect = navContainerRef.current.getBoundingClientRect()
|
||||||
|
const y = clientY - rect.top
|
||||||
|
const items = navContainerRef.current.children
|
||||||
|
const itemCount = items.length
|
||||||
|
if (itemCount === 0) return
|
||||||
|
|
||||||
|
// 计算当前指向第几个项
|
||||||
|
const itemHeight = rect.height / itemCount
|
||||||
|
const index = Math.floor(y / itemHeight)
|
||||||
|
const safeIndex = Math.max(0, Math.min(itemCount - 1, index))
|
||||||
|
|
||||||
|
const targetGroup = groupedStudents[safeIndex]
|
||||||
|
if (targetGroup) {
|
||||||
|
scrollToGroup(targetGroup.key)
|
||||||
|
}
|
||||||
|
}, [groupedStudents])
|
||||||
|
|
||||||
|
const onNavMouseDown = (e: React.MouseEvent) => {
|
||||||
|
isNavDragging.current = true
|
||||||
|
handleNavAction(e.clientY)
|
||||||
|
document.addEventListener('mousemove', onGlobalMouseMove)
|
||||||
|
document.addEventListener('mouseup', onGlobalMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onGlobalMouseMove = (e: MouseEvent) => {
|
||||||
|
if (isNavDragging.current) {
|
||||||
|
handleNavAction(e.clientY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onGlobalMouseUp = () => {
|
||||||
|
isNavDragging.current = false
|
||||||
|
document.removeEventListener('mousemove', onGlobalMouseMove)
|
||||||
|
document.removeEventListener('mouseup', onGlobalMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染快速导航
|
// 渲染快速导航
|
||||||
const renderQuickNav = () => {
|
const renderQuickNav = () => {
|
||||||
if (groupedStudents.length <= 1 || sortType === 'score' || (sortType === 'alphabet' && searchKeyword)) return null
|
if (groupedStudents.length <= 1 || sortType === 'score' || (sortType === 'alphabet' && searchKeyword)) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={navContainerRef}
|
||||||
|
onMouseDown={onNavMouseDown}
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
right: '12px',
|
right: '12px',
|
||||||
@@ -411,21 +455,20 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
transform: 'translateY(-50%)',
|
transform: 'translateY(-50%)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '2px',
|
|
||||||
backgroundColor: 'var(--ss-card-bg)',
|
backgroundColor: 'var(--ss-card-bg)',
|
||||||
padding: '8px 4px',
|
padding: '8px 4px',
|
||||||
borderRadius: '20px',
|
borderRadius: '20px',
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
maxHeight: '80vh',
|
maxHeight: '80vh',
|
||||||
overflowY: 'auto',
|
border: '1px solid var(--ss-border-color)',
|
||||||
border: '1px solid var(--ss-border-color)'
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{groupedStudents.map((group) => (
|
{groupedStudents.map((group) => (
|
||||||
<div
|
<div
|
||||||
key={group.key}
|
key={group.key}
|
||||||
onClick={() => scrollToGroup(group.key)}
|
|
||||||
style={{
|
style={{
|
||||||
width: '24px',
|
width: '24px',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
@@ -435,12 +478,9 @@ export const Home: React.FC<{ canEdit: boolean }> = ({ canEdit }) => {
|
|||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: 'var(--td-brand-color)',
|
color: 'var(--td-brand-color)',
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
transition: 'background 0.2s'
|
pointerEvents: 'none' // 让事件由父容器统一处理
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = 'var(--td-brand-color-1)')}
|
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
||||||
>
|
>
|
||||||
{group.key}
|
{group.key}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user