Files
dukang/apps/h5-shop/src/pages/HomePage.tsx
T
jacy 92cf51ba3d
CI / verify (pull_request) Has been cancelled
feat(store): dual business hours, avg price, and status lock
Support 1-2 hour segments and optional avgPrice; show bank settlement on HQ store detail; enforce pickup min 2 bottles; Chinese order statuses; block shop reopen after permanent close.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 10:16:22 +08:00

140 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback, useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import PullToRefresh from '@dukang/shared-ui/PullToRefresh';
import { request } from '../lib/api';
function formatMoney(n: number) {
return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
}
export default function HomePage() {
const navigate = useNavigate();
const [dash, setDash] = useState<Record<string, unknown> | null>(null);
const [open, setOpen] = useState(true);
const loadDashboard = useCallback(() => {
return request<Record<string, unknown>>('SHOP_H5', '/shop/dashboard')
.then((d) => {
setDash(d);
setOpen(String((d.store as Record<string, unknown>)?.status) === 'OPEN');
})
.catch(() => {});
}, []);
useEffect(() => {
void loadDashboard();
}, [loadDashboard]);
useEffect(() => {
function onResume() {
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 store = dash?.store as Record<string, unknown> | undefined;
const recent = (dash?.recentRecords as Array<Record<string, unknown>>) || [];
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 (
<PullToRefresh onRefresh={loadDashboard} className="shop-home-page">
<header className="shop-home-header">
<h1 className="app-page-title">门店管理中心</h1>
</header>
<div className="shop-home-content">
<section className="shop-home-hero">
<div className="shop-home-hero-store">
<span className="material-symbols-outlined shop-fill-icon">store</span>
<h2>{String(store?.name || '门店')}</h2>
</div>
<div className="shop-home-stats">
<div className="shop-home-stat">
<p className="shop-home-stat-label">今日核销笔数</p>
<p className="shop-home-stat-value">{Number(dash?.todayCount || 0)}</p>
</div>
<div className="shop-home-stat">
<p className="shop-home-stat-label">今日到账金额</p>
<p className="shop-home-stat-value">
<span style={{ fontSize: 18 }}>¥</span>
{formatMoney(Number(dash?.todayAmount || 0))}
</p>
</div>
</div>
</section>
<section className="shop-home-scan">
<Link
to="/redeem/phone"
className="shop-home-scan-btn"
>
<span className="material-symbols-outlined">smartphone</span>
</Link>
<p className="shop-home-scan-label">手机号核销</p>
</section>
<section className="shop-home-status">
<div className="shop-home-status-left">
<div className={`shop-home-status-icon${open ? '' : ' closed'}`}>
<span className="material-symbols-outlined shop-fill-icon">schedule</span>
</div>
<div>
<p className="shop-home-status-title">营业状态</p>
<p className="shop-home-status-sub">{statusText}</p>
<p className="shop-home-status-sub">营业时间: {hoursText}</p>
</div>
</div>
<label className="shop-home-switch" onClick={() => navigate('/status')}>
<input type="checkbox" checked={open && status !== 'CLOSED'} readOnly tabIndex={-1} />
<span className="shop-home-switch-track" />
</label>
</section>
<section>
<div className="shop-home-records-head">
<h3 className="shop-home-records-title">核销记录</h3>
<Link to="/records" className="shop-home-records-link">
查看全部
<span className="material-symbols-outlined" style={{ fontSize: 16 }}>chevron_right</span>
</Link>
</div>
<div className="shop-home-record-list">
{recent.length === 0 && (
<p className="shop-home-status-sub" style={{ textAlign: 'center', padding: '16px 0' }}>暂无核销记录</p>
)}
{recent.map((r) => (
<div key={String(r.id)} className="shop-home-record-item">
<div>
<p className="shop-home-record-time">核销时间</p>
<p className="shop-home-record-value">
{new Date(String(r.createdAt)).toLocaleString('zh-CN')}
</p>
</div>
<p className="shop-home-record-amount">¥{formatMoney(Number(r.amount))}</p>
</div>
))}
</div>
</section>
</div>
</PullToRefresh>
);
}