开店需要验证手机号

This commit is contained in:
2026-07-12 17:51:20 +08:00
parent b550ee2255
commit 908a5dcf0f
7 changed files with 235 additions and 9 deletions
+9 -1
View File
@@ -6,6 +6,7 @@ export type StoreDraftForm = {
district: string;
name: string;
phone: string;
storeSmsCode: string;
address: string;
intro: string;
coverUrl: string;
@@ -34,6 +35,7 @@ export const defaultStoreForm = (): StoreDraftForm => ({
cityId: '',
name: '',
phone: '',
storeSmsCode: '',
address: '',
intro: '',
coverUrl: '',
@@ -64,6 +66,7 @@ function normalizeForm(raw: Record<string, unknown>): StoreDraftForm {
district: String(raw.district ?? base.district),
name: String(raw.name ?? base.name),
phone: String(raw.phone ?? base.phone),
storeSmsCode: String(raw.storeSmsCode ?? base.storeSmsCode),
address: String(raw.address ?? base.address),
intro: String(raw.intro ?? base.intro),
coverUrl: String(raw.coverUrl ?? base.coverUrl),
@@ -112,13 +115,18 @@ const PHONE_RE = /^1\d{10}$/;
const BANK_RE = /^\d{16,19}$/;
export function validateStoreStep1(
form: Pick<StoreDraftForm, 'regionCodes' | 'cityId' | 'name' | 'phone' | 'address' | 'intro'>,
form: Pick<
StoreDraftForm,
'regionCodes' | 'cityId' | 'name' | 'phone' | 'storeSmsCode' | 'address' | 'intro'
>,
): string | null {
if (!form.regionCodes || form.regionCodes.length < 3) return '请选择省 / 市 / 区县';
if (!form.cityId) return '所选地区未匹配到开城城市,请联系总部配置开城区划';
if (!form.name.trim()) return '请填写门店名称';
if (!form.phone.trim()) return '请填写联系电话';
if (!PHONE_RE.test(form.phone.trim())) return '联系电话须为11位手机号';
if (!form.storeSmsCode.trim()) return '请输入门店手机号验证码';
if (!/^\d{4,6}$/.test(form.storeSmsCode.trim())) return '验证码格式不正确';
if (!form.address.trim()) return '请填写详细地址';
if (form.intro.trim()) {
const len = form.intro.trim().length;
+8
View File
@@ -8,4 +8,12 @@ export function checkStorePhoneAvailable(phone: string) {
);
}
export function sendStorePhoneSms(phone: string) {
return request<{ ok: boolean; maskedPhone: string }>('PARTNER_H5', '/partner/stores/send-phone-sms', {
method: 'POST',
body: JSON.stringify({ phone: phone.trim() }),
silent: true,
});
}
export type { PartnerStorePhoneAvailableResponse };
+177 -7
View File
@@ -13,7 +13,7 @@ import { toastError, toastSuccess } from '../lib/toast';
import { resolveRegionBinding } from '../lib/china-region';
import { checkStorePhoneAvailable } from '../lib/storePhone';
import { checkStorePhoneAvailable, sendStorePhoneSms } from '../lib/storePhone';
import { fetchPartnerCities, type OpenCityOption } from '../lib/upload';
@@ -54,6 +54,8 @@ type FieldErrors = {
phone?: string;
storeSmsCode?: string;
};
@@ -68,7 +70,7 @@ function isPhoneConflictMessage(message: string) {
function isPhoneValidationMessage(message: string) {
return message.includes('联系电话') || message.includes('手机号');
return message.includes('联系电话') || message.includes('手机号') || message.includes('验证码');
}
@@ -108,6 +110,10 @@ export default function StoreCreatePage() {
const [citiesError, setCitiesError] = useState('');
const [smsCooldown, setSmsCooldown] = useState(0);
const [smsHint, setSmsHint] = useState('');
function reportFormError(message: string) {
setSubmitError(message);
}
@@ -216,16 +222,28 @@ export default function StoreCreatePage() {
function patchForm(patch: Partial<StoreDraftForm>) {
setForm((prev) => ({ ...prev, ...patch }));
setSubmitError('');
let nextPatch = patch;
if ('phone' in patch) {
setFieldErrors((prev) => ({ ...prev, phone: undefined }));
setFieldErrors((prev) => ({ ...prev, phone: undefined, storeSmsCode: undefined }));
setSmsHint('');
nextPatch = { ...patch, storeSmsCode: '' };
}
if ('storeSmsCode' in patch) {
setFieldErrors((prev) => ({ ...prev, storeSmsCode: undefined }));
}
setForm((prev) => ({ ...prev, ...nextPatch }));
}
@@ -286,6 +304,70 @@ export default function StoreCreatePage() {
async function sendStorePhoneCode() {
const phone = form.phone.trim();
if (!/^1\d{10}$/.test(phone)) {
setFieldErrors({ phone: '请先填写正确的11位手机号' });
return;
}
setSmsHint('');
setFieldErrors((prev) => ({ ...prev, phone: undefined }));
try {
const phoneCheck = await checkStorePhoneAvailable(phone);
if (!phoneCheck.available) {
setFieldErrors({ phone: phoneCheck.message ?? '该手机号不可用于门店账号' });
return;
}
const res = await sendStorePhoneSms(phone);
setSmsHint(`验证码已发送至 ${res.maskedPhone}`);
setSmsCooldown(60);
const timer = setInterval(() => {
setSmsCooldown((s) => {
if (s <= 1) {
clearInterval(timer);
return 0;
}
return s - 1;
});
}, 1000);
} catch (e) {
const msg = e instanceof Error ? e.message : '验证码发送失败';
setFieldErrors({ phone: msg });
}
}
async function handleNext() {
if (step === 1) {
@@ -293,7 +375,15 @@ export default function StoreCreatePage() {
const msg = validateStoreStep1(form);
if (msg) {
reportFormError(msg);
if (isPhoneValidationMessage(msg)) {
if (msg.includes('验证码')) {
setFieldErrors({ storeSmsCode: msg });
} else {
setFieldErrors({ phone: msg });
}
} else {
reportFormError(msg);
}
return;
}
@@ -365,7 +455,12 @@ export default function StoreCreatePage() {
if (step1Msg) {
if (isPhoneValidationMessage(step1Msg)) {
setFieldErrors({ phone: step1Msg });
if (step1Msg.includes('验证码')) {
setFieldErrors({ storeSmsCode: step1Msg });
} else {
setFieldErrors({ phone: step1Msg });
}
goStep(1);
return;
}
reportFormError(step1Msg);
@@ -448,6 +543,8 @@ export default function StoreCreatePage() {
phone: form.phone.trim(),
smsCode: form.storeSmsCode.trim(),
district: form.district.trim(),
address: form.address.trim(),
@@ -481,6 +578,11 @@ export default function StoreCreatePage() {
setFieldErrors({ phone: message });
return;
}
if (/验证码/.test(message)) {
setFieldErrors({ storeSmsCode: message });
goStep(1);
return;
}
setSubmitError(message);
toastError(message);
} finally {
@@ -631,6 +733,74 @@ export default function StoreCreatePage() {
)}
<p className="label-md text-muted" style={{ marginTop: 8 }}>
</p>
</div>
<div className="partner-field">
<label> <span className="text-primary">*</span></label>
<div className="partner-input-row">
<div className="partner-input-wrap" style={{ flex: 1 }}>
<span className="material-symbols-outlined partner-input-icon">shield</span>
<input
className="partner-input"
type="text"
inputMode="numeric"
maxLength={6}
placeholder="请输入短信验证码"
value={form.storeSmsCode}
onChange={(e) => patchForm({ storeSmsCode: e.target.value.replace(/\D/g, '') })}
/>
</div>
<button
type="button"
className="partner-code-btn"
disabled={smsCooldown > 0 || checkingPhone}
onClick={() => void sendStorePhoneCode()}
>
{smsCooldown > 0 ? `${smsCooldown}s` : '获取验证码'}
</button>
</div>
{smsHint && (
<p className="label-md text-muted" style={{ marginTop: 8 }}>{smsHint}</p>
)}
{fieldErrors.storeSmsCode && (
<p className="partner-field-error" role="alert">{fieldErrors.storeSmsCode}</p>
)}
</div>
<div className="partner-field">