feat(store): dual business hours, avg price, and status lock
CI / verify (pull_request) Has been cancelled

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>
This commit is contained in:
2026-07-26 10:16:22 +08:00
parent 94d2a88581
commit 92cf51ba3d
25 changed files with 560 additions and 146 deletions
+2 -2
View File
@@ -34,8 +34,8 @@ export const ORDER_STATUS_COLORS: Record<string, string> = {
export const STORE_STATUS_LABELS: Record<string, string> = {
OPEN: '营业中',
PAUSED: '暂停',
CLOSED: '关闭',
PAUSED: '临时闭店',
CLOSED: '永久关闭',
};
export const STORE_AUDIT_STATUS_LABELS: Record<string, string> = {
+68 -1
View File
@@ -262,7 +262,20 @@ type StoreRow = {
createdAt: string;
cityRef?: { name: string; code: string };
partner?: { companyName: string };
account?: { phone: string; name: string; status: string };
account?: {
phone: string;
name: string;
status: string;
bankAccountName?: string | null;
bankAccountNo?: string | null;
bankBranch?: string | null;
};
openTime?: string | null;
closeTime?: string | null;
openTime2?: string | null;
closeTime2?: string | null;
avgPrice?: number | null;
settlementRate?: number;
category?: { id: string; name: string; parentId?: string | null } | null;
};
@@ -525,6 +538,11 @@ export default function StoresPage() {
address: d.address,
district: d.district,
settlementRate: d.settlementRate != null ? Number(d.settlementRate) * 100 : 60,
openTime: d.openTime || '10:00',
closeTime: d.closeTime || '22:00',
openTime2: d.openTime2 || undefined,
closeTime2: d.closeTime2 || undefined,
avgPrice: d.avgPrice != null ? Number(d.avgPrice) : undefined,
});
setDrawerOpen(true);
}}></Button>
@@ -640,9 +658,39 @@ export default function StoresPage() {
<Descriptions.Item label="驳回原因">{String(detail.rejectReason || '—')}</Descriptions.Item>
) : null}
<Descriptions.Item label="地址">{String(detail.province)}{String(detail.cityName)}{String(detail.district)}{String(detail.address)}</Descriptions.Item>
<Descriptions.Item label="营业时间">
{[
detail.openTime && detail.closeTime
? `${String(detail.openTime)}-${String(detail.closeTime)}`
: null,
detail.openTime2 && detail.closeTime2
? `${String(detail.openTime2)}-${String(detail.closeTime2)}`
: null,
]
.filter(Boolean)
.join('') || '—'}
</Descriptions.Item>
<Descriptions.Item label="人均费用">
{detail.avgPrice != null ? `¥${Number(detail.avgPrice).toFixed(0)}` : '—'}
</Descriptions.Item>
<Descriptions.Item label="核销结算比例">
{detail.settlementRate != null ? `${Math.round(Number(detail.settlementRate) * 100)}%` : '60%'}
</Descriptions.Item>
<Descriptions.Item label="结算户名">
{detail.account && typeof detail.account === 'object' && 'bankAccountName' in detail.account
? String((detail.account as { bankAccountName?: string | null }).bankAccountName || '—')
: '—'}
</Descriptions.Item>
<Descriptions.Item label="开户银行">
{detail.account && typeof detail.account === 'object' && 'bankBranch' in detail.account
? String((detail.account as { bankBranch?: string | null }).bankBranch || '—')
: '—'}
</Descriptions.Item>
<Descriptions.Item label="银行卡号">
{detail.account && typeof detail.account === 'object' && 'bankAccountNo' in detail.account
? String((detail.account as { bankAccountNo?: string | null }).bankAccountNo || '—')
: '—'}
</Descriptions.Item>
<Descriptions.Item label="核销数">{String(detail.redeemCount ?? 0)}</Descriptions.Item>
<Descriptions.Item label="操作">
<Button type="link" size="small" style={{ padding: 0 }} onClick={() => navigate(`/logs/stores?storeId=${detail.id}`)}>
@@ -670,6 +718,25 @@ export default function StoresPage() {
<Form.Item name="intro" label="介绍"><Input.TextArea rows={4} /></Form.Item>
<Form.Item name="district" label="区县"><Input /></Form.Item>
<Form.Item name="address" label="详细地址"><Input /></Form.Item>
<Form.Item name="avgPrice" label="人均费用(选填)">
<InputNumber min={0} precision={0} style={{ width: '100%' }} addonAfter="元" placeholder="用户端展示" />
</Form.Item>
<Space wrap style={{ width: '100%' }}>
<Form.Item name="openTime" label="营业开始" rules={[{ required: true }]}>
<Input type="time" style={{ width: 140 }} />
</Form.Item>
<Form.Item name="closeTime" label="营业结束" rules={[{ required: true }]}>
<Input type="time" style={{ width: 140 }} />
</Form.Item>
</Space>
<Space wrap style={{ width: '100%' }}>
<Form.Item name="openTime2" label="第二段开始(选填)">
<Input type="time" style={{ width: 140 }} />
</Form.Item>
<Form.Item name="closeTime2" label="第二段结束">
<Input type="time" style={{ width: 140 }} />
</Form.Item>
</Space>
<Form.Item name="settlementRate" label="核销结算比例 %" initialValue={60} rules={[{ required: true }]}>
<InputNumber min={0} max={100} precision={2} style={{ width: '100%' }} addonAfter="%" />
</Form.Item>