门店页面修改

This commit is contained in:
2026-07-07 00:18:29 +08:00
parent 1fcafac2a7
commit 5d19089f91
11 changed files with 432 additions and 39 deletions
+121 -20
View File
@@ -1,7 +1,20 @@
import { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { useStoreSession } from '../contexts/StoreSessionContext';
import { request } from '../lib/api';
import { parseRedeemTokenFromScan } from '../lib/redeem-scan';
import {
authorizeShopWechat,
fetchShopAccount,
handleShopWechatCallback,
needsWechatAuth,
saveShopWechatAuth,
sessionFromWechatLogin,
} from '../lib/wechat-auth';
import { isWechatEnv, weixinSdk } from '../lib/weixin';
import WechatScanAuthModal from '../components/WechatScanAuthModal';
const PENDING_SCAN_KEY = 'shop_pending_scan';
function formatMoney(n: number) {
return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
@@ -9,37 +22,107 @@ function formatMoney(n: number) {
export default function HomePage() {
const navigate = useNavigate();
const { applySession } = useStoreSession();
const [searchParams, setSearchParams] = useSearchParams();
const [dash, setDash] = useState<Record<string, unknown> | null>(null);
const [open, setOpen] = useState(true);
const [scanMsg, setScanMsg] = useState('');
const [scanning, setScanning] = useState(false);
const [authModalOpen, setAuthModalOpen] = useState(false);
const [authLoading, setAuthLoading] = useState(false);
const [authError, setAuthError] = useState('');
useEffect(() => {
request('SHOP_H5', '/shop/dashboard').then((d) => {
request<Record<string, unknown>>('SHOP_H5', '/shop/dashboard').then((d) => {
setDash(d);
setOpen(String((d.store as Record<string, unknown>)?.status) === 'OPEN');
});
}, []);
useEffect(() => {
if (!isWechatEnv() || !searchParams.get('code')) return;
void handleShopWechatCallback()
.then((result) => {
if (!result) return;
const session = sessionFromWechatLogin(result);
if (session) {
saveShopWechatAuth(result);
applySession(session);
}
setAuthModalOpen(false);
setAuthError('');
setSearchParams({}, { replace: true });
const shouldScan = sessionStorage.getItem(PENDING_SCAN_KEY) === '1';
sessionStorage.removeItem(PENDING_SCAN_KEY);
if (shouldScan) {
void runScan();
}
})
.catch((e) => {
setAuthError(e instanceof Error ? e.message : '微信授权失败');
});
}, [searchParams, applySession, setSearchParams]);
async function runScan() {
if (!isWechatEnv()) {
navigate('/redeem');
return;
}
setScanning(true);
setScanMsg('');
try {
await weixinSdk.init();
const raw = await weixinSdk.scanQrCode();
if (!raw) return;
const token = parseRedeemTokenFromScan(raw);
if (!token) {
setScanMsg('无法识别核销码,请扫描用户出示的核销二维码');
return;
}
navigate(`/redeem?token=${encodeURIComponent(token)}`);
} catch (e) {
setScanMsg(e instanceof Error ? e.message : '扫码失败,请重试');
} finally {
setScanning(false);
}
}
async function handleScan() {
setScanMsg('');
if (!isWechatEnv()) {
navigate('/redeem');
return;
}
try {
const profile = await fetchShopAccount();
if (needsWechatAuth(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<string, unknown> | undefined;
const recent = (dash?.recentRecords as Array<Record<string, unknown>>) || [];
const openTime = String(store?.openTime || '10:00');
const closeTime = String(store?.closeTime || '22:00');
async function handleScan() {
if (isWechatEnv()) {
try {
await weixinSdk.init();
const token = await weixinSdk.scanQrCode();
if (token) {
navigate(`/redeem?token=${encodeURIComponent(token)}`);
return;
}
} catch {
/* fall through to manual redeem page */
}
}
navigate('/redeem');
}
return (
<div className="shop-home-page">
<header className="shop-home-header">
@@ -68,10 +151,16 @@ export default function HomePage() {
</section>
<section className="shop-home-scan">
<button type="button" className="shop-home-scan-btn" onClick={handleScan}>
<button
type="button"
className="shop-home-scan-btn"
disabled={scanning}
onClick={() => void handleScan()}
>
<span className="material-symbols-outlined">qr_code_scanner</span>
</button>
<p className="shop-home-scan-label"></p>
<p className="shop-home-scan-label">{scanning ? '正在打开相机…' : '扫码核销'}</p>
{scanMsg && <p className="shop-home-scan-msg" role="alert">{scanMsg}</p>}
</section>
<section className="shop-home-status">
@@ -117,6 +206,18 @@ export default function HomePage() {
</div>
</section>
</div>
<WechatScanAuthModal
open={authModalOpen}
loading={authLoading}
error={authError}
onAuthorize={() => void startWechatAuth()}
onCancel={() => {
setAuthModalOpen(false);
setAuthError('');
sessionStorage.removeItem(PENDING_SCAN_KEY);
}}
/>
</div>
);
}
+4 -6
View File
@@ -9,8 +9,6 @@ function maskPhone(phone: string) {
return `${phone.slice(0, 3)} **** ${phone.slice(-4)}`;
}
const DEV_DEFAULT_CODE = import.meta.env.DEV ? '123456' : '';
export default function LoginPage() {
const navigate = useNavigate();
const { applySession } = useStoreSession();
@@ -18,8 +16,8 @@ export default function LoginPage() {
const quick = params.get('quick') === '1';
const savedProfile = getStoreProfile();
const [phone, setPhone] = useState(getLastPhone());
const [code, setCode] = useState(DEV_DEFAULT_CODE);
const [agreed, setAgreed] = useState(false);
const [code, setCode] = useState('');
const [agreed, setAgreed] = useState(true);
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState('');
const [codeCooldown, setCodeCooldown] = useState(0);
@@ -43,7 +41,7 @@ export default function LoginPage() {
method: 'POST',
body: JSON.stringify({ phone, scene: 'STORE_LOGIN' }),
});
setMsg(import.meta.env.DEV ? '验证码已发送(Mock: 123456' : '验证码已发送');
setMsg('验证码已发送');
setCodeCooldown(60);
const timer = setInterval(() => {
setCodeCooldown((c) => {
@@ -202,7 +200,7 @@ export default function LoginPage() {
type="button"
className="shop-login-submit"
disabled={loading}
onClick={login}
onClick={() => void login()}
>
<span>{loading ? '登录中...' : '登录'}</span>
{!loading && <span className="material-symbols-outlined">arrow_forward</span>}
+1 -1
View File
@@ -60,7 +60,7 @@ export default function RedeemConfirmPage() {
return;
}
if (!token.trim()) {
setMsg('请在开发者选项中输入核销码');
setMsg('请先扫码获取核销码');
return;
}
setLoading(true);