Merge branch 'dev_new_ljy' into dev

This commit is contained in:
ljy
2026-07-10 01:54:39 +08:00
7 changed files with 88 additions and 28 deletions
+23 -4
View File
@@ -1,13 +1,32 @@
import { Routes, Route, Navigate } from 'react-router-dom';
import { Routes, Route, Navigate, useLocation } from 'react-router-dom';
import LoginPage from './pages/LoginPage';
import PartnerAppRoutes from './PartnerAppRoutes';
import { isLoggedIn } from './lib/api';
import { usePartnerSession } from './contexts/PartnerSessionContext';
import { partnerHomePath } from './lib/partnerAccess';
function LoginRoute() {
const { account } = usePartnerSession();
if (isLoggedIn()) {
return <Navigate to={partnerHomePath(account)} replace />;
}
return <LoginPage />;
}
function AppRoutes() {
const location = useLocation();
export default function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/*" element={isLoggedIn() ? <PartnerAppRoutes /> : <Navigate to="/login" replace />} />
<Route path="/login" element={<LoginRoute />} />
<Route
path="/*"
element={isLoggedIn() ? <PartnerAppRoutes /> : <Navigate to="/login" replace state={{ from: location }} />}
/>
</Routes>
);
}
export default function App() {
return <AppRoutes />;
}
+3 -3
View File
@@ -61,12 +61,12 @@ function SubAccountRoutes() {
}
export default function PartnerAppRoutes() {
const { account, loading, loggedIn } = usePartnerSession();
const { account, loading } = usePartnerSession();
if (!loggedIn && !isLoggedIn()) {
if (!isLoggedIn()) {
return <Navigate to="/login" replace />;
}
if (loading) {
if (loading && !account) {
return <SessionLoading />;
}
if (isSubAccount(account)) {
@@ -1,6 +1,6 @@
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
import { toAppPath } from '@dukang/weixin-sdk';
import { clearAuth, isLoggedIn, request } from '../lib/api';
import { clearAuth, isLoggedIn, request, saveAuth, type PartnerAuthPayload } from '../lib/api';
import type { PartnerMe, PartnerStaffRole } from '@dukang/shared-types';
@@ -12,6 +12,7 @@ type PartnerSessionValue = {
account: PartnerAccount | null;
loading: boolean;
loggedIn: boolean;
applySession: (session: PartnerAuthPayload) => void;
refresh: () => Promise<PartnerAccount | null>;
logout: () => void;
};
@@ -23,6 +24,15 @@ export function PartnerSessionProvider({ children }: { children: ReactNode }) {
const [loading, setLoading] = useState(true);
const [loggedIn, setLoggedIn] = useState(() => isLoggedIn());
const applySession = useCallback((session: PartnerAuthPayload) => {
saveAuth(session);
setLoggedIn(true);
setLoading(false);
if (session.partner) {
setAccount(session.partner as PartnerAccount);
}
}, []);
const refresh = useCallback(async (): Promise<PartnerAccount | null> => {
if (!isLoggedIn()) {
setAccount(null);
@@ -38,6 +48,9 @@ export function PartnerSessionProvider({ children }: { children: ReactNode }) {
return data;
} catch {
setAccount(null);
if (!isLoggedIn()) {
setLoggedIn(false);
}
return null;
} finally {
setLoading(false);
@@ -57,7 +70,7 @@ export function PartnerSessionProvider({ children }: { children: ReactNode }) {
return (
<PartnerSessionContext.Provider
value={{ account, loading, loggedIn, refresh, logout }}
value={{ account, loading, loggedIn, applySession, refresh, logout }}
>
{children}
</PartnerSessionContext.Provider>
+5 -2
View File
@@ -1,4 +1,4 @@
import type { PartnerMe } from '@dukang/shared-types';
import type { PartnerMe, PartnerStaffRole } from '@dukang/shared-types';
import { isOnAppPath, toAppPath } from '@dukang/weixin-sdk';
export const apiBase = '/api/v1';
@@ -6,7 +6,10 @@ export const apiBase = '/api/v1';
const LAST_PHONE = 'partnerLastPhone';
const PARTNER_PROFILE = 'partnerProfile';
export type PartnerSessionProfile = Pick<PartnerMe, 'id' | 'name' | 'phone' | 'companyName'>;
export type PartnerSessionProfile = Pick<PartnerMe, 'id' | 'name' | 'phone' | 'companyName'> & {
isPrimary?: PartnerMe['isPrimary'];
staffRole?: PartnerStaffRole;
};
export type PartnerAuthPayload = {
accessToken: string;
+5 -2
View File
@@ -2,7 +2,7 @@ import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-type
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
import { isWechatEnv, weixinSdk } from './weixin';
import { request, saveAuth } from './api';
import { request, saveAuth, type PartnerSessionProfile } from './api';
export type PartnerProfile = {
id: string;
@@ -37,7 +37,10 @@ export async function checkNeedsWechatAuth(profile: PartnerProfile | null): Prom
/** 处理微信登录/绑定结果,返回是否已拿到 token 可进入首页 */
export function handlePartnerWechatLoginResult(result: WechatLoginResult): boolean {
if (!result.accessToken) return false;
saveAuth({ accessToken: result.accessToken });
saveAuth({
accessToken: result.accessToken,
partner: result.partner as PartnerSessionProfile | undefined,
});
return true;
}
+32 -14
View File
@@ -1,13 +1,13 @@
import { useEffect, useState } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { request, getLastPhone, getPartnerProfile, saveAuth } from '../lib/api';
import { request, getLastPhone, getPartnerProfile, type PartnerAuthPayload } from '../lib/api';
import { usePartnerSession } from '../contexts/PartnerSessionContext';
import type { PartnerAccount } from '../contexts/PartnerSessionContext';
import { partnerHomePath } from '../lib/partnerAccess';
import type { PartnerMe } from '@dukang/shared-types';
import {
fetchClientConfig,
handlePartnerWechatCallback,
handlePartnerWechatLoginResult,
loginPartnerWithWechat,
} from '../lib/wechat-auth';
import { isWechatEnv } from '../lib/weixin';
@@ -43,9 +43,21 @@ function formatPartnerError(e: unknown): string {
return text;
}
function toLoginSession(data: { accessToken: string; partner?: PartnerMe }): PartnerAuthPayload {
return {
accessToken: data.accessToken,
partner: data.partner,
};
}
function toPartnerAccount(partner?: Record<string, unknown>): PartnerAccount | undefined {
if (!partner || typeof partner.id !== 'string') return undefined;
return partner as unknown as PartnerAccount;
}
export default function LoginPage() {
const navigate = useNavigate();
const { refresh } = usePartnerSession();
const { applySession } = usePartnerSession();
const [params] = useSearchParams();
const quick = params.get('quick') === '1';
const savedProfile = getPartnerProfile();
@@ -73,14 +85,17 @@ export default function LoginPage() {
useEffect(() => {
if (!isWechatEnv() || !wxAuthorize || !params.get('code')) return;
void handlePartnerWechatCallback()
.then(async (result) => {
if (!result) return;
if (!handlePartnerWechatLoginResult(result)) return;
const account = await refresh();
navigate(partnerHomePath(account ?? undefined));
.then((result) => {
if (!result?.accessToken) return;
const session: PartnerAuthPayload = {
accessToken: result.accessToken,
partner: toPartnerAccount(result.partner),
};
applySession(session);
navigate(partnerHomePath(session.partner), { replace: true });
})
.catch((e) => setMsg(formatWechatError(e)));
}, [navigate, refresh, wxAuthorize, params]);
}, [applySession, navigate, wxAuthorize, params]);
function formatWechatError(e: unknown): string {
const text = e instanceof Error ? e.message : '微信登录失败';
@@ -155,10 +170,10 @@ export default function LoginPage() {
body: JSON.stringify({ phone: loginPhone, code }),
silent: true,
});
saveAuth(data);
const session = toLoginSession(data);
applySession(session);
persistRememberAccount(loginPhone);
const account = await refresh();
navigate(partnerHomePath(account ?? data.partner));
navigate(partnerHomePath(session.partner), { replace: true });
} catch (e) {
setMsg(formatPartnerError(e));
} finally {
@@ -177,8 +192,11 @@ export default function LoginPage() {
try {
const ok = await loginPartnerWithWechat();
if (ok) {
const account = await refresh();
navigate(partnerHomePath(account ?? undefined));
applySession({
accessToken: localStorage.getItem('accessToken') ?? '',
partner: getPartnerProfile() ?? undefined,
});
navigate(partnerHomePath(getPartnerProfile()), { replace: true });
}
} catch (e) {
setMsg(formatWechatError(e));