门店端增加绑定微信按钮
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-07-16 08:30:04 +08:00
parent 8d7507e2d8
commit 22845e5d5f
3 changed files with 146 additions and 5 deletions
+25 -2
View File
@@ -9,6 +9,8 @@ export type ShopAccountProfile = {
storeId: string;
name: string;
phone: string;
/** 是否已绑定微信(来自 /shop/auth/me account.hasWechat */
hasWechat?: boolean;
wxOpenId?: string | null;
store?: { name: string };
};
@@ -18,7 +20,27 @@ export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
}
export async function fetchShopAccount(): Promise<ShopAccountProfile> {
return request<ShopAccountProfile>('SHOP_H5', '/shop/auth/me');
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(
@@ -26,7 +48,8 @@ export function needsWechatAuth(
config?: Pick<ClientRuntimeConfig, 'wxAuthorize'> | null,
): boolean {
if (config && !isWxAuthorizeEnabled(config)) return false;
return isWechatEnv() && !!profile && !profile.wxOpenId;
const unbound = !!profile && !(profile.hasWechat || profile.wxOpenId);
return isWechatEnv() && unbound;
}
export async function checkNeedsWechatAuth(profile: ShopAccountProfile | null): Promise<boolean> {
+90 -2
View File
@@ -1,21 +1,95 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { isWxAuthorizeEnabled } from '@dukang/shared-types';
import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk';
import { useStoreSession } from '../contexts/StoreSessionContext';
import { getStoreProfile, request } from '../lib/api';
import {
authorizeShopWechat,
fetchClientConfig,
fetchShopAccount,
handleShopWechatCallback,
handleShopWechatLoginResult,
} from '../lib/wechat-auth';
import { isWechatEnv } from '../lib/weixin';
export default function MinePage() {
const navigate = useNavigate();
const { resetSession, store: sessionStore } = useStoreSession();
const [searchParams, setSearchParams] = useSearchParams();
const { resetSession, store: sessionStore, applySession } = useStoreSession();
const profile = sessionStore ?? getStoreProfile();
const [store, setStore] = useState<Record<string, unknown> | null>(null);
const [hasWechat, setHasWechat] = useState<boolean | null>(null);
const [wxAuthorize, setWxAuthorize] = useState(false);
const [binding, setBinding] = useState(false);
const [bindMsg, setBindMsg] = useState('');
useEffect(() => {
request('SHOP_H5', '/shop/store').then(setStore).catch(() => setStore(null));
}, []);
useEffect(() => {
fetchClientConfig()
.then((config) => setWxAuthorize(isWxAuthorizeEnabled(config)))
.catch(() => setWxAuthorize(false));
}, []);
useEffect(() => {
fetchShopAccount()
.then((me) => setHasWechat(!!(me.hasWechat || me.wxOpenId)))
.catch(() => setHasWechat(null));
}, []);
useEffect(() => {
if (!isWechatEnv() || !wxAuthorize || !searchParams.get('code')) return;
void handleShopWechatCallback()
.then((result) => {
if (!result) return;
const session = handleShopWechatLoginResult(result);
if (session) {
applySession(session);
setHasWechat(true);
setBindMsg('微信绑定成功');
}
stripOAuthParamsFromLocation();
setSearchParams({}, { replace: true });
})
.catch((e) => setBindMsg(e instanceof Error ? e.message : '微信绑定失败'));
}, [applySession, searchParams, setSearchParams, wxAuthorize]);
const openTime = String(store?.openTime || '09:30');
const closeTime = String(store?.closeTime || '22:00');
const multiStore = (profile?.stores?.length ?? 0) > 1;
const showBindWechat = wxAuthorize && hasWechat === false;
async function bindWechat() {
if (binding) return;
setBindMsg('');
if (!isWechatEnv()) {
setBindMsg('请在微信内打开门店端以绑定微信');
return;
}
setBinding(true);
try {
const result = await authorizeShopWechat();
if (!result) {
// 跳转公众号 OAuth,回跳后由上面 useEffect 处理
return;
}
const session = handleShopWechatLoginResult(result);
if (session) {
applySession(session);
setHasWechat(true);
setBindMsg('微信绑定成功');
} else {
setBindMsg('绑定未完成,请重试');
}
} catch (e) {
setBindMsg(e instanceof Error ? e.message : '微信绑定失败');
} finally {
setBinding(false);
}
}
return (
<div className="shop-mine-page">
@@ -77,6 +151,20 @@ export default function MinePage() {
<p></p>
</div>
{bindMsg ? <p className="shop-mine-bind-msg">{bindMsg}</p> : null}
{showBindWechat ? (
<button
type="button"
className="shop-mine-bind-wechat"
disabled={binding}
onClick={() => void bindWechat()}
>
<span className="material-symbols-outlined" style={{ fontSize: 20 }}>chat</span>
{binding ? '绑定中…' : '绑定微信'}
</button>
) : null}
<button
type="button"
className="shop-mine-logout"
+31 -1
View File
@@ -2153,8 +2153,38 @@
color: var(--color-subtle-gray);
}
.shop-mine-bind-msg {
margin: 0 0 8px;
text-align: center;
font-size: 13px;
color: var(--color-heritage-red);
}
.shop-mine-bind-wechat {
margin-top: 8px;
width: 100%;
height: 48px;
border: none;
border-radius: var(--radius-md);
background: #07c160;
color: #fff;
font-family: var(--font-headline);
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
cursor: pointer;
}
.shop-mine-bind-wechat:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.shop-mine-logout {
margin-top: 24px;
margin-top: 12px;
width: 100%;
height: 48px;
border: 1px solid var(--color-heritage-red);