门店端微信号自动登录
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Navigate, useLocation } from 'react-router-dom';
|
import { Navigate, useLocation } from 'react-router-dom';
|
||||||
|
import { getStoreProfile, hasShopWxSession } from '../lib/api';
|
||||||
import { useStoreSession } from '../contexts/StoreSessionContext';
|
import { useStoreSession } from '../contexts/StoreSessionContext';
|
||||||
|
|
||||||
const PUBLIC_PATHS = new Set(['/login']);
|
const PUBLIC_PATHS = new Set(['/login']);
|
||||||
@@ -20,6 +21,10 @@ export default function AuthGate({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!authenticated && !PUBLIC_PATHS.has(location.pathname)) {
|
if (!authenticated && !PUBLIC_PATHS.has(location.pathname)) {
|
||||||
|
const profile = getStoreProfile();
|
||||||
|
if (profile && hasShopWxSession() && location.pathname !== '/login') {
|
||||||
|
return <Navigate to="/login?quick=1" replace state={{ from: location }} />;
|
||||||
|
}
|
||||||
return <Navigate to="/login" replace state={{ from: location }} />;
|
return <Navigate to="/login" replace state={{ from: location }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,18 @@ const ACCESS_TOKEN = 'accessToken';
|
|||||||
const REFRESH_TOKEN = 'refreshToken';
|
const REFRESH_TOKEN = 'refreshToken';
|
||||||
const LAST_PHONE = 'shopLastPhone';
|
const LAST_PHONE = 'shopLastPhone';
|
||||||
const STORE_PROFILE = 'shopStoreProfile';
|
const STORE_PROFILE = 'shopStoreProfile';
|
||||||
|
const SESSION_EXPIRES_AT = 'shopSessionExpiresAt';
|
||||||
|
export const SHOP_WX_BOUND = 'shopWxBound';
|
||||||
|
|
||||||
const AUTH_RECOVERY_EXEMPT_PATHS = ['/shop/auth/token/refresh', '/shop/auth/sms/send', '/shop/auth/login/sms'];
|
/** 微信验证通过后的免登录时长 */
|
||||||
|
export const SHOP_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
const AUTH_RECOVERY_EXEMPT_PATHS = [
|
||||||
|
'/shop/auth/token/refresh',
|
||||||
|
'/shop/auth/sms/send',
|
||||||
|
'/shop/auth/login/sms',
|
||||||
|
'/shop/auth/login/wechat',
|
||||||
|
];
|
||||||
|
|
||||||
export function getLastPhone() {
|
export function getLastPhone() {
|
||||||
return localStorage.getItem(LAST_PHONE) ?? '';
|
return localStorage.getItem(LAST_PHONE) ?? '';
|
||||||
@@ -44,6 +54,21 @@ export function getStoreProfile(): StoreSessionStore | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasShopWxSession() {
|
||||||
|
return localStorage.getItem(SHOP_WX_BOUND) === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isShopSessionExpired() {
|
||||||
|
const raw = localStorage.getItem(SESSION_EXPIRES_AT);
|
||||||
|
if (!raw) return false;
|
||||||
|
return Date.now() > Number(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function touchShopSession() {
|
||||||
|
if (!hasShopWxSession()) return;
|
||||||
|
localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + SHOP_SESSION_TTL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
export function saveAuth(data: ShopSessionPayload) {
|
export function saveAuth(data: ShopSessionPayload) {
|
||||||
localStorage.setItem(ACCESS_TOKEN, data.accessToken);
|
localStorage.setItem(ACCESS_TOKEN, data.accessToken);
|
||||||
if (data.refreshToken) localStorage.setItem(REFRESH_TOKEN, data.refreshToken);
|
if (data.refreshToken) localStorage.setItem(REFRESH_TOKEN, data.refreshToken);
|
||||||
@@ -53,10 +78,22 @@ export function saveAuth(data: ShopSessionPayload) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearAuth() {
|
/** 微信登录/绑定成功后写入 7 天免登录 session */
|
||||||
|
export function saveWechatSession(data: ShopSessionPayload) {
|
||||||
|
saveAuth(data);
|
||||||
|
localStorage.setItem(SHOP_WX_BOUND, '1');
|
||||||
|
localStorage.setItem(SESSION_EXPIRES_AT, String(Date.now() + SHOP_SESSION_TTL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAuth(options?: { keepProfile?: boolean }) {
|
||||||
localStorage.removeItem(ACCESS_TOKEN);
|
localStorage.removeItem(ACCESS_TOKEN);
|
||||||
localStorage.removeItem(REFRESH_TOKEN);
|
localStorage.removeItem(REFRESH_TOKEN);
|
||||||
localStorage.removeItem(STORE_PROFILE);
|
localStorage.removeItem(SESSION_EXPIRES_AT);
|
||||||
|
localStorage.removeItem(SHOP_WX_BOUND);
|
||||||
|
if (!options?.keepProfile) {
|
||||||
|
localStorage.removeItem(STORE_PROFILE);
|
||||||
|
localStorage.removeItem(LAST_PHONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLoggedIn() {
|
export function isLoggedIn() {
|
||||||
@@ -109,6 +146,7 @@ async function refreshSession(): Promise<ShopSessionPayload | null> {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
saveAuth(data);
|
saveAuth(data);
|
||||||
|
touchShopSession();
|
||||||
return data;
|
return data;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
@@ -147,6 +185,10 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
|
|||||||
if (!isLoggedIn()) {
|
if (!isLoggedIn()) {
|
||||||
return { authenticated: false, store: null };
|
return { authenticated: false, store: null };
|
||||||
}
|
}
|
||||||
|
if (isShopSessionExpired()) {
|
||||||
|
clearAuth({ keepProfile: true });
|
||||||
|
return { authenticated: false, store: getStoreProfile() };
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const me = await rawRequest<StoreProfile>('/shop/auth/me');
|
const me = await rawRequest<StoreProfile>('/shop/auth/me');
|
||||||
const store = profileFromMe(me);
|
const store = profileFromMe(me);
|
||||||
@@ -155,6 +197,7 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
|
|||||||
refreshToken: localStorage.getItem(REFRESH_TOKEN) ?? '',
|
refreshToken: localStorage.getItem(REFRESH_TOKEN) ?? '',
|
||||||
store,
|
store,
|
||||||
});
|
});
|
||||||
|
touchShopSession();
|
||||||
return { authenticated: true, store };
|
return { authenticated: true, store };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const err = e as Error & { status?: number };
|
const err = e as Error & { status?: number };
|
||||||
@@ -163,8 +206,8 @@ export async function ensureSession(): Promise<{ authenticated: boolean; store:
|
|||||||
if (refreshed?.store) {
|
if (refreshed?.store) {
|
||||||
return { authenticated: true, store: refreshed.store };
|
return { authenticated: true, store: refreshed.store };
|
||||||
}
|
}
|
||||||
clearAuth();
|
clearAuth({ keepProfile: true });
|
||||||
return { authenticated: false, store: null };
|
return { authenticated: false, store: getStoreProfile() };
|
||||||
}
|
}
|
||||||
const cached = getStoreProfile();
|
const cached = getStoreProfile();
|
||||||
if (cached) return { authenticated: true, store: cached };
|
if (cached) return { authenticated: true, store: cached };
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
|
import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types';
|
||||||
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
||||||
|
import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk';
|
||||||
import { isWechatEnv, weixinSdk } from './weixin';
|
import { isWechatEnv, weixinSdk } from './weixin';
|
||||||
import { request, saveAuth, type ShopSessionPayload } from './api';
|
import { request, saveWechatSession, type ShopSessionPayload } from './api';
|
||||||
|
|
||||||
export type ShopAccountProfile = {
|
export type ShopAccountProfile = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -51,20 +52,17 @@ export function sessionFromWechatLogin(result: WechatLoginResult): ShopSessionPa
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveShopWechatAuth(result: WechatLoginResult): boolean {
|
/** 处理微信登录/绑定结果,写入 7 天免登录 session */
|
||||||
|
export function handleShopWechatLoginResult(result: WechatLoginResult): ShopSessionPayload | null {
|
||||||
const session = sessionFromWechatLogin(result);
|
const session = sessionFromWechatLogin(result);
|
||||||
if (!session) return false;
|
if (!session) return null;
|
||||||
saveAuth(session);
|
saveWechatSession(session);
|
||||||
return true;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
|
/** @deprecated 使用 handleShopWechatLoginResult */
|
||||||
const config = await fetchClientConfig();
|
export function saveShopWechatAuth(result: WechatLoginResult): boolean {
|
||||||
if (!isWxAuthorizeEnabled(config)) return;
|
return !!handleShopWechatLoginResult(result);
|
||||||
if (!isWechatEnv()) {
|
|
||||||
throw new Error('请在微信内打开以完成授权');
|
|
||||||
}
|
|
||||||
return weixinSdk.login();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleShopWechatCallback(): Promise<WechatLoginResult | null> {
|
export async function handleShopWechatCallback(): Promise<WechatLoginResult | null> {
|
||||||
@@ -73,3 +71,34 @@ export async function handleShopWechatCallback(): Promise<WechatLoginResult | nu
|
|||||||
if (!isWxAuthorizeEnabled(config)) return null;
|
if (!isWxAuthorizeEnabled(config)) return null;
|
||||||
return weixinSdk.handleOAuthCallback();
|
return weixinSdk.handleOAuthCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信一键登录(已绑定微信的门店账号免验证码)。
|
||||||
|
* 返回 session = 已登录;void = 已跳转授权页等待回调。
|
||||||
|
*/
|
||||||
|
export async function loginShopWithWechat(): Promise<ShopSessionPayload | null | void> {
|
||||||
|
const config = await fetchClientConfig();
|
||||||
|
if (!isWxAuthorizeEnabled(config)) return null;
|
||||||
|
if (!isWechatEnv()) {
|
||||||
|
throw new Error(WECHAT_INAPP_REQUIRED_MSG);
|
||||||
|
}
|
||||||
|
const result = await weixinSdk.login();
|
||||||
|
if (result) return handleShopWechatLoginResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 短信登录成功后于微信内自动发起 OAuth,绑定 openId 便于后续免登 */
|
||||||
|
export async function bindShopWechatAfterSmsLogin(): Promise<void> {
|
||||||
|
const config = await fetchClientConfig();
|
||||||
|
if (!isWxAuthorizeEnabled(config)) return;
|
||||||
|
if (!isWechatEnv()) return;
|
||||||
|
await weixinSdk.login();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authorizeShopWechat(): Promise<WechatLoginResult | void> {
|
||||||
|
const config = await fetchClientConfig();
|
||||||
|
if (!isWxAuthorizeEnabled(config)) return;
|
||||||
|
if (!isWechatEnv()) {
|
||||||
|
throw new Error(WECHAT_INAPP_REQUIRED_MSG);
|
||||||
|
}
|
||||||
|
return weixinSdk.login();
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import {
|
|||||||
checkNeedsWechatAuth,
|
checkNeedsWechatAuth,
|
||||||
fetchShopAccount,
|
fetchShopAccount,
|
||||||
handleShopWechatCallback,
|
handleShopWechatCallback,
|
||||||
saveShopWechatAuth,
|
handleShopWechatLoginResult,
|
||||||
sessionFromWechatLogin,
|
|
||||||
} from '../lib/wechat-auth';
|
} from '../lib/wechat-auth';
|
||||||
import { isWechatEnv, weixinSdk } from '../lib/weixin';
|
import { isWechatEnv, weixinSdk } from '../lib/weixin';
|
||||||
import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk';
|
import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk';
|
||||||
@@ -56,9 +55,8 @@ export default function HomePage() {
|
|||||||
void handleShopWechatCallback()
|
void handleShopWechatCallback()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const session = sessionFromWechatLogin(result);
|
const session = handleShopWechatLoginResult(result);
|
||||||
if (session) {
|
if (session) {
|
||||||
saveShopWechatAuth(result);
|
|
||||||
applySession(session);
|
applySession(session);
|
||||||
}
|
}
|
||||||
setAuthModalOpen(false);
|
setAuthModalOpen(false);
|
||||||
|
|||||||
@@ -2,25 +2,42 @@ import { useEffect, useState } from 'react';
|
|||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import AppImage from '@dukang/shared-ui/AppImage';
|
import AppImage from '@dukang/shared-ui/AppImage';
|
||||||
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
|
||||||
|
import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk';
|
||||||
import { useStoreSession } from '../contexts/StoreSessionContext';
|
import { useStoreSession } from '../contexts/StoreSessionContext';
|
||||||
import { getLastPhone, getStoreProfile, request, type ShopSessionPayload } from '../lib/api';
|
import { getLastPhone, getStoreProfile, hasShopWxSession, request, saveAuth, type ShopSessionPayload } from '../lib/api';
|
||||||
import { fetchClientConfig } from '../lib/wechat-auth';
|
import {
|
||||||
|
bindShopWechatAfterSmsLogin,
|
||||||
|
fetchClientConfig,
|
||||||
|
handleShopWechatCallback,
|
||||||
|
handleShopWechatLoginResult,
|
||||||
|
loginShopWithWechat,
|
||||||
|
} from '../lib/wechat-auth';
|
||||||
|
import { isWechatEnv } from '../lib/weixin';
|
||||||
|
|
||||||
function maskPhone(phone: string) {
|
function maskPhone(phone: string) {
|
||||||
if (phone.length < 7) return phone;
|
if (phone.length < 7) return phone;
|
||||||
return `${phone.slice(0, 3)} **** ${phone.slice(-4)}`;
|
return `${phone.slice(0, 3)} **** ${phone.slice(-4)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatWechatError(e: unknown): string {
|
||||||
|
const text = e instanceof Error ? e.message : '微信登录失败';
|
||||||
|
if (text.includes('首次登录') || text.includes('手机验证码')) {
|
||||||
|
return '该微信尚未绑定门店账号,请先使用手机验证码登录,登录后将自动关联微信';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { applySession } = useStoreSession();
|
const { applySession } = useStoreSession();
|
||||||
const [params] = useSearchParams();
|
const [params, setSearchParams] = useSearchParams();
|
||||||
const quick = params.get('quick') === '1';
|
const quick = params.get('quick') === '1';
|
||||||
const savedProfile = getStoreProfile();
|
const savedProfile = getStoreProfile();
|
||||||
const [phone, setPhone] = useState(getLastPhone());
|
const [phone, setPhone] = useState(getLastPhone());
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
const [agreed, setAgreed] = useState(true);
|
const [agreed, setAgreed] = useState(true);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [wxLoading, setWxLoading] = useState(false);
|
||||||
const [msg, setMsg] = useState('');
|
const [msg, setMsg] = useState('');
|
||||||
const [codeCooldown, setCodeCooldown] = useState(0);
|
const [codeCooldown, setCodeCooldown] = useState(0);
|
||||||
const [wxAuthorize, setWxAuthorize] = useState(false);
|
const [wxAuthorize, setWxAuthorize] = useState(false);
|
||||||
@@ -31,6 +48,22 @@ export default function LoginPage() {
|
|||||||
.catch(() => setWxAuthorize(false));
|
.catch(() => setWxAuthorize(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isWechatEnv() || !wxAuthorize || !params.get('code')) return;
|
||||||
|
void handleShopWechatCallback()
|
||||||
|
.then((result) => {
|
||||||
|
if (!result) return;
|
||||||
|
const session = handleShopWechatLoginResult(result);
|
||||||
|
if (session) {
|
||||||
|
applySession(session);
|
||||||
|
stripOAuthParamsFromLocation();
|
||||||
|
setSearchParams({}, { replace: true });
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => setMsg(formatWechatError(e)));
|
||||||
|
}, [applySession, navigate, params, setSearchParams, wxAuthorize]);
|
||||||
|
|
||||||
const quickStoreName = savedProfile?.storeName ?? '门店管理中心';
|
const quickStoreName = savedProfile?.storeName ?? '门店管理中心';
|
||||||
const quickPhone = savedProfile?.phone || phone;
|
const quickPhone = savedProfile?.phone || phone;
|
||||||
|
|
||||||
@@ -66,22 +99,22 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function login(options?: { quick?: boolean }) {
|
async function login() {
|
||||||
if (!options?.quick && !ensureAgreed()) return;
|
if (!ensureAgreed()) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setMsg('');
|
setMsg('');
|
||||||
try {
|
try {
|
||||||
if (options?.quick) {
|
|
||||||
await request('SHOP_H5', '/shop/auth/sms/send', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ phone: quickPhone, scene: 'STORE_LOGIN' }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const data = await request<ShopSessionPayload>('SHOP_H5', '/shop/auth/login/sms', {
|
const data = await request<ShopSessionPayload>('SHOP_H5', '/shop/auth/login/sms', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ phone: options?.quick ? quickPhone : phone, code }),
|
body: JSON.stringify({ phone, code }),
|
||||||
});
|
});
|
||||||
|
saveAuth(data);
|
||||||
applySession(data);
|
applySession(data);
|
||||||
|
if (isWechatEnv() && wxAuthorize) {
|
||||||
|
setMsg('登录成功,正在关联微信…');
|
||||||
|
await bindShopWechatAfterSmsLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
navigate('/');
|
navigate('/');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg(e instanceof Error ? e.message : '登录失败');
|
setMsg(e instanceof Error ? e.message : '登录失败');
|
||||||
@@ -90,13 +123,30 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function wechatLogin() {
|
async function wechatLogin() {
|
||||||
if (!ensureAgreed()) return;
|
if (!ensureAgreed()) return;
|
||||||
if (!wxAuthorize) return;
|
setMsg('');
|
||||||
setMsg('preV1:微信一键授权暂未开放,请使用手机验证码登录');
|
if (!isWechatEnv()) {
|
||||||
|
setMsg('请在微信内打开以使用微信一键登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setWxLoading(true);
|
||||||
|
try {
|
||||||
|
const session = await loginShopWithWechat();
|
||||||
|
if (session) {
|
||||||
|
applySession(session);
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setMsg(formatWechatError(e));
|
||||||
|
} finally {
|
||||||
|
setWxLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quick) {
|
if (quick) {
|
||||||
|
const canWechatQuick = wxAuthorize && isWechatEnv() && hasShopWxSession() && !!savedProfile;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="shop-quick-login-page">
|
<div className="shop-quick-login-page">
|
||||||
<header className="shop-quick-header">
|
<header className="shop-quick-header">
|
||||||
@@ -129,18 +179,31 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
<div className="shop-quick-actions">
|
<div className="shop-quick-actions">
|
||||||
{msg && <p className="shop-login-msg">{msg}</p>}
|
{msg && <p className="shop-login-msg">{msg}</p>}
|
||||||
<button
|
{canWechatQuick ? (
|
||||||
type="button"
|
<button
|
||||||
className="shop-quick-login-btn"
|
type="button"
|
||||||
disabled={loading}
|
className="shop-quick-login-btn shop-quick-login-btn--wechat"
|
||||||
onClick={() => void login({ quick: true })}
|
disabled={wxLoading}
|
||||||
>
|
onClick={() => void wechatLogin()}
|
||||||
<span>{loading ? '登录中...' : '一键登录'}</span>
|
>
|
||||||
{!loading && <span className="material-symbols-outlined">arrow_forward</span>}
|
<span className="material-symbols-outlined">chat</span>
|
||||||
</button>
|
<span>{wxLoading ? '登录中...' : '微信一键登录'}</span>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<p className="shop-login-msg" style={{ textAlign: 'center' }}>
|
||||||
|
{wxAuthorize && !isWechatEnv()
|
||||||
|
? '请在微信内打开以使用一键登录'
|
||||||
|
: '请使用验证码登录并绑定微信后,即可 7 天内免登录'}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{!canWechatQuick && (
|
||||||
|
<Link to="/login" className="shop-quick-login-btn" style={{ textAlign: 'center', textDecoration: 'none' }}>
|
||||||
|
验证码登录
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<div className="shop-quick-secure">
|
<div className="shop-quick-secure">
|
||||||
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>lock</span>
|
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>lock</span>
|
||||||
<span>加密环境安全登录中</span>
|
<span>{canWechatQuick ? '微信验证 · 7 天内免登录' : '加密环境安全登录中'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -218,16 +281,21 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
{wxAuthorize && (
|
{wxAuthorize && (
|
||||||
<>
|
<>
|
||||||
<div className="shop-login-divider">
|
<div className="shop-login-divider">
|
||||||
<span className="shop-login-divider-line" />
|
<span className="shop-login-divider-line" />
|
||||||
<span className="shop-login-divider-text">或者</span>
|
<span className="shop-login-divider-text">或者</span>
|
||||||
<span className="shop-login-divider-line" />
|
<span className="shop-login-divider-line" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" className="shop-login-wechat" onClick={wechatLogin}>
|
<button
|
||||||
<span className="material-symbols-outlined">chat</span>
|
type="button"
|
||||||
<span>微信一键授权</span>
|
className="shop-login-wechat"
|
||||||
</button>
|
disabled={wxLoading}
|
||||||
|
onClick={() => void wechatLogin()}
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">chat</span>
|
||||||
|
<span>{wxLoading ? '登录中...' : '微信一键登录'}</span>
|
||||||
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -252,9 +320,11 @@ export default function LoginPage() {
|
|||||||
<span className="material-symbols-outlined shop-fill-icon" style={{ opacity: 0.3, fontSize: 20, color: 'var(--color-outline)' }}>
|
<span className="material-symbols-outlined shop-fill-icon" style={{ opacity: 0.3, fontSize: 20, color: 'var(--color-outline)' }}>
|
||||||
security
|
security
|
||||||
</span>
|
</span>
|
||||||
<p style={{ marginTop: 16, textAlign: 'center' }}>
|
{hasShopWxSession() && savedProfile && (
|
||||||
<Link to="/login?quick=1" className="text-primary body-md">一键登录</Link>
|
<p style={{ marginTop: 16, textAlign: 'center' }}>
|
||||||
</p>
|
<Link to="/login?quick=1" className="text-primary body-md">微信快捷登录</Link>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -473,6 +473,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shop-quick-login-btn--wechat {
|
||||||
|
background: #07c160;
|
||||||
|
box-shadow: 0 8px 24px rgba(7, 193, 96, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
.shop-quick-secure {
|
.shop-quick-secure {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1393,7 +1393,8 @@ export class AuthService {
|
|||||||
phoneVerified,
|
phoneVerified,
|
||||||
};
|
};
|
||||||
const accessToken = this.jwtService.sign(payload);
|
const accessToken = this.jwtService.sign(payload);
|
||||||
const refreshToken = this.jwtService.sign(payload, { expiresIn: '30d' });
|
const refreshExpiresIn = actorType === 'STORE' ? '7d' : '30d';
|
||||||
|
const refreshToken = this.jwtService.sign(payload, { expiresIn: refreshExpiresIn });
|
||||||
return {
|
return {
|
||||||
accessToken,
|
accessToken,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
|
|||||||
Reference in New Issue
Block a user