refactor(eq): 重构EQ页面及相关代码以优化PEQ管理与同步
- 禁用Vite配置中的manus debug collector插件,减少无用请求 - 对DeviceContext添加syncPeqCatalog方法用于同步PEQ状态 - 重构luxsinApi.ts中PEQ请求相关代码,统一POST请求封装,增强错误处理 - 将EQPage相关常量、工具和组件抽取至独立模块,简化主文件结构 - 使用缓存机制记忆预设滤波器列表,减少不必要的重复计算 - 优化PEQ状态同步逻辑,支持按模式(A/B)分别保存与应用滤波器 - 实现选择耳机型号时自动保存并更新对应滤波器缓存和显示 - 修改A/B切换逻辑,使用防抖同步和固定的peqApply调用 - 为音量滑块添加thumb-only滑动样式及拖拽区域限制,提升交互体验 - Home页面ListRow组件支持显示自定义缩略图 - 增加eq/constants.ts定义滤波器类型、参数范围和样式,统一管理配置 - 解决多处EQ页面组件交互和状态切换的潜在同步问题,提高代码维护性和可读性
This commit is contained in:
@@ -137,7 +137,7 @@ function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: bool
|
||||
|
||||
// ── List row ──
|
||||
function ListRow({
|
||||
icon, label, value, onClick, toggle, checked, onToggle,
|
||||
icon, label, value, onClick, toggle, checked, onToggle, thumbnail,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
@@ -146,6 +146,7 @@ function ListRow({
|
||||
toggle?: boolean;
|
||||
checked?: boolean;
|
||||
onToggle?: (v: boolean) => void;
|
||||
thumbnail?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="ios-list-row cursor-pointer" onClick={onClick}>
|
||||
@@ -160,6 +161,13 @@ function ListRow({
|
||||
<IOSToggle checked={!!checked} onChange={onToggle ?? (() => {})} />
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
{thumbnail && (
|
||||
<img
|
||||
src={thumbnail}
|
||||
alt={value ?? label}
|
||||
className="h-8 w-14 object-contain rounded bg-black/20 border border-white/10"
|
||||
/>
|
||||
)}
|
||||
{value && <span className="ios-row-value">{value}</span>}
|
||||
<ChevronRight size={16} className="ios-chevron" />
|
||||
</div>
|
||||
@@ -217,6 +225,8 @@ export default function Home() {
|
||||
|
||||
const [localVol, setLocalVol] = useState(deviceState?.volume ?? 100);
|
||||
const isDragging = useRef(false);
|
||||
const volumeSliderRef = useRef<HTMLInputElement>(null);
|
||||
const volumeSliderDragAllowedRef = useRef(false);
|
||||
const knobDraggingRef = useRef(false);
|
||||
const knobActivePointerIdRef = useRef<number | null>(null);
|
||||
const knobStartAngleRef = useRef(0);
|
||||
@@ -233,6 +243,16 @@ export default function Home() {
|
||||
knobLastVolRef.current = localVol;
|
||||
}, [localVol]);
|
||||
|
||||
const isPointerOnVolumeSliderThumb = useCallback((clientX: number) => {
|
||||
const el = volumeSliderRef.current;
|
||||
if (!el) return false;
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width <= 0) return false;
|
||||
const thumbCenterX = rect.left + (localVol / 200) * rect.width;
|
||||
const hitRadius = Math.max(28, rect.width * 0.05);
|
||||
return Math.abs(clientX - thumbCenterX) <= hitRadius;
|
||||
}, [localVol]);
|
||||
|
||||
const handlePowerOff = useCallback(async () => {
|
||||
if (powerActionRef.current) return;
|
||||
powerActionRef.current = true;
|
||||
@@ -662,29 +682,51 @@ export default function Home() {
|
||||
boxShadow: "0 0 8px rgba(0,255,246,0.5)"
|
||||
}} />
|
||||
<input
|
||||
ref={volumeSliderRef}
|
||||
type="range" min={0} max={200} value={localVol}
|
||||
disabled={volumePassthroughActive}
|
||||
className="cyan-slider relative z-10 disabled:opacity-50"
|
||||
className="cyan-slider cyan-slider-thumb-only relative z-10 disabled:opacity-50"
|
||||
onChange={(e) => {
|
||||
if (volumePassthroughActive) return;
|
||||
if (!volumeSliderDragAllowedRef.current) {
|
||||
e.target.value = String(localVol);
|
||||
return;
|
||||
}
|
||||
const v = Number(e.target.value);
|
||||
setLocalVol(v);
|
||||
}}
|
||||
onPointerDown={() => {
|
||||
onPointerDown={(e) => {
|
||||
if (volumePassthroughActive) return;
|
||||
const onThumb = isPointerOnVolumeSliderThumb(e.clientX);
|
||||
volumeSliderDragAllowedRef.current = onThumb;
|
||||
if (!onThumb) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
isDragging.current = true;
|
||||
}}
|
||||
onPointerUp={(e) => {
|
||||
const allowed = volumeSliderDragAllowedRef.current;
|
||||
volumeSliderDragAllowedRef.current = false;
|
||||
isDragging.current = false;
|
||||
if (!allowed || volumePassthroughActive) return;
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
}}
|
||||
onPointerCancel={(e) => {
|
||||
const allowed = volumeSliderDragAllowedRef.current;
|
||||
volumeSliderDragAllowedRef.current = false;
|
||||
isDragging.current = false;
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
if (!volumePassthroughActive && allowed) {
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
const allowed = volumeSliderDragAllowedRef.current;
|
||||
volumeSliderDragAllowedRef.current = false;
|
||||
isDragging.current = false;
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
if (!volumePassthroughActive && allowed) {
|
||||
applyVolume(Number(e.currentTarget.value));
|
||||
}
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
const k = e.key;
|
||||
@@ -916,6 +958,7 @@ export default function Home() {
|
||||
icon={<Gauge size={16} />}
|
||||
label={homeText.vu ?? "VU表"}
|
||||
value={vuLabel}
|
||||
thumbnail={`${import.meta.env.BASE_URL}vu/vu${(ds.vu ?? 0) + 1}.png`}
|
||||
onClick={() => setLocation("/vu")}
|
||||
/>
|
||||
<ListRow
|
||||
|
||||
Reference in New Issue
Block a user