feat: multi-module iteration

This commit is contained in:
2026-08-04 21:38:49 +08:00
parent 9d96c73246
commit 71f508e02b
1366 changed files with 202004 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
import type { ClientRuntimeConfig, WechatLoginResult } 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 { request, saveWechatSession, type ShopSessionPayload } from './api';
export type ShopAccountProfile = {
id: string;
storeId: string;
name: string;
phone: string;
/** 是否已绑定微信(来自 /shop/auth/me account.hasWechat */
hasWechat?: boolean;
wxOpenId?: string | null;
store?: { name: string };
};
/** 微信未绑定门店账号时的登录提示 */
export const SHOP_WX_NEED_PHONE_LOGIN_MSG = '请先用手机号登录';
const SHOP_WX_LOGIN_HINT_KEY = 'shop_wx_login_hint';
export function isShopWechatUnboundError(message: string): boolean {
return message.includes('首次登录') || message.includes('手机验证码') || message.includes('请先用手机号');
}
export function formatShopWechatError(e: unknown): string {
const text = e instanceof Error ? e.message : '微信登录失败';
if (isShopWechatUnboundError(text)) return SHOP_WX_NEED_PHONE_LOGIN_MSG;
return text;
}
/** OAuth 在启动层失败时暂存提示,供登录页展示 */
export function stashShopWechatLoginHint(message: string): void {
try {
sessionStorage.setItem(SHOP_WX_LOGIN_HINT_KEY, message);
} catch {
/* ignore */
}
}
export function consumeShopWechatLoginHint(): string | null {
try {
const hint = sessionStorage.getItem(SHOP_WX_LOGIN_HINT_KEY);
if (hint) sessionStorage.removeItem(SHOP_WX_LOGIN_HINT_KEY);
return hint;
} catch {
return null;
}
}
export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
return request<ClientRuntimeConfig>('SHOP_H5', '/common/client-config');
}
export async function fetchShopAccount(): Promise<ShopAccountProfile> {
const me = await request<{
id?: string;
storeId?: string;
name?: string;
phone?: string;
account?: {
id?: string;
name?: string;
phone?: string;
hasWechat?: boolean;
};
store?: { name?: string } | null;
}>('SHOP_H5', '/shop/auth/me');
return {
id: String(me.account?.id ?? me.id ?? ''),
storeId: String(me.storeId ?? ''),
name: String(me.account?.name ?? me.name ?? ''),
phone: String(me.account?.phone ?? me.phone ?? ''),
hasWechat: !!me.account?.hasWechat,
store: me.store?.name ? { name: String(me.store.name) } : undefined,
};
}
export function needsWechatAuth(
profile: ShopAccountProfile | null,
config?: Pick<ClientRuntimeConfig, 'wxAuthorize'> | null,
): boolean {
if (config && !isWxAuthorizeEnabled(config)) return false;
const unbound = !!profile && !(profile.hasWechat || profile.wxOpenId);
return isWechatEnv() && unbound;
}
export async function checkNeedsWechatAuth(profile: ShopAccountProfile | null): Promise<boolean> {
const config = await fetchClientConfig();
return needsWechatAuth(profile, config);
}
export function sessionFromWechatLogin(result: WechatLoginResult): ShopSessionPayload | null {
if (!result.accessToken || !result.refreshToken) return null;
const store = result.store as Record<string, unknown> | undefined;
const stores = Array.isArray((result as { stores?: unknown }).stores)
? ((result as { stores: ShopSessionPayload['stores'] }).stores)
: undefined;
const account = (result as { account?: ShopSessionPayload['account'] }).account;
return {
accessToken: result.accessToken,
refreshToken: result.refreshToken,
stores,
account,
selectedStoreId: (result as { selectedStoreId?: string }).selectedStoreId,
store: store
? {
id: String(store.id ?? account?.id ?? ''),
storeId: String(store.storeId ?? ''),
name: String(store.name ?? account?.name ?? ''),
phone: String(store.phone ?? account?.phone ?? ''),
storeName: String(store.storeName ?? store.name ?? ''),
isPrimary: account?.isPrimary,
stores,
}
: undefined,
};
}
/** 处理微信登录/绑定结果,写入 7 天免登录 session */
export function handleShopWechatLoginResult(result: WechatLoginResult): ShopSessionPayload | null {
const session = sessionFromWechatLogin(result);
if (!session) return null;
saveWechatSession(session);
return session;
}
/** @deprecated 使用 handleShopWechatLoginResult */
export function saveShopWechatAuth(result: WechatLoginResult): boolean {
return !!handleShopWechatLoginResult(result);
}
export async function handleShopWechatCallback(): Promise<WechatLoginResult | null> {
if (!isWechatEnv()) return null;
const config = await fetchClientConfig();
if (!isWxAuthorizeEnabled(config)) return null;
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();
}