import { useCallback, useEffect, useState } from 'react'; import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import PullToRefresh from '@dukang/shared-ui/PullToRefresh'; import { useStoreSession } from '../contexts/StoreSessionContext'; import { request } from '../lib/api'; import { parseRedeemTokenFromScan } from '../lib/redeem-scan'; import { authorizeShopWechat, checkNeedsWechatAuth, fetchShopAccount, handleShopWechatCallback, handleShopWechatLoginResult, } from '../lib/wechat-auth'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk'; import WechatScanAuthModal from '../components/WechatScanAuthModal'; import { useStorePageView } from '../lib/usePageView'; import { trackStore } from '../lib/analytics'; const PENDING_SCAN_KEY = 'shop_pending_scan'; function formatMoney(n: number) { return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }); } function formatScanError(e: unknown): string { const msg = e instanceof Error ? e.message : '扫码失败,请重试'; if (/invalid signature/i.test(msg)) { return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名,并刷新页面后重试'; } if (/offline verifying|权限验证中|接口未就绪/i.test(msg)) { return '微信权限校验尚未完成,请等待 1~2 秒后再次点击扫码(首次绑定微信时较常见)'; } return msg; } export default function HomePage() { useStorePageView('store_home_view'); const navigate = useNavigate(); const { applySession } = useStoreSession(); const [searchParams, setSearchParams] = 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 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]); async function runScan(opts?: { postAuthWarmup?: boolean }) { trackStore('store_redeem_scan_start'); if (!isWechatEnv()) { setScanMsg('请在微信内打开门店端进行扫码核销'); return; } setScanning(true); setScanMsg(''); try { // 授权回跳后强制重签,避免沿用带 code 的旧签名态 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)); } finally { setScanning(false); } } useEffect(() => { if (!isWechatEnv() || !searchParams.get('code')) return; void handleShopWechatCallback() .then((result) => { if (!result) return; const session = handleShopWechatLoginResult(result); if (session) { applySession(session); } setAuthModalOpen(false); setAuthError(''); // 先清 OAuth 参数再扫,保证 JSSDK 签名 URL 与当前页一致 stripOAuthParamsFromLocation(); setSearchParams({}, { replace: true }); const shouldScan = sessionStorage.getItem(PENDING_SCAN_KEY) === '1'; sessionStorage.removeItem(PENDING_SCAN_KEY); if (shouldScan) { // OAuth 回跳后微信权限离线校验未完成;稍候再扫,失败可再点一次 window.setTimeout(() => void runScan({ postAuthWarmup: true }), 400); } }) .catch((e) => { setAuthError(e instanceof Error ? e.message : '微信授权失败'); }); }, [searchParams, applySession, setSearchParams]); async function handleScan() { setScanMsg(''); if (!isWechatEnv()) { setScanMsg('请在微信内打开门店端进行扫码核销'); return; } try { const profile = await fetchShopAccount(); if (await checkNeedsWechatAuth(profile)) { setAuthModalOpen(true); return; } await runScan(); } catch (e) { setScanMsg(e instanceof Error ? e.message : '无法发起扫码'); } } async function startWechatAuth() { setAuthLoading(true); setAuthError(''); try { sessionStorage.setItem(PENDING_SCAN_KEY, '1'); await authorizeShopWechat(); } catch (e) { sessionStorage.removeItem(PENDING_SCAN_KEY); 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(''); sessionStorage.removeItem(PENDING_SCAN_KEY); }} />
); }