feat: move debug overlay to MobileLayout (全局持久显示)
This commit is contained in:
0
backend/serv.sh
Executable file → Normal file
0
backend/serv.sh
Executable file → Normal file
@@ -1,14 +1,27 @@
|
|||||||
import { Outlet } from "react-router";
|
import { Outlet } from "react-router";
|
||||||
|
import { useUser } from "../context/UserContext";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* H5 移动端全局布局容器
|
* H5 移动端全局布局容器
|
||||||
* - max-w-md (448px) 限制最大宽度,大屏居中显示
|
|
||||||
* - min-h-dvh 使用动态视口高度,解决 Safari 地址栏问题
|
|
||||||
* - safe-area padding 适配刘海屏/底部手势条
|
|
||||||
*/
|
*/
|
||||||
export function MobileLayout() {
|
export function MobileLayout() {
|
||||||
|
const { debugInfo, clearDebug } = useUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-md mx-auto min-h-dvh relative bg-morandi-50 shadow-[0_0_40px_rgba(0,0,0,0.06)]">
|
<div className="max-w-md mx-auto min-h-dvh relative bg-morandi-50 shadow-[0_0_40px_rgba(0,0,0,0.06)]">
|
||||||
|
{/* 全局调试浮层:所有页面下可见 */}
|
||||||
|
{debugInfo.length > 0 && (
|
||||||
|
<div
|
||||||
|
className="absolute top-0 left-0 right-0 z-50 bg-black/80 text-green-400 text-xs p-3 overflow-auto max-h-[50%] cursor-pointer font-mono"
|
||||||
|
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all', lineHeight: 1.5 }}
|
||||||
|
onClick={() => clearDebug()}
|
||||||
|
>
|
||||||
|
<div style={{ borderBottom: '1px solid #0f0', paddingBottom: 4, marginBottom: 4, fontWeight: 'bold', fontSize: 14 }}>H5应用调试</div>
|
||||||
|
{debugInfo.map((msg, i) => (
|
||||||
|
<div key={i}>{msg}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ interface UserContextValue {
|
|||||||
setToken: (token: string) => void;
|
setToken: (token: string) => void;
|
||||||
setTokenAndUser: (token: string, info: BasicUserInfo) => void;
|
setTokenAndUser: (token: string, info: BasicUserInfo) => void;
|
||||||
fetchProfile: () => Promise<UserProfileDTO | null>;
|
fetchProfile: () => Promise<UserProfileDTO | null>;
|
||||||
|
debugInfo: string[];
|
||||||
|
addDebug: (msg: string) => void;
|
||||||
|
clearDebug: () => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +198,14 @@ const UserContext = createContext<UserContextValue | null>(null);
|
|||||||
export function UserProvider({ children }: { children: ReactNode }) {
|
export function UserProvider({ children }: { children: ReactNode }) {
|
||||||
const [user, setUser] = useState<UserProfile>(loadProfile);
|
const [user, setUser] = useState<UserProfile>(loadProfile);
|
||||||
const [token, setTokenState] = useState<string | null>(getToken);
|
const [token, setTokenState] = useState<string | null>(getToken);
|
||||||
|
const [debugInfo, setDebugInfo] = useState<string[]>([]);
|
||||||
|
const addDebug = useCallback((msg: string) => {
|
||||||
|
console.log(msg);
|
||||||
|
setDebugInfo(prev => [...prev.slice(-20), msg]);
|
||||||
|
}, []);
|
||||||
|
const clearDebug = useCallback(() => {
|
||||||
|
setDebugInfo([]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const setToken = useCallback((newToken: string) => {
|
const setToken = useCallback((newToken: string) => {
|
||||||
// 清理前一个用户的本地数据,防止用户间数据残留
|
// 清理前一个用户的本地数据,防止用户间数据残留
|
||||||
@@ -280,7 +291,7 @@ export function UserProvider({ children }: { children: ReactNode }) {
|
|||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider value={{ user, token, updateUser, updateUserRemote, setToken, setTokenAndUser, fetchProfile, logout }}>
|
<UserContext.Provider value={{ user, token, updateUser, updateUserRemote, setToken, setTokenAndUser, fetchProfile, debugInfo, addDebug, clearDebug, logout }}>
|
||||||
{children}
|
{children}
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,11 +10,7 @@ export function LoginPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setTokenAndUser, fetchProfile } = useUser();
|
const { setTokenAndUser, fetchProfile } = useUser();
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [debugInfo, setDebugInfo] = useState<string[]>([]);
|
const { addDebug, debugInfo } = useUser();
|
||||||
const addDebug = (msg: string) => {
|
|
||||||
console.log(msg);
|
|
||||||
setDebugInfo(prev => [...prev.slice(-20), msg]);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearDebugLogs();
|
clearDebugLogs();
|
||||||
@@ -98,19 +94,6 @@ export function LoginPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh bg-morandi-50 flex items-center justify-center p-4 relative">
|
<div className="min-h-dvh bg-morandi-50 flex items-center justify-center p-4 relative">
|
||||||
{/* 调试浮层:点击可关闭 */}
|
|
||||||
{debugInfo.length > 0 && (
|
|
||||||
<div
|
|
||||||
className="absolute top-0 left-0 right-0 z-50 bg-black/80 text-green-400 text-xs p-3 overflow-auto max-h-[50%] cursor-pointer font-mono"
|
|
||||||
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all', lineHeight: 1.5 }}
|
|
||||||
onClick={() => setDebugInfo([])}
|
|
||||||
>
|
|
||||||
<div style={{borderBottom:'1px solid #0f0',paddingBottom:4,marginBottom:4,fontWeight:'bold',fontSize:14}}>H5应用调试</div>
|
|
||||||
{debugInfo.map((msg, i) => (
|
|
||||||
<div key={i}>{msg}</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<motion.div
|
<motion.div
|
||||||
animate={{ rotate: 360 }}
|
animate={{ rotate: 360 }}
|
||||||
|
|||||||
Reference in New Issue
Block a user