feat(admin): require business hours when creating stores
CI / verify (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
HQ create-store wizard now collects 1-2 hour segments and avgPrice, and API validates them on create/update. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,11 @@ export type StoreCreateForm = {
|
||||
phone: string;
|
||||
address: string;
|
||||
intro?: string;
|
||||
openTime?: string;
|
||||
closeTime?: string;
|
||||
openTime2?: string;
|
||||
closeTime2?: string;
|
||||
avgPrice?: number | null;
|
||||
coverUrl?: string;
|
||||
envPhotoUrls?: string[];
|
||||
contractUrl?: string;
|
||||
@@ -23,6 +28,12 @@ export type StoreCreateForm = {
|
||||
|
||||
const PHONE_RE = /^1\d{10}$/;
|
||||
const BANK_RE = /^\d{16,19}$/;
|
||||
const TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
||||
|
||||
function timeToMinutes(hhmm: string): number {
|
||||
const [h, m] = hhmm.split(':').map(Number);
|
||||
return h * 60 + m;
|
||||
}
|
||||
|
||||
export function validateStoreCreateStep1(
|
||||
form: Pick<
|
||||
@@ -35,6 +46,11 @@ export function validateStoreCreateStep1(
|
||||
| 'phone'
|
||||
| 'address'
|
||||
| 'intro'
|
||||
| 'openTime'
|
||||
| 'closeTime'
|
||||
| 'openTime2'
|
||||
| 'closeTime2'
|
||||
| 'avgPrice'
|
||||
>,
|
||||
): string | null {
|
||||
if (!form.partnerAccountId) return '请选择开城合伙人';
|
||||
@@ -45,6 +61,27 @@ export function validateStoreCreateStep1(
|
||||
if (!form.phone?.trim()) return '请填写门店手机号';
|
||||
if (!PHONE_RE.test(form.phone.trim())) return '门店手机号须为11位手机号';
|
||||
if (!form.address?.trim()) return '请填写详细地址';
|
||||
|
||||
const openTime = String(form.openTime || '').trim();
|
||||
const closeTime = String(form.closeTime || '').trim();
|
||||
if (!openTime || !closeTime) return '请填写营业时间';
|
||||
if (!TIME_RE.test(openTime) || !TIME_RE.test(closeTime)) return '营业时间格式须为 HH:MM';
|
||||
if (timeToMinutes(openTime) >= timeToMinutes(closeTime)) return '营业结束时间须晚于开始时间';
|
||||
|
||||
const openTime2 = String(form.openTime2 || '').trim();
|
||||
const closeTime2 = String(form.closeTime2 || '').trim();
|
||||
if (openTime2 || closeTime2) {
|
||||
if (!openTime2 || !closeTime2) return '请完整填写第二段营业时间';
|
||||
if (!TIME_RE.test(openTime2) || !TIME_RE.test(closeTime2)) return '第二段时间格式须为 HH:MM';
|
||||
if (timeToMinutes(openTime2) >= timeToMinutes(closeTime2)) return '第二段结束时间须晚于开始时间';
|
||||
if (timeToMinutes(closeTime) >= timeToMinutes(openTime2)) return '第二段开始时间须晚于第一段结束时间';
|
||||
}
|
||||
|
||||
if (form.avgPrice != null && String(form.avgPrice).trim() !== '') {
|
||||
const n = Number(form.avgPrice);
|
||||
if (Number.isNaN(n) || n < 0) return '人均费用须为非负数字';
|
||||
}
|
||||
|
||||
if (form.intro?.trim()) {
|
||||
const len = form.intro.trim().length;
|
||||
if (len < 10 || len > 500) return '门店简介须为 10~500 字';
|
||||
|
||||
@@ -405,6 +405,11 @@ export default function StoresPage() {
|
||||
createForm.setFieldsValue({
|
||||
envPhotoUrls: ['', '', ''],
|
||||
settlementRate: 60,
|
||||
openTime: '10:00',
|
||||
closeTime: '22:00',
|
||||
openTime2: undefined,
|
||||
closeTime2: undefined,
|
||||
avgPrice: undefined,
|
||||
});
|
||||
setCreateStep(0);
|
||||
setCreateError('');
|
||||
@@ -461,6 +466,14 @@ export default function StoresPage() {
|
||||
district: values.district.trim(),
|
||||
address: values.address.trim(),
|
||||
intro: values.intro?.trim() || undefined,
|
||||
openTime: values.openTime?.trim() || '10:00',
|
||||
closeTime: values.closeTime?.trim() || '22:00',
|
||||
...(values.openTime2?.trim() && values.closeTime2?.trim()
|
||||
? { openTime2: values.openTime2.trim(), closeTime2: values.closeTime2.trim() }
|
||||
: {}),
|
||||
...(values.avgPrice != null && values.avgPrice !== ''
|
||||
? { avgPrice: Number(values.avgPrice) }
|
||||
: {}),
|
||||
coverUrl: values.coverUrl?.trim() || undefined,
|
||||
envPhotoUrls: envPhotoUrls.length ? envPhotoUrls : undefined,
|
||||
contractUrl: values.contractUrl?.trim() || undefined,
|
||||
@@ -841,6 +854,25 @@ export default function StoresPage() {
|
||||
<Form.Item name="address" label="详细地址" rules={[{ required: true, message: '请填写详细地址' }]}>
|
||||
<Input.TextArea rows={2} placeholder="请输入详细门牌号" />
|
||||
</Form.Item>
|
||||
<Space wrap style={{ width: '100%' }}>
|
||||
<Form.Item name="openTime" label="营业开始" rules={[{ required: true, message: '请填写营业开始时间' }]}>
|
||||
<Input type="time" style={{ width: 140 }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="closeTime" label="营业结束" rules={[{ required: true, message: '请填写营业结束时间' }]}>
|
||||
<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="avgPrice" label="人均费用(选填)">
|
||||
<InputNumber min={0} precision={0} style={{ width: '100%' }} addonAfter="元" placeholder="用户端展示" />
|
||||
</Form.Item>
|
||||
<Form.Item name="intro" label="门店简介">
|
||||
<Input.TextArea rows={3} placeholder="选填,10~500字" showCount maxLength={500} />
|
||||
</Form.Item>
|
||||
|
||||
Reference in New Issue
Block a user