更换 UI

This commit is contained in:
Cheney
2026-08-06 09:32:15 +08:00
parent 28d1b5d1c6
commit 7f72bf809b
21 changed files with 767 additions and 2277 deletions
+27 -21
View File
@@ -1,9 +1,12 @@
import { useRef, useState, useEffect } from 'react'
import { Tabs } from 'antd-mobile'
// 滑屏容器 + 顶栏 tabs
// - 顶部: antd-mobile Tabs (带底部激活线动画, 仿原生)
// - 下方: 横向滑屏容器, 手势切换 tab
// props:
// tabs: [{ key, label, render }]
// active: 当前激活 key
// tabs: [{ key, label, render }]
// active: 当前激活 key
// onChange(key): 切换回调 (用户滑动或点击 tab)
export default function SwipeTabs({ tabs, active, onChange }) {
const idx = Math.max(0, tabs.findIndex((t) => t.key === active))
@@ -12,10 +15,21 @@ export default function SwipeTabs({ tabs, active, onChange }) {
const [animOff, setAnimOff] = useState(false)
const startX = useRef(0)
const containerRef = useRef(null)
const widthRef = useRef(0)
// idx 变化时关闭拖动动画 (避免快速切换时滑动残影)
useEffect(() => { setOffsetPx(0); setAnimOff(false) }, [idx])
// 监听容器宽度变化, 拖动偏移按宽度百分比计算
useEffect(() => {
const el = containerRef.current
if (!el) return
const ro = new ResizeObserver(() => { widthRef.current = el.offsetWidth })
ro.observe(el)
widthRef.current = el.offsetWidth
return () => ro.disconnect()
}, [])
function onTouchStart(e) {
startX.current = e.touches[0].clientX
setDragging(true)
@@ -24,7 +38,7 @@ export default function SwipeTabs({ tabs, active, onChange }) {
function onTouchMove(e) {
if (!dragging) return
const dx = e.touches[0].clientX - startX.current
const w = containerRef.current?.offsetWidth || window.innerWidth
const w = widthRef.current || window.innerWidth
// 限制左右溢出 (超过边界阻力增大)
const limited = Math.max(Math.min(dx, w * 0.4), -w * 0.4)
setOffsetPx(limited)
@@ -33,7 +47,7 @@ export default function SwipeTabs({ tabs, active, onChange }) {
if (!dragging) return
setDragging(false)
setAnimOff(false)
const w = containerRef.current?.offsetWidth || window.innerWidth
const w = widthRef.current || window.innerWidth
const dx = offsetPx
let next = idx
if (dx < -w * 0.18 && idx < tabs.length - 1) next = idx + 1
@@ -44,26 +58,18 @@ export default function SwipeTabs({ tabs, active, onChange }) {
// 用 -33.33% × idx 作为基础位移, 再叠加拖动偏移
const basePct = idx * 33.3333
const dragPct = containerRef.current
? (offsetPx / containerRef.current.offsetWidth) * 100
const dragPct = widthRef.current
? (offsetPx / widthRef.current) * 100
: 0
const translate = `calc(-${basePct}% + ${dragPct}%)`
return (
<>
<div className="top-tabs" role="tablist">
<div className="swipe-tabs-wrap">
<Tabs activeKey={active} onChange={onChange}>
{tabs.map((t) => (
<button
key={t.key}
role="tab"
aria-selected={t.key === active}
className={'top-tabs-item' + (t.key === active ? ' active' : '')}
onClick={() => onChange(t.key)}
>
{t.label}
</button>
<Tabs.Tab key={t.key} title={t.label} />
))}
</div>
</Tabs>
<div
ref={containerRef}
className="swipe-container"
@@ -74,13 +80,13 @@ export default function SwipeTabs({ tabs, active, onChange }) {
>
<div
className={'swipe-track' + (animOff || dragging ? ' no-anim' : '')}
style={{ '--swipe-offset': translate, transform: `translateX(${translate})` }}
style={{ transform: `translateX(${translate})` }}
>
{tabs.map((t) => (
<div key={t.key} className="swipe-pane">{t.render()}</div>
))}
</div>
</div>
</>
</div>
)
}
}