cleanup: 移除所有调试代码(DebugPanel/红横幅/URL_CHECK),登录页精简为仅 uid/openid/phone 流程
This commit is contained in:
@@ -9,23 +9,6 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<script>window.__INITIAL_URL__ = document.URL;</script>
|
|
||||||
<!-- 🐞 原始 URL 验证 -->
|
|
||||||
<div id="url-check" style="display:none" data-url=""></div>
|
|
||||||
<script>
|
|
||||||
var u = document.URL;
|
|
||||||
document.getElementById('url-check').setAttribute('data-url', u);
|
|
||||||
console.log('[URL_CHECK] document.URL:', u);
|
|
||||||
console.log('[URL_CHECK] search:', location.search);
|
|
||||||
console.log('[URL_CHECK] has_token:', location.search.includes('token='));
|
|
||||||
// 如果没 token,在页面顶部写一行红色提示
|
|
||||||
if (!location.search.includes('token=')) {
|
|
||||||
var d = document.createElement('div');
|
|
||||||
d.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:99999;background:red;color:white;padding:10px;font-size:14px;text-align:center';
|
|
||||||
d.textContent = '⚠️ URL无token: search=[' + location.search + '] url=' + u.substring(0, 80);
|
|
||||||
document.body.prepend(d);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,210 +1,50 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import { Heart } from "lucide-react";
|
import { Heart } from "lucide-react";
|
||||||
import { useUser } from "../context/UserContext";
|
import { useUser } from "../context/UserContext";
|
||||||
import { decodeToken, devLogin, createDummyToken, getToken, setToken as saveToken } from "../services/api";
|
import { devLogin, createDummyToken, getToken } from "../services/api";
|
||||||
|
|
||||||
/** 判断是否在微信小程序 webview 环境 */
|
|
||||||
function isWechatMiniProgram(): boolean {
|
|
||||||
const ua = navigator.userAgent.toLowerCase();
|
|
||||||
return ua.includes("micromessenger") && ua.includes("miniprogram");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── 🐞 调试区域:排查后删除 ───
|
|
||||||
|
|
||||||
interface DebugInfo {
|
|
||||||
url: string;
|
|
||||||
ua: string;
|
|
||||||
isWxMp: boolean;
|
|
||||||
hasTokenParam: boolean;
|
|
||||||
tokenPrefix: string;
|
|
||||||
decodeResult: string;
|
|
||||||
fetchError: string;
|
|
||||||
devLoginError: string;
|
|
||||||
step: string;
|
|
||||||
tokenInStorage: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const INIT_DEBUG: DebugInfo = {
|
|
||||||
url: "", ua: "", isWxMp: false, hasTokenParam: false,
|
|
||||||
tokenPrefix: "", decodeResult: "", fetchError: "",
|
|
||||||
devLoginError: "", step: "init", tokenInStorage: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
function DebugPanel({ info }: { info: DebugInfo }) {
|
|
||||||
const [initialUrl] = useState(() => (window as any).__INITIAL_URL__ || '(N/A)');
|
|
||||||
return (
|
|
||||||
<div className="absolute top-0 left-0 right-0 z-50 bg-black/80 text-white text-xs font-mono p-3 text-left max-h-[60vh] overflow-auto">
|
|
||||||
<div className="font-bold text-yellow-400 mb-1">🐞 调试信息 (关闭需去掉 ?debug=1)</div>
|
|
||||||
<table className="w-full">
|
|
||||||
<tbody>
|
|
||||||
{Object.entries(info).map(([k, v]) => (
|
|
||||||
<tr key={k} className="border-b border-white/10">
|
|
||||||
<td className="pr-2 text-gray-400 w-28 align-top whitespace-nowrap">{k}</td>
|
|
||||||
<td className="break-all text-green-300">{v === false ? 'false' : v === true ? 'true' : v || <span className="text-gray-500">(空)</span>}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
<tr className="border-b border-yellow-400/50">
|
|
||||||
<td className="pr-2 text-yellow-400 w-28 align-top whitespace-nowrap font-bold">initialUrl</td>
|
|
||||||
<td className="break-all text-yellow-300">{initialUrl}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── 🐞 调试区域结束 ───
|
|
||||||
|
|
||||||
export function LoginPage() {
|
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 [debug] = useState<DebugInfo>(() => {
|
|
||||||
sessionStorage.setItem('debug', '1');
|
|
||||||
return { ...INIT_DEBUG };
|
|
||||||
});
|
|
||||||
const processedRef = useRef(false); // 防止重复处理
|
|
||||||
|
|
||||||
// ─── 🐞 调试更新函数 ───
|
|
||||||
const dbg = (patch: Partial<DebugInfo>) => {
|
|
||||||
if (debug) Object.assign(debug, patch);
|
|
||||||
};
|
|
||||||
// ─── 🐞 调试更新结束 ───
|
|
||||||
|
|
||||||
/** 处理 token:解码 → 登录 → 拉取数据 → 跳转 */
|
|
||||||
const processToken = (token: string) => {
|
|
||||||
if (processedRef.current) return;
|
|
||||||
processedRef.current = true;
|
|
||||||
|
|
||||||
const oldToken = getToken();
|
|
||||||
if (token !== oldToken) {
|
|
||||||
try {
|
|
||||||
const payload = decodeToken(token);
|
|
||||||
dbg({ step: "decode-ok", decodeResult: `uid=${payload.uid}, openid=${payload.openid?.substring(0,15)}..., phone=${payload.phone}` });
|
|
||||||
setTokenAndUser(token, {
|
|
||||||
id: String(payload.uid),
|
|
||||||
openid: payload.openid,
|
|
||||||
phone: payload.phone,
|
|
||||||
});
|
|
||||||
} catch (e: any) {
|
|
||||||
dbg({ step: "decode-fail", decodeResult: `解析失败: ${e?.message || e}` });
|
|
||||||
setError("Token 解析失败: " + (e?.message || "格式错误"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
saveToken(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
dbg({ step: "fetch-profile" });
|
|
||||||
fetchProfile().then((dto) => {
|
|
||||||
dbg({ step: "fetch-ok", fetchError: `is_onboarded=${dto?.is_onboarded}` });
|
|
||||||
const target = dto?.is_onboarded ? "/home" : "/onboarding";
|
|
||||||
navigate(sessionStorage.getItem('debug') ? target + "?debug=1" : target, { replace: true });
|
|
||||||
}).catch((e: any) => {
|
|
||||||
dbg({ step: "fetch-fail", fetchError: `${e?.message || e}` });
|
|
||||||
setError("登录已失效,请在小程序重新进入");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 暴露到全局,供小程序 evaluateJavascript 直接调用
|
|
||||||
(window as any).handleInjectedToken = processToken;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dbg({ url: window.location.href, ua: navigator.userAgent, tokenInStorage: !!getToken() });
|
|
||||||
|
|
||||||
// 从 URL 获取 token(query / hash / 注入)
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
let token = params.get('token');
|
|
||||||
|
|
||||||
if (!token) {
|
// URL 参数 uid/openid/phone 直接构建本地 token 登录
|
||||||
const hash = window.location.hash;
|
|
||||||
if (hash.startsWith('#token=')) {
|
|
||||||
token = decodeURIComponent(hash.replace('#token=', ''));
|
|
||||||
dbg({ step: "token-from-hash", tokenPrefix: token.substring(0, 30) + "..." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!token && (window as any).__INJECTED_TOKEN__) {
|
|
||||||
token = (window as any).__INJECTED_TOKEN__;
|
|
||||||
dbg({ step: "token-from-inject", tokenPrefix: token.substring(0, 30) + "..." });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
dbg({ step: "found-token", hasTokenParam: true, tokenPrefix: token.substring(0, 30) + "..." });
|
|
||||||
processToken(token);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 URL 参数 uid/openid/phone 直接构建本地 token
|
|
||||||
const uidParam = params.get('uid');
|
|
||||||
const openidParam = params.get('openid');
|
const openidParam = params.get('openid');
|
||||||
if (openidParam) {
|
if (openidParam) {
|
||||||
const uid = parseInt(uidParam || '0');
|
const uid = parseInt(params.get('uid') || '0');
|
||||||
const phone = params.get('phone') || '';
|
const phone = params.get('phone') || '';
|
||||||
const dummyToken = createDummyToken(uid, openidParam, phone);
|
const token = createDummyToken(uid, openidParam, phone);
|
||||||
dbg({ step: "params-login", hasTokenParam: true, tokenPrefix: `uid=${uidParam}, openid=${openidParam.substring(0, 15)}...` });
|
setTokenAndUser(token, { id: String(uid), openid: openidParam, phone });
|
||||||
setTokenAndUser(dummyToken, { id: String(uid), openid: openidParam, phone });
|
|
||||||
fetchProfile().then((dto) => {
|
fetchProfile().then((dto) => {
|
||||||
dbg({ step: "params-ok", fetchError: `is_onboarded=${dto?.is_onboarded}` });
|
navigate(dto?.is_onboarded ? "/home" : "/onboarding", { replace: true });
|
||||||
const target = dto?.is_onboarded ? "/home" : "/onboarding";
|
}).catch(() => {
|
||||||
navigate(sessionStorage.getItem('debug') ? target + "?debug=1" : target, { replace: true });
|
setError("登录已失效,请在小程序重新进入");
|
||||||
}).catch((e: any) => {
|
|
||||||
dbg({ step: "params-fail", fetchError: `${e?.message || e}` });
|
|
||||||
setError("登录失败: " + (e?.message || ""));
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有 token → 小程序环境则发 NEED_TOKEN
|
|
||||||
dbg({ step: "check-wx-mp", isWxMp: isWechatMiniProgram() });
|
|
||||||
if (isWechatMiniProgram()) {
|
|
||||||
try {
|
|
||||||
(window as any).wx?.miniProgram?.postMessage({ type: "NEED_TOKEN" });
|
|
||||||
dbg({ step: "wx-postMessage-done" });
|
|
||||||
} catch (e: any) {
|
|
||||||
dbg({ step: "wx-postMessage-error", fetchError: `${e?.message || e}` });
|
|
||||||
}
|
|
||||||
setError("正在获取登录信息...");
|
|
||||||
|
|
||||||
// 轮询:等待小程序 evaluateJavascript 注入 token
|
|
||||||
let attempts = 0;
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
const injected = (window as any).__INJECTED_TOKEN__;
|
|
||||||
if (injected) {
|
|
||||||
clearInterval(timer);
|
|
||||||
dbg({ step: "token-polled", tokenPrefix: injected.substring(0, 30) + "..." });
|
|
||||||
processToken(injected);
|
|
||||||
}
|
|
||||||
attempts++;
|
|
||||||
if (attempts >= 20) clearInterval(timer); // 最多等 10 秒
|
|
||||||
}, 500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 浏览器开发环境 → dev-login
|
// 浏览器开发环境 → dev-login
|
||||||
dbg({ step: "dev-login" });
|
|
||||||
devLogin().then((res) => {
|
devLogin().then((res) => {
|
||||||
dbg({ step: "dev-login-ok" });
|
|
||||||
setTokenAndUser(res.token, {
|
setTokenAndUser(res.token, {
|
||||||
id: res.user_id,
|
id: res.user_id,
|
||||||
openid: res.openid,
|
openid: res.openid,
|
||||||
phone: res.mobile,
|
phone: res.mobile,
|
||||||
});
|
});
|
||||||
fetchProfile().then((dto) => {
|
fetchProfile().then((dto) => {
|
||||||
const target = dto?.is_onboarded ? "/home" : "/onboarding";
|
navigate(dto?.is_onboarded ? "/home" : "/onboarding", { replace: true });
|
||||||
navigate(sessionStorage.getItem('debug') ? target + "?debug=1" : target, { replace: true });
|
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
dbg({ step: "dev-login-fail", devLoginError: err?.message || String(err) });
|
|
||||||
setError(err?.message || "登录失败,请刷新重试");
|
setError(err?.message || "登录失败,请刷新重试");
|
||||||
});
|
});
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
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">
|
||||||
{debug && <DebugPanel info={debug} />}
|
|
||||||
<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