门店审核功能

This commit is contained in:
2026-07-14 15:25:17 +08:00
parent d61a43cc8c
commit e0f5977dd9
13 changed files with 479 additions and 40 deletions
+47 -13
View File
@@ -3,14 +3,23 @@ import { Link, useNavigate } from 'react-router-dom';
import { isLoggedIn, request } from '../lib/api';
import { usePartnerSession } from '../contexts/PartnerSessionContext';
import { isSubAccount } from '../lib/partnerAccess';
import { storeStatusLabel, storeStatusPillClass, type StoreStatusValue } from '../lib/storeStatus';
import {
canPartnerOpenStore,
storeAuditLabel,
storeAuditPillClass,
storeStatusLabel,
storeStatusPillClass,
type StoreStatusValue,
} from '../lib/storeStatus';
type StatusFilter = 'ALL' | StoreStatusValue;
type StatusFilter = 'ALL' | StoreStatusValue | 'PENDING_AUDIT' | 'REJECTED';
const FILTERS: { key: StatusFilter; label: string }[] = [
{ key: 'ALL', label: '全部' },
{ key: 'OPEN', label: '营业中' },
{ key: 'PAUSED', label: '暂时闭店' },
{ key: 'PENDING_AUDIT', label: '待审核' },
{ key: 'REJECTED', label: '已驳回' },
{ key: 'CLOSED', label: '关闭' },
];
@@ -35,11 +44,20 @@ export default function StoreListPage() {
const filtered = useMemo(() => stores.filter((s) => {
const matchQ = !q || String(s.name).includes(q) || String(s.address).includes(q);
const matchStatus = filter === 'ALL' || String(s.status).toUpperCase() === filter;
const audit = String(s.auditStatus || 'APPROVED').toUpperCase();
const status = String(s.status).toUpperCase();
let matchStatus = true;
if (filter === 'PENDING_AUDIT') matchStatus = audit === 'PENDING';
else if (filter === 'REJECTED') matchStatus = audit === 'REJECTED';
else if (filter !== 'ALL') matchStatus = status === filter;
return matchQ && matchStatus;
}), [stores, q, filter]);
async function updateStatus(storeId: string, next: StoreStatusValue) {
async function updateStatus(storeId: string, next: StoreStatusValue, auditStatus?: string) {
if (next === 'OPEN' && !canPartnerOpenStore(auditStatus)) {
setError(auditStatus === 'REJECTED' ? '门店审核未通过,请查看驳回原因并修改后重新提交' : '门店尚在总部审核中,通过后方可开门');
return;
}
if (next === 'CLOSED') {
const ok = window.confirm('关闭后不可恢复营业,确认关闭该门店?');
if (!ok) return;
@@ -98,9 +116,11 @@ export default function StoreListPage() {
{filtered.map((s) => {
const storeId = String(s.id);
const currentStatus = String(s.status).toUpperCase() as StoreStatusValue;
const auditStatus = String(s.auditStatus || 'APPROVED').toUpperCase();
const dim = currentStatus === 'CLOSED';
const storeName = String(s.name || '未命名门店');
const busy = updatingId === storeId;
const canOpen = canPartnerOpenStore(auditStatus);
return (
<div key={storeId} className={`partner-store-card${dim ? ' partner-store-card--dim' : ''}`}>
<Link to={`/stores/${storeId}`} className="partner-store-card-hit" style={{ color: 'inherit', textDecoration: 'none' }}>
@@ -109,10 +129,24 @@ export default function StoreListPage() {
<p className="label-md text-muted" style={{ marginBottom: 2 }}></p>
<h3 className="headline-md">{storeName}</h3>
<p className="label-md text-muted" style={{ marginTop: 4 }}>{String(s.address || s.district || '')}</p>
{auditStatus !== 'APPROVED' && (
<p className="label-md" style={{ marginTop: 8, color: auditStatus === 'REJECTED' ? 'var(--color-heritage-red)' : 'var(--color-secondary)' }}>
{storeAuditLabel(auditStatus)}
{auditStatus === 'REJECTED' && s.rejectReason ? `${String(s.rejectReason)}` : ''}
</p>
)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 6 }}>
{auditStatus !== 'APPROVED' ? (
<span className={`partner-status-pill ${storeAuditPillClass(auditStatus)}`}>
{storeAuditLabel(auditStatus)}
</span>
) : (
<span className={`partner-status-pill ${storeStatusPillClass(currentStatus)}`}>
{storeStatusLabel(currentStatus)}
</span>
)}
</div>
<span className={`partner-status-pill ${storeStatusPillClass(currentStatus)}`}>
{storeStatusLabel(currentStatus)}
</span>
</div>
</Link>
{!readonly && (
@@ -121,8 +155,8 @@ export default function StoreListPage() {
type="button"
className="btn btn-outline"
style={{ fontSize: 12, padding: '8px 12px' }}
disabled={busy || currentStatus === 'CLOSED' || currentStatus === 'PAUSED'}
onClick={() => void updateStatus(storeId, 'PAUSED')}
disabled={busy || currentStatus === 'CLOSED' || currentStatus === 'PAUSED' || auditStatus === 'PENDING'}
onClick={() => void updateStatus(storeId, 'PAUSED', auditStatus)}
>
</button>
@@ -131,7 +165,7 @@ export default function StoreListPage() {
className="btn btn-outline"
style={{ fontSize: 12, padding: '8px 12px', borderColor: 'var(--color-subtle-gray)', color: 'var(--color-subtle-gray)' }}
disabled={busy || currentStatus === 'CLOSED'}
onClick={() => void updateStatus(storeId, 'CLOSED')}
onClick={() => void updateStatus(storeId, 'CLOSED', auditStatus)}
>
</button>
@@ -140,10 +174,10 @@ export default function StoreListPage() {
type="button"
className="btn btn-outline"
style={{ fontSize: 12, padding: '8px 12px' }}
disabled={busy}
onClick={() => void updateStatus(storeId, 'OPEN')}
disabled={busy || !canOpen}
onClick={() => void updateStatus(storeId, 'OPEN', auditStatus)}
>
{canOpen ? '开门营业' : '待审核通过'}
</button>
)}
<Link to={`/stores/${storeId}`} className="partner-menu-icon" style={{ width: 40, height: 40, borderRadius: 8, textDecoration: 'none' }}>