feat(导航): 添加快速导航滑动功能并优化交互体验

- 在快速导航组件中添加滑动选择功能,提升操作便捷性
- 移除单个导航项的点击事件,改为父容器统一处理
- 禁用全局文本选择,但保留输入框的文本选择功能
- 将滚动行为从平滑滚动改为自动滚动以提高响应速度
This commit is contained in:
Linkon
2026-01-18 21:29:29 +08:00
parent f5da065d8c
commit db699f0c41
2 changed files with 55 additions and 9 deletions
+6
View File
@@ -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 {
+49 -9
View File
@@ -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>