Add WeChat auth entry for H5
This commit is contained in:
@@ -16,3 +16,8 @@ SCHEDULE_CRON=0 1 * * *
|
|||||||
SCHEDULE_TIMEZONE=Asia/Shanghai
|
SCHEDULE_TIMEZONE=Asia/Shanghai
|
||||||
|
|
||||||
MOCK_MODE=true
|
MOCK_MODE=true
|
||||||
|
|
||||||
|
# H5 WeChat OAuth. Leave empty to use the frontend demo authorization flow.
|
||||||
|
NEXT_PUBLIC_WECHAT_APP_ID=
|
||||||
|
NEXT_PUBLIC_WECHAT_AUTH_REDIRECT_URI=
|
||||||
|
NEXT_PUBLIC_WECHAT_AUTH_SCOPE=snsapi_userinfo
|
||||||
|
|||||||
@@ -128,6 +128,20 @@ export default function H5QAExperience() {
|
|||||||
.finally(() => setLoadingQa(false));
|
.finally(() => setLoadingQa(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const code = params.get("code");
|
||||||
|
const state = params.get("state");
|
||||||
|
if (code && state === "hyqa_h5") {
|
||||||
|
setUser({
|
||||||
|
mode: "wechat",
|
||||||
|
name: "微信已授权",
|
||||||
|
phone: "等待后端换取登录态"
|
||||||
|
});
|
||||||
|
window.history.replaceState(null, "", window.location.pathname);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
bottomRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
|
bottomRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
|
||||||
}, [messages, activeTab]);
|
}, [messages, activeTab]);
|
||||||
@@ -141,6 +155,15 @@ export default function H5QAExperience() {
|
|||||||
setAuthOpen(false);
|
setAuthOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockWechatLogin() {
|
||||||
|
setUser({
|
||||||
|
mode: "wechat",
|
||||||
|
name: "微信用户",
|
||||||
|
phone: "微信授权演示"
|
||||||
|
});
|
||||||
|
setAuthOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
function persistMessages(nextMessages: ChatMessage[]) {
|
function persistMessages(nextMessages: ChatMessage[]) {
|
||||||
setMessages(nextMessages);
|
setMessages(nextMessages);
|
||||||
setSessions((current) => {
|
setSessions((current) => {
|
||||||
@@ -413,6 +436,7 @@ export default function H5QAExperience() {
|
|||||||
user={user}
|
user={user}
|
||||||
onClose={() => setAuthOpen(false)}
|
onClose={() => setAuthOpen(false)}
|
||||||
onMockLogin={mockLogin}
|
onMockLogin={mockLogin}
|
||||||
|
onWechatMockLogin={mockWechatLogin}
|
||||||
onLogout={() => {
|
onLogout={() => {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setAuthOpen(false);
|
setAuthOpen(false);
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { LogIn, ShieldCheck, UserPlus, X } from "lucide-react";
|
import { LogIn, MessageCircle, ShieldCheck, UserPlus, X } from "lucide-react";
|
||||||
|
import { buildWechatOAuthUrl, getWechatAuthConfig, isWechatBrowser } from "@/lib/wechatAuth";
|
||||||
|
|
||||||
export type MockUser = {
|
export type MockUser = {
|
||||||
name: string;
|
name: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
mode: "login" | "register";
|
mode: "login" | "register" | "wechat";
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MockAuthPanel({
|
export default function MockAuthPanel({
|
||||||
@@ -13,23 +14,36 @@ export default function MockAuthPanel({
|
|||||||
user,
|
user,
|
||||||
onClose,
|
onClose,
|
||||||
onMockLogin,
|
onMockLogin,
|
||||||
|
onWechatMockLogin,
|
||||||
onLogout
|
onLogout
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
user: MockUser | null;
|
user: MockUser | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onMockLogin: (mode: MockUser["mode"]) => void;
|
onMockLogin: (mode: MockUser["mode"]) => void;
|
||||||
|
onWechatMockLogin: () => void;
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
}) {
|
}) {
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
|
const wechatConfig = getWechatAuthConfig();
|
||||||
|
const inWechat = typeof navigator !== "undefined" && isWechatBrowser(navigator.userAgent);
|
||||||
|
|
||||||
|
function startWechatAuth() {
|
||||||
|
if (wechatConfig) {
|
||||||
|
window.location.href = buildWechatOAuthUrl(wechatConfig);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onWechatMockLogin();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 z-30 flex items-end bg-slate-950/35 px-3 pb-3">
|
<div className="absolute inset-0 z-30 flex items-end bg-slate-950/35 px-3 pb-3">
|
||||||
<section className="w-full rounded-t-[18px] rounded-b-md bg-white p-4 shadow-2xl">
|
<section className="w-full rounded-t-[18px] rounded-b-md bg-white p-4 shadow-2xl">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-ink">账号入口</h2>
|
<h2 className="text-lg font-semibold text-ink">账号入口</h2>
|
||||||
<p className="mt-1 text-sm text-slate-500">先用 mock 登录,后续可替换为微信授权或手机号登录。</p>
|
<p className="mt-1 text-sm text-slate-500">优先预留微信授权;没有配置时使用演示账号。</p>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onClose} className="flex h-9 w-9 items-center justify-center rounded-md hover:bg-slate-100" aria-label="关闭">
|
<button onClick={onClose} className="flex h-9 w-9 items-center justify-center rounded-md hover:bg-slate-100" aria-label="关闭">
|
||||||
<X className="h-5 w-5" aria-hidden />
|
<X className="h-5 w-5" aria-hidden />
|
||||||
@@ -54,8 +68,15 @@ export default function MockAuthPanel({
|
|||||||
) : (
|
) : (
|
||||||
<div className="grid gap-3">
|
<div className="grid gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => onMockLogin("login")}
|
onClick={startWechatAuth}
|
||||||
className="flex h-11 items-center justify-center gap-2 rounded-md bg-jade text-sm font-medium text-white hover:bg-teal-800"
|
className="flex h-11 items-center justify-center gap-2 rounded-md bg-jade text-sm font-medium text-white hover:bg-teal-800"
|
||||||
|
>
|
||||||
|
<MessageCircle className="h-4 w-4" aria-hidden />
|
||||||
|
{wechatConfig ? "微信授权登录" : "微信授权演示"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => onMockLogin("login")}
|
||||||
|
className="flex h-11 items-center justify-center gap-2 rounded-md border border-line text-sm font-medium text-ink hover:bg-slate-50"
|
||||||
>
|
>
|
||||||
<LogIn className="h-4 w-4" aria-hidden />
|
<LogIn className="h-4 w-4" aria-hidden />
|
||||||
mock 登录
|
mock 登录
|
||||||
@@ -71,7 +92,9 @@ export default function MockAuthPanel({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-4 rounded-md bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-800">
|
<div className="mt-4 rounded-md bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-800">
|
||||||
这里暂不写入后端,也不保存敏感信息;后续接微信小程序时可把这个面板替换成授权弹窗。
|
{wechatConfig
|
||||||
|
? `已配置微信 OAuth${inWechat ? ",当前在微信内置浏览器中。" : "。如果不在微信内打开,授权页可能无法完成。"}`
|
||||||
|
: "尚未配置 NEXT_PUBLIC_WECHAT_APP_ID,当前按钮只模拟微信授权,不写入后端。"}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
43
hy_qa_asset_backend/frontend/lib/wechatAuth.ts
Normal file
43
hy_qa_asset_backend/frontend/lib/wechatAuth.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const WECHAT_OAUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
|
||||||
|
|
||||||
|
export type WechatAuthConfig = {
|
||||||
|
appId: string;
|
||||||
|
redirectUri: string;
|
||||||
|
scope: "snsapi_base" | "snsapi_userinfo";
|
||||||
|
state: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getWechatAuthConfig(): WechatAuthConfig | null {
|
||||||
|
const appId = process.env.NEXT_PUBLIC_WECHAT_APP_ID;
|
||||||
|
const redirectUri =
|
||||||
|
process.env.NEXT_PUBLIC_WECHAT_AUTH_REDIRECT_URI ||
|
||||||
|
(typeof window !== "undefined" ? `${window.location.origin}/h5` : "");
|
||||||
|
const scope = (process.env.NEXT_PUBLIC_WECHAT_AUTH_SCOPE || "snsapi_userinfo") as WechatAuthConfig["scope"];
|
||||||
|
|
||||||
|
if (!appId || !redirectUri) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
appId,
|
||||||
|
redirectUri,
|
||||||
|
scope,
|
||||||
|
state: "hyqa_h5"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildWechatOAuthUrl(config: WechatAuthConfig): string {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
appid: config.appId,
|
||||||
|
redirect_uri: config.redirectUri,
|
||||||
|
response_type: "code",
|
||||||
|
scope: config.scope,
|
||||||
|
state: config.state
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${WECHAT_OAUTH_URL}?${params.toString()}#wechat_redirect`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWechatBrowser(userAgent: string): boolean {
|
||||||
|
return /MicroMessenger/i.test(userAgent);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user