import { useCallback, useEffect, useRef, useState } from 'react'; import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import PullToRefresh from '@dukang/shared-ui/PullToRefresh'; import { isScanPermissionWarmupError } from '@dukang/weixin-sdk'; import { useStoreSession } from '../contexts/StoreSessionContext'; import { request } from '../lib/api'; import { parseRedeemTokenFromScan } from '../lib/redeem-scan'; import { authorizeShopWechat, checkNeedsWechatAuth, fetchShopAccount, } from '../lib/wechat-auth'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { clearPendingScanAfterAuth, getPostAuthScanDelayMs, markPendingScanAfterAuth, peekPendingScanAfterAuth, } from '../lib/shop-scan-auth'; import WechatScanAuthModal from '../components/WechatScanAuthModal'; import { useStorePageView } from '../lib/usePageView'; import { trackStore } from '../lib/analytics'; function formatMoney(n: number) { return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }); } function formatScanError(e: unknown, opts?: { afterAuth?: boolean }): string { const msg = e instanceof Error ? e.message : '扫码失败,请重试'; if (/invalid signature/i.test(msg)) { return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名,并刷新页面后重试'; } if (isScanPermissionWarmupError(msg)) { if (opts?.afterAuth) { return '微信授权已完成,扫码权限仍在准备中,请再点一次「扫码核销」'; } return '微信权限校验尚未完成,请等待 1~2 秒后再次点击扫码(首次绑定微信时较常见)'; } return msg; } export default function HomePage() { useStorePageView('store_home_view'); const navigate = useNavigate(); const { ready, authenticated } = useStoreSession(); const [searchParams] = useSearchParams(); const [dash, setDash] = useState | null>(null); const [scanMsg, setScanMsg] = useState(''); const [scanning, setScanning] = useState(false); const [authModalOpen, setAuthModalOpen] = useState(false); const [authLoading, setAuthLoading] = useState(false); const [authError, setAuthError] = useState(''); const pendingScanStartedRef = useRef(false); const loadDashboard = useCallback(() => { return request>('SHOP_H5', '/shop/dashboard') .then((d) => { setDash(d); }) .catch(() => {}); }, []); useEffect(() => { void loadDashboard(); }, [loadDashboard]); useEffect(() => { function onResume() { setScanning(false); void loadDashboard(); } function onVisibility() { if (document.visibilityState === 'visible') onResume(); } document.addEventListener('visibilitychange', onVisibility); window.addEventListener('pageshow', onResume); window.addEventListener('focus', onResume); return () => { document.removeEventListener('visibilitychange', onVisibility); window.removeEventListener('pageshow', onResume); window.removeEventListener('focus', onResume); }; }, [loadDashboard]); const runScan = useCallback( async (opts?: { postAuthWarmup?: boolean }) => { trackStore('store_redeem_scan_start'); if (!isWechatEnv()) { setScanMsg('请在微信内打开门店端进行扫码核销'); return; } setScanning(true); if (!opts?.postAuthWarmup) { setScanMsg(''); } try { if (opts?.postAuthWarmup) { weixinSdk.reset(); } await weixinSdk.init(); const raw = await weixinSdk.scanQrCode( opts?.postAuthWarmup ? { postAuthWarmup: true } : undefined, ); if (!raw) { void loadDashboard(); return; } const token = parseRedeemTokenFromScan(raw); if (!token) { setScanMsg('无法识别核销码,请扫描用户出示的核销二维码'); return; } navigate(`/redeem?token=${encodeURIComponent(token)}`); } catch (e) { setScanMsg(formatScanError(e, { afterAuth: opts?.postAuthWarmup })); } finally { setScanning(false); } }, [loadDashboard, navigate], ); // OAuth 由 StoreSessionContext 单例处理;此处仅在授权完成后续扫 useEffect(() => { if (!ready || !authenticated || !isWechatEnv()) return; if (searchParams.get('code')) return; if (!peekPendingScanAfterAuth() || pendingScanStartedRef.current) return; pendingScanStartedRef.current = true; clearPendingScanAfterAuth(); setAuthModalOpen(false); setAuthLoading(false); setAuthError(''); setScanMsg('微信授权成功,正在准备扫码…'); const timer = window.setTimeout(() => { void runScan({ postAuthWarmup: true }); }, getPostAuthScanDelayMs()); return () => window.clearTimeout(timer); }, [ready, authenticated, searchParams, runScan]); async function handleScan() { setScanMsg(''); if (!isWechatEnv()) { setScanMsg('请在微信内打开门店端进行扫码核销'); return; } try { const profile = await fetchShopAccount(); if (await checkNeedsWechatAuth(profile)) { pendingScanStartedRef.current = false; setAuthModalOpen(true); return; } await runScan(); } catch (e) { setScanMsg(e instanceof Error ? e.message : '无法发起扫码'); } } async function startWechatAuth() { setAuthLoading(true); setAuthError(''); try { pendingScanStartedRef.current = false; markPendingScanAfterAuth(); await authorizeShopWechat(); } catch (e) { clearPendingScanAfterAuth(); setAuthError(e instanceof Error ? e.message : '微信授权失败'); setAuthLoading(false); } } const store = dash?.store as Record | undefined; const recent = (dash?.recentRecords as Array>) || []; const status = String(store?.status || ''); const open = status === 'OPEN'; const hoursParts: string[] = []; if (store?.openTime && store?.closeTime) hoursParts.push(`${store.openTime} - ${store.closeTime}`); if (store?.openTime2 && store?.closeTime2) hoursParts.push(`${store.openTime2} - ${store.closeTime2}`); const hoursText = hoursParts.length ? hoursParts.join(',') : '10:00 - 22:00'; const statusText = status === 'CLOSED' ? '永久关闭' : open ? '当前正在营业中' : '当前临时闭店'; return (

门店管理中心

store

{String(store?.name || '门店')}

今日核销笔数

{Number(dash?.todayCount || 0)}

扫码 {Number(dash?.todayScanCount || 0)} · 手机号 {Number(dash?.todayPhoneCount || 0)}

今日到账金额

¥ {formatMoney(Number(dash?.todayAmount || 0))}

{scanning ? '正在打开相机…' : '扫码核销'}

{scanMsg &&

{scanMsg}

} smartphone 手机号核销
schedule

营业状态

{statusText}

营业时间: {hoursText}

核销记录

查看全部 chevron_right
{recent.length === 0 && (

暂无核销记录

)} {recent.map((r) => (

核销时间

{new Date(String(r.createdAt)).toLocaleString('zh-CN')}

¥{formatMoney(Number(r.amount))}

))}
void startWechatAuth()} onCancel={() => { setAuthModalOpen(false); setAuthError(''); clearPendingScanAfterAuth(); pendingScanStartedRef.current = false; }} />
); }