Refactor build and start scripts in package.json; add plugin to exclude debug files from production build in vite.config.ts; remove ConnectScreen component and replace with ConnectionPlaceholder in multiple pages; update DeviceContext to auto-connect using device IP from URL; enhance locale files with new device IP labels.
This commit is contained in:
@@ -1,102 +0,0 @@
|
||||
/* ============================================================
|
||||
CONNECT SCREEN — Luxsin X9 Controller
|
||||
Design: Pure black iOS App style, glass card, #00FFF6 accent
|
||||
============================================================ */
|
||||
import { useDevice } from "@/contexts/DeviceContext";
|
||||
import { Loader2, Wifi } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ConnectScreen() {
|
||||
const { ip, setIp, connect, isConnecting, error, setDemoMode, isDemoMode } = useDevice();
|
||||
const [inputIp, setInputIp] = useState(ip);
|
||||
|
||||
const handleConnect = async () => {
|
||||
const targetIp = inputIp.trim();
|
||||
if (!targetIp) return;
|
||||
setIp(targetIp);
|
||||
setDemoMode(false);
|
||||
await connect(false, targetIp);
|
||||
};
|
||||
|
||||
const handleDemo = async () => {
|
||||
setDemoMode(true);
|
||||
await connect(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black flex flex-col items-center justify-center px-6">
|
||||
{/* Logo / Brand */}
|
||||
<div className="mb-10 flex flex-col items-center">
|
||||
<div
|
||||
className="mb-4 flex size-[5.5rem] shrink-0 items-center justify-center rounded-[17px] border border-white/15 bg-white/[0.02]"
|
||||
>
|
||||
<img
|
||||
src={`${import.meta.env.BASE_URL}logo.png`}
|
||||
alt="Luxsin"
|
||||
className="h-auto max-h-[3.75rem] w-auto max-w-[3.75rem] object-contain"
|
||||
/>
|
||||
</div>
|
||||
<h1 className="text-[16px] font-semibold tracking-tight text-white/70">
|
||||
Luxsin X8 Controller
|
||||
</h1>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Connection card */}
|
||||
<div className="w-full max-w-sm rounded-[20px] p-6 space-y-4"
|
||||
style={{
|
||||
background: "rgba(28,28,30,0.95)",
|
||||
border: "1px solid rgba(255,255,255,0.07)",
|
||||
backdropFilter: "blur(20px)",
|
||||
}}>
|
||||
<div>
|
||||
<label className="text-[13px] text-white/40 mb-2 block tracking-wide uppercase" style={{ letterSpacing: "0.06em" }}>
|
||||
设备 IP 地址
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Wifi size={14} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-white/25" />
|
||||
<input
|
||||
type="text"
|
||||
value={inputIp}
|
||||
onChange={(e) => setInputIp(e.target.value)}
|
||||
placeholder="192.168.2.30"
|
||||
className="w-full pl-9 pr-4 py-3.5 rounded-[12px] text-[15px] text-white outline-none transition-all"
|
||||
style={{
|
||||
background: "rgba(44,44,46,0.8)",
|
||||
border: "1px solid rgba(255,255,255,0.08)",
|
||||
fontFamily: "inherit",
|
||||
}}
|
||||
onFocus={(e) => { e.target.style.borderColor = "rgba(0,255,246,0.4)"; e.target.style.boxShadow = "0 0 0 3px rgba(0,255,246,0.06)"; }}
|
||||
onBlur={(e) => { e.target.style.borderColor = "rgba(255,255,255,0.08)"; e.target.style.boxShadow = "none"; }}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleConnect()}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[12px] text-white/25 mt-1.5">请确保与设备处于同一局域网</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="px-3.5 py-2.5 rounded-[10px] text-[13px]"
|
||||
style={{ background: "rgba(239,68,68,0.08)", color: "#f87171", border: "1px solid rgba(239,68,68,0.15)" }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handleConnect}
|
||||
disabled={isConnecting || !inputIp}
|
||||
className="w-full py-3.5 rounded-[12px] text-[16px] font-semibold text-black transition-all duration-150 active:scale-[0.98] disabled:opacity-40 flex items-center justify-center gap-2"
|
||||
style={{
|
||||
background: "#00FFF6",
|
||||
boxShadow: isConnecting ? "none" : "0 0 24px rgba(0,255,246,0.25)",
|
||||
}}>
|
||||
{isConnecting && !isDemoMode ? (
|
||||
<><Loader2 size={17} className="animate-spin" /> 连接中...</>
|
||||
) : (
|
||||
<><Wifi size={17} /> 连接设备</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useDevice } from "@/contexts/DeviceContext";
|
||||
import { Loader2, Wifi } from "lucide-react";
|
||||
import BottomNav from "@/components/BottomNav";
|
||||
|
||||
/** 自动连接设备时的占位(替代原 ConnectScreen) */
|
||||
export default function ConnectionPlaceholder() {
|
||||
const { ip, isConnecting, error } = useDevice();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black flex flex-col items-center justify-center px-6 pb-24">
|
||||
<div className="mb-6 flex size-[4.5rem] items-center justify-center rounded-[16px] border border-white/15">
|
||||
<Wifi size={28} className="text-[#00FFF6]/80" />
|
||||
</div>
|
||||
{isConnecting ? (
|
||||
<>
|
||||
<Loader2 size={28} className="animate-spin text-[#00FFF6] mb-3" />
|
||||
<p className="text-[15px] text-white/70">Connecting to {ip}…</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-[15px] text-white/80 mb-2">Connection failed</p>
|
||||
<p className="text-[13px] text-white/45 text-center max-w-xs">
|
||||
{error ?? "Check the device IP and your network connection."}
|
||||
</p>
|
||||
<p className="text-[12px] text-white/30 mt-3">
|
||||
Specify the device in the URL, e.g.{" "}
|
||||
<span className="text-white/50">?ip=10.0.0.119</span>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<BottomNav />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PeqFilter,
|
||||
PeqState,
|
||||
} from "@/lib/luxsinApi";
|
||||
import { getDeviceIpFromUrl, resolveInitialDeviceIp } from "@/lib/deviceIp";
|
||||
|
||||
interface DeviceContextType {
|
||||
ip: string;
|
||||
@@ -39,7 +40,7 @@ interface DeviceContextType {
|
||||
const DeviceContext = createContext<DeviceContextType | null>(null);
|
||||
|
||||
export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
||||
const [ip, setIp] = useState(() => localStorage.getItem("luxsin_ip") || "192.168.2.30");
|
||||
const [ip, setIp] = useState(resolveInitialDeviceIp);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
const [isDemoMode, setDemoMode] = useState(false);
|
||||
@@ -51,6 +52,7 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
||||
const [api, setApi] = useState<LuxsinAPI | null>(null);
|
||||
const lastMsgCountRef = useRef<number>(-1);
|
||||
const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const autoConnectStartedRef = useRef(false);
|
||||
|
||||
const saveIp = useCallback((newIp: string) => {
|
||||
setIp(newIp);
|
||||
@@ -202,6 +204,17 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
|
||||
if (!isDemoMode && api) api.setBalance(v).catch(console.error);
|
||||
}, [api, isDemoMode]);
|
||||
|
||||
// 启动时根据 ?ip= 或已存 IP 自动连接(默认进入首页,无需连接页)
|
||||
useEffect(() => {
|
||||
if (autoConnectStartedRef.current) return;
|
||||
autoConnectStartedRef.current = true;
|
||||
|
||||
const urlIp = getDeviceIpFromUrl();
|
||||
const targetIp = urlIp ?? ip;
|
||||
if (urlIp) saveIp(urlIp);
|
||||
void connect(false, targetIp);
|
||||
}, [connect, ip, saveIp]);
|
||||
|
||||
// Polling for changes
|
||||
useEffect(() => {
|
||||
if (!isConnected || isDemoMode || !api) return;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/** 从地址栏 ?ip= 读取设备 IP(hash 路由下 query 仍在 search 上) */
|
||||
export function getDeviceIpFromUrl(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
const ip = new URLSearchParams(window.location.search).get("ip");
|
||||
const trimmed = ip?.trim();
|
||||
return trimmed || null;
|
||||
}
|
||||
|
||||
export function resolveInitialDeviceIp(): string {
|
||||
return getDeviceIpFromUrl() ?? localStorage.getItem("luxsin_ip") ?? "192.168.2.30";
|
||||
}
|
||||
@@ -12,7 +12,8 @@
|
||||
"powerOffConfirmCancel": "Cancel",
|
||||
"powerOffToastDemo": "Demo mode: disconnected from the device.",
|
||||
"powerOffToastSent": "Shutdown command sent.",
|
||||
"doubleClickHint": "Double-click to enter"
|
||||
"doubleClickHint": "Double-click to enter",
|
||||
"backToLegacy": "Legacy version"
|
||||
},
|
||||
"vuPage": {
|
||||
"title": "VU meter gallery"
|
||||
@@ -587,7 +588,7 @@
|
||||
"deviceName": "Device name",
|
||||
"firmwareVersion": "Firmware version",
|
||||
"macAddress": "MAC address",
|
||||
"disconnect": "Disconnect"
|
||||
"deviceIp": "Device IP"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"powerOffConfirmCancel": "取消",
|
||||
"powerOffToastDemo": "演示模式:已中斷與裝置的連線",
|
||||
"powerOffToastSent": "已傳送關機指令",
|
||||
"doubleClickHint": "按兩下進入"
|
||||
"doubleClickHint": "按兩下進入",
|
||||
"backToLegacy": "返回舊版"
|
||||
},
|
||||
"vuPage": {
|
||||
"title": "VU 表庫"
|
||||
@@ -403,7 +404,7 @@
|
||||
"deviceName": "裝置名稱",
|
||||
"firmwareVersion": "韌體版本",
|
||||
"macAddress": "MAC 位址",
|
||||
"disconnect": "中斷連線"
|
||||
"deviceIp": "裝置 IP"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"powerOffConfirmCancel": "取消",
|
||||
"powerOffToastDemo": "演示模式:已断开与设备的连接",
|
||||
"powerOffToastSent": "已发送关机指令",
|
||||
"doubleClickHint": "双击进入"
|
||||
"doubleClickHint": "双击进入",
|
||||
"backToLegacy": "返回旧版"
|
||||
},
|
||||
"vuPage": {
|
||||
"title": "VU 表库"
|
||||
@@ -404,7 +405,7 @@
|
||||
"deviceName": "设备名称",
|
||||
"firmwareVersion": "固件版本",
|
||||
"macAddress": "MAC 地址",
|
||||
"disconnect": "断开连接"
|
||||
"deviceIp": "设备 IP"
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Backend: /sse/question (SSE), /sse/messages, /sse/chats, /sse/clear
|
||||
============================================================ */
|
||||
import BottomNav from "@/components/BottomNav";
|
||||
import ConnectScreen from "@/components/ConnectScreen";
|
||||
import ConnectionPlaceholder from "@/components/ConnectionPlaceholder";
|
||||
import { useDevice } from "@/contexts/DeviceContext";
|
||||
import {
|
||||
AI_SPLIT_RESTORE_PATH_KEY,
|
||||
@@ -337,7 +337,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
||||
})();
|
||||
}, [mac, loadChat]);
|
||||
|
||||
// 没有 mac 时(设备未识别完毕)不应该停在 initializing;ConnectScreen 会拦截
|
||||
// 没有 mac 时(设备未识别完毕)不应该停在 initializing;未连接时 ConnectionPlaceholder 会拦截
|
||||
useEffect(() => {
|
||||
if (!mac && initializing) {
|
||||
// 给 mac 一段缓冲期,否则即便最终没有 mac 也要释放欢迎页
|
||||
@@ -692,7 +692,7 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
|
||||
|
||||
const headerTitle = aiText.title;
|
||||
|
||||
if (!isConnected) return <ConnectScreen />;
|
||||
if (!isConnected) return <ConnectionPlaceholder />;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
+92
-37
@@ -10,7 +10,7 @@
|
||||
6. iOS list rows (输入源/输出端口/音频设置/系统设置/VU表)
|
||||
============================================================ */
|
||||
import { useDevice } from "@/contexts/DeviceContext";
|
||||
import ConnectScreen from "@/components/ConnectScreen";
|
||||
import ConnectionPlaceholder from "@/components/ConnectionPlaceholder";
|
||||
import BottomNav from "@/components/BottomNav";
|
||||
import {
|
||||
AlertDialog,
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
Sliders,
|
||||
Activity,
|
||||
Gauge,
|
||||
Undo2,
|
||||
ChevronDown,
|
||||
Wifi,
|
||||
Bluetooth,
|
||||
@@ -57,14 +58,17 @@ const JACK_EGG_SEQUENCE = ["lg", "smTop", "smBot"] as const;
|
||||
type JackId = (typeof JACK_EGG_SEQUENCE)[number];
|
||||
|
||||
const VU_BAR_COUNT = 20;
|
||||
const EGG_DURATION_MS = 4200;
|
||||
const EGG_BEAT_MS = 130;
|
||||
const EGG_DANCE_DURATION_MS = 5000;
|
||||
const EGG_WAVE_DURATION_MS = 5000;
|
||||
const EGG_WAVE_HALF_MS = EGG_WAVE_DURATION_MS / 2;
|
||||
const EGG_BEAT_MS = 400;
|
||||
const JACK_SEQUENCE_TIMEOUT_MS = 4000;
|
||||
|
||||
/** 旋律主音在 20 段 VU 上的位置(约 4.2s) */
|
||||
/** 旋律主音在 20 段 VU 上的位置(约 5s) */
|
||||
const EGG_MELODY_LEAD = [
|
||||
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1,
|
||||
0, 4, 8, 12, 16, 19, 16, 12, 8, 4, 0, 2, 5, 9, 13, 17, 19, 14, 10, 6, 2,
|
||||
0, 3, 7, 11, 15, 18, 15, 11, 7, 3,
|
||||
];
|
||||
|
||||
function idleVuBarHeights(): number[] {
|
||||
@@ -73,15 +77,44 @@ function idleVuBarHeights(): number[] {
|
||||
);
|
||||
}
|
||||
|
||||
function vuHeightsForMelodyBeat(lead: number, pulse: number): number[] {
|
||||
/** 彩蛋律动:主峰随节拍移动,每拍起落,全柱叠加波纹 */
|
||||
function vuHeightsForDance(elapsedMs: number): number[] {
|
||||
const t = elapsedMs / 1000;
|
||||
const beatIdx = Math.floor(elapsedMs / EGG_BEAT_MS) % EGG_MELODY_LEAD.length;
|
||||
const lead = EGG_MELODY_LEAD[beatIdx];
|
||||
const beatPhase = (elapsedMs % EGG_BEAT_MS) / EGG_BEAT_MS;
|
||||
const kick = Math.sin(beatPhase * Math.PI);
|
||||
|
||||
return Array.from({ length: VU_BAR_COUNT }, (_, i) => {
|
||||
const dist = Math.abs(i - lead);
|
||||
let amp = 0.14;
|
||||
if (dist === 0) amp = 0.96;
|
||||
else if (dist === 1) amp = 0.74;
|
||||
else if (dist === 2) amp = 0.5;
|
||||
else if (dist === 3) amp = 0.3;
|
||||
return Math.min(1, amp * (0.82 + pulse * 0.18));
|
||||
let peak = 0.06;
|
||||
if (dist === 0) peak = 1;
|
||||
else if (dist === 1) peak = 0.82;
|
||||
else if (dist === 2) peak = 0.62;
|
||||
else if (dist === 3) peak = 0.42;
|
||||
else if (dist === 4) peak = 0.26;
|
||||
|
||||
const ripple = 0.35 * Math.max(0, Math.sin(t * 2.75 + i * 0.62));
|
||||
const sway = 0.1 * Math.sin(t * 1.4 + i * 0.35);
|
||||
const h = 0.05 + peak * kick + ripple * (0.55 + kick * 0.45) + sway;
|
||||
return Math.min(1, Math.max(0.05, h));
|
||||
});
|
||||
}
|
||||
|
||||
/** 彩蛋律动二:海浪形波从左→右,再右→左 */
|
||||
function vuHeightsForWave(phaseElapsedMs: number): number[] {
|
||||
const half = EGG_WAVE_HALF_MS;
|
||||
const ltr = phaseElapsedMs < half;
|
||||
const localMs = ltr ? phaseElapsedMs : phaseElapsedMs - half;
|
||||
const t = localMs / 1000;
|
||||
const direction = ltr ? 1 : -1;
|
||||
const speed = 0.75;
|
||||
|
||||
return Array.from({ length: VU_BAR_COUNT }, (_, i) => {
|
||||
const phase = t * speed * Math.PI * 2 - direction * i * 0.72;
|
||||
const wave = (Math.sin(phase) + 1) / 2;
|
||||
const foam = 0.06 + 0.05 * Math.sin(i * 0.28 + t * 0.5);
|
||||
return Math.min(1, Math.max(0.06, foam + wave * 0.9));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,7 +164,7 @@ function ListRow({
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const { isConnected, deviceState, setVolume, updateSetting, api, disconnect, isDemoMode } = useDevice();
|
||||
const { isConnected, deviceState, setVolume, updateSetting, api, disconnect, isDemoMode, ip } = useDevice();
|
||||
const [, setLocation] = useLocation();
|
||||
const powerActionRef = useRef(false);
|
||||
const hpEqLastTapRef = useRef<number | null>(null);
|
||||
@@ -144,6 +177,8 @@ export default function Home() {
|
||||
const jackProgressRef = useRef(0);
|
||||
const jackResetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const eggRafRef = useRef<number | null>(null);
|
||||
/** 下次三连击成功时播放的律动:1=旋律,2=海浪(各需单独三连击触发) */
|
||||
const jackEggNextRef = useRef<1 | 2>(1);
|
||||
|
||||
const homeText = useMemo((): HomeLocale => {
|
||||
if (!deviceState) return {} as HomeLocale;
|
||||
@@ -257,28 +292,33 @@ export default function Home() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const triggerJackEasterEgg = useCallback(() => {
|
||||
setEasterEggActive(true);
|
||||
jackProgressRef.current = 0;
|
||||
const start = performance.now();
|
||||
const runJackEggAnimation = useCallback(
|
||||
(mode: "dance" | "wave") => {
|
||||
setEasterEggActive(true);
|
||||
setVuHeights(mode === "dance" ? vuHeightsForDance(0) : vuHeightsForWave(0));
|
||||
jackProgressRef.current = 0;
|
||||
const duration = mode === "dance" ? EGG_DANCE_DURATION_MS : EGG_WAVE_DURATION_MS;
|
||||
const start = performance.now();
|
||||
|
||||
const tick = (now: number) => {
|
||||
const elapsed = now - start;
|
||||
if (elapsed >= EGG_DURATION_MS) {
|
||||
setEasterEggActive(false);
|
||||
setVuHeights(idleVuBarHeights());
|
||||
eggRafRef.current = null;
|
||||
return;
|
||||
}
|
||||
const beat = Math.floor(elapsed / EGG_BEAT_MS) % EGG_MELODY_LEAD.length;
|
||||
const pulse = 0.5 + 0.5 * Math.sin(elapsed * 0.02);
|
||||
setVuHeights(vuHeightsForMelodyBeat(EGG_MELODY_LEAD[beat], pulse));
|
||||
const tick = (now: number) => {
|
||||
const elapsed = now - start;
|
||||
if (elapsed >= duration) {
|
||||
setEasterEggActive(false);
|
||||
setVuHeights(idleVuBarHeights());
|
||||
eggRafRef.current = null;
|
||||
return;
|
||||
}
|
||||
setVuHeights(
|
||||
mode === "dance" ? vuHeightsForDance(elapsed) : vuHeightsForWave(elapsed),
|
||||
);
|
||||
eggRafRef.current = requestAnimationFrame(tick);
|
||||
};
|
||||
|
||||
if (eggRafRef.current !== null) cancelAnimationFrame(eggRafRef.current);
|
||||
eggRafRef.current = requestAnimationFrame(tick);
|
||||
};
|
||||
|
||||
if (eggRafRef.current !== null) cancelAnimationFrame(eggRafRef.current);
|
||||
eggRafRef.current = requestAnimationFrame(tick);
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onJackClick = useCallback(
|
||||
(jack: JackId) => {
|
||||
@@ -288,7 +328,13 @@ export default function Home() {
|
||||
if (jack === expected) {
|
||||
jackProgressRef.current += 1;
|
||||
if (jackProgressRef.current >= JACK_EGG_SEQUENCE.length) {
|
||||
triggerJackEasterEgg();
|
||||
if (jackEggNextRef.current === 1) {
|
||||
runJackEggAnimation("dance");
|
||||
jackEggNextRef.current = 2;
|
||||
} else {
|
||||
runJackEggAnimation("wave");
|
||||
jackEggNextRef.current = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jackProgressRef.current = jack === JACK_EGG_SEQUENCE[0] ? 1 : 0;
|
||||
@@ -299,7 +345,7 @@ export default function Home() {
|
||||
jackProgressRef.current = 0;
|
||||
}, JACK_SEQUENCE_TIMEOUT_MS);
|
||||
},
|
||||
[easterEggActive, triggerJackEasterEgg],
|
||||
[easterEggActive, runJackEggAnimation],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -309,7 +355,7 @@ export default function Home() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!isConnected) return <ConnectScreen />;
|
||||
if (!isConnected) return <ConnectionPlaceholder />;
|
||||
|
||||
const ds = deviceState!;
|
||||
const inputLabel = INPUT_LABELS[ds.input] ?? "USB-C";
|
||||
@@ -471,8 +517,8 @@ export default function Home() {
|
||||
background: isHot ? "#ef4444" : i > 13 ? "#f59e0b" : "#00FFF6",
|
||||
opacity: easterEggActive ? 0.95 : 0.7,
|
||||
transition: easterEggActive
|
||||
? "height 90ms ease-out, opacity 120ms ease-out"
|
||||
: "height 200ms ease-out",
|
||||
? "none"
|
||||
: "height 200ms ease-out, opacity 200ms ease-out",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -766,6 +812,15 @@ export default function Home() {
|
||||
value={vuLabel}
|
||||
onClick={() => setLocation("/vu")}
|
||||
/>
|
||||
<ListRow
|
||||
icon={<Undo2 size={16} />}
|
||||
label={homeText.backToLegacy ?? "返回旧版"}
|
||||
onClick={() => {
|
||||
const target = ip.trim();
|
||||
if (!target) return;
|
||||
window.location.href = `https://am.luxsinaudio.com/x8/i.html?ip=${encodeURIComponent(target)}#/home`;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ type LocaleSettings = {
|
||||
deviceName: string;
|
||||
firmwareVersion: string;
|
||||
macAddress: string;
|
||||
disconnect: string;
|
||||
deviceIp: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -109,7 +109,7 @@ function optionLabels(items: Array<{ index: number; label: string }> | undefined
|
||||
|
||||
export default function SystemPage() {
|
||||
const [, setLocation] = useLocation();
|
||||
const { deviceState, updateSetting, disconnect, refresh, isConnected, api } = useDevice();
|
||||
const { deviceState, updateSetting, refresh, isConnected, api, ip } = useDevice();
|
||||
const ds = deviceState;
|
||||
|
||||
// 页面加载且已连接时刷新数据
|
||||
@@ -204,16 +204,10 @@ export default function SystemPage() {
|
||||
<span className="flex-1 text-[16px] text-white">{ui.macAddress}</span>
|
||||
<span className="ios-row-value text-[12px]">{ds?.mac ?? "—"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── 断开连接 ── */}
|
||||
<div className="mt-6 mb-4">
|
||||
<button
|
||||
className="w-full py-4 rounded-[14px] text-[16px] font-medium transition-all duration-150 active:scale-[0.98]"
|
||||
style={{ background: "rgba(239,68,68,0.1)", border: "1px solid rgba(239,68,68,0.2)", color: "#ef4444" }}
|
||||
onClick={() => { disconnect(); setLocation("/"); }}>
|
||||
{ui.disconnect}
|
||||
</button>
|
||||
<div className="ios-list-row">
|
||||
<span className="flex-1 text-[16px] text-white">{ui.deviceIp ?? "设备 IP"}</span>
|
||||
<span className="ios-row-value text-[12px]">{ip || "—"}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user