feat: 搜索框下滚动、本地服务卡片排序、拖拽末位修复

- 恢复内容区内部滚动:页头/搜索框固定,滚动区域从搜索框下方开始;侧栏跟随内容滚动,侧栏滚轮驱动内容区
- v22:本地服务以 card 行 local-service 参与排序;首页按卡片顺序渲染,后台可拖动排序
- 本地服务卡片后端禁止删除/改名/配置分组
- 修复向下拖动时插到目标之后,倒数第二项可拖到最后
- 验证:搜索框位置固定、侧栏 scrollTop 跟随、本地服务与倒数第二拖末位均落库、首页顺序一致
This commit is contained in:
2026-10-31 15:30:09 +08:00
parent 0e6c772466
commit 57ab90a1fa
7 changed files with 116 additions and 40 deletions
+41 -4
View File
@@ -205,6 +205,39 @@ export default function AppNext() {
}
}, [])
useEffect(() => {
const content = document.getElementById('content-scroll')
const sider = document.querySelector('aside')
if (!(content instanceof HTMLElement) || !(sider instanceof HTMLElement)) return
// 左右两栏共用一个滚动位置:侧栏没有独立滚动条,滚轮也驱动内容区
sider.id = 'sidebar-scroll'
sider.style.overflow = 'hidden'
let frame = 0
const sync = () => {
if (frame !== 0) return
frame = window.requestAnimationFrame(() => {
frame = 0
sider.scrollTop = content.scrollTop
})
}
const onWheel = (event: WheelEvent) => {
event.preventDefault()
content.scrollTop += event.deltaY
}
content.addEventListener('scroll', sync, { passive: true })
sider.addEventListener('wheel', onWheel, { passive: false })
return () => {
content.removeEventListener('scroll', sync)
sider.removeEventListener('wheel', onWheel)
if (frame !== 0) window.cancelAnimationFrame(frame)
sider.id = ''
sider.style.overflow = ''
}
}, [])
const site: SiteSettings = settings?.settings ?? {
header: { enabled: true, speed: 24, items: [] },
sidebar: {
@@ -311,7 +344,7 @@ export default function AppNext() {
)
}
function renderLocalService() {
function renderLocalService(card: CardItem) {
const items: AppItem[] = projects.map((project) => ({
id: project.name,
name: project.name,
@@ -325,7 +358,7 @@ export default function AppNext() {
}))
return (
<SectionCard title="本地服务" count={items.length} barColor="#3858f6">
<SectionCard key={card.id} title="本地服务" count={items.length} barColor="#3858f6">
{items.length > 0 ? (
<AppGrid items={items} />
) : (
@@ -367,8 +400,9 @@ export default function AppNext() {
<div className="home-view">
{loading && <Empty text="正在读取内容库…" />}
{!loading && cards.length === 0 && <Empty text="后台还没有创建首页卡片" />}
{renderLocalService()}
{cards.map(renderCard)}
{cards.map((card) =>
card.key === 'local-service' ? renderLocalService(card) : renderCard(card),
)}
{keywordLower !== '' && cards.length > 0 && (
<Notice title={`搜索结果:${keyword.trim()}`}>在卡片与分组内按标题过滤。</Notice>
)}
@@ -547,6 +581,9 @@ export default function AppNext() {
siderBordered={false}
contentPadding="none"
contentOffset={88}
height="screen"
scrollable
hideScrollbar
contentId="content-scroll"
className="app"
header={