开合伙人子账号要验证手机号

This commit is contained in:
2026-07-12 17:53:54 +08:00
parent 908a5dcf0f
commit 3b4833b51a
7 changed files with 176 additions and 3 deletions
+9
View File
@@ -10,12 +10,21 @@ export function listPartnerStaff() {
return request<PartnerStaffItem[]>('PARTNER_H5', '/partner/staff');
}
export function sendPartnerStaffPhoneSms(phone: string) {
return request<{ ok: boolean; maskedPhone: string }>('PARTNER_H5', '/partner/staff/send-phone-sms', {
method: 'POST',
body: JSON.stringify({ phone: phone.trim() }),
silent: true,
});
}
export function createPartnerStaff(body: CreatePartnerStaffRequest) {
return request<PartnerStaffItem>('PARTNER_H5', '/partner/staff', {
method: 'POST',
body: JSON.stringify({
name: body.name,
phone: body.phone,
smsCode: body.smsCode,
staffRole: PartnerStaffRole.INTERNAL,
}),
});
+85 -1
View File
@@ -1,18 +1,22 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import PageHeader from '@dukang/shared-ui/PageHeader';
import { createPartnerStaff } from '../lib/staff';
import { createPartnerStaff, sendPartnerStaffPhoneSms } from '../lib/staff';
import { toastError, toastSuccess } from '../lib/toast';
type FieldErrors = {
name?: string;
phone?: string;
smsCode?: string;
};
export default function StaffCreatePage() {
const navigate = useNavigate();
const [name, setName] = useState('');
const [phone, setPhone] = useState('');
const [smsCode, setSmsCode] = useState('');
const [smsCooldown, setSmsCooldown] = useState(0);
const [smsHint, setSmsHint] = useState('');
const [submitting, setSubmitting] = useState(false);
const [submitError, setSubmitError] = useState('');
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
@@ -25,13 +29,51 @@ export default function StaffCreatePage() {
function patchPhone(value: string) {
setPhone(value);
setSmsHint('');
setSubmitError('');
setFieldErrors((prev) => ({ ...prev, phone: undefined, smsCode: undefined }));
if (smsCode) setSmsCode('');
}
function patchSmsCode(value: string) {
setSmsCode(value.replace(/\D/g, ''));
setSubmitError('');
setFieldErrors((prev) => ({ ...prev, smsCode: undefined }));
}
async function sendPhoneCode() {
const trimmedPhone = phone.trim();
if (!/^1[3-9]\d{9}$/.test(trimmedPhone)) {
setFieldErrors({ phone: '请先填写正确的11位手机号' });
return;
}
setSmsHint('');
setFieldErrors((prev) => ({ ...prev, phone: undefined }));
try {
const res = await sendPartnerStaffPhoneSms(trimmedPhone);
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 submit() {
const trimmedName = name.trim();
const trimmedPhone = phone.trim();
const trimmedSmsCode = smsCode.trim();
const nextFieldErrors: FieldErrors = {};
if (!trimmedName) nextFieldErrors.name = '请填写真实姓名';
@@ -40,6 +82,11 @@ export default function StaffCreatePage() {
} else if (!/^1[3-9]\d{9}$/.test(trimmedPhone)) {
nextFieldErrors.phone = '请输入正确的11位手机号';
}
if (!trimmedSmsCode) {
nextFieldErrors.smsCode = '请输入手机号验证码';
} else if (!/^\d{4,6}$/.test(trimmedSmsCode)) {
nextFieldErrors.smsCode = '验证码格式不正确';
}
if (Object.keys(nextFieldErrors).length) {
setFieldErrors(nextFieldErrors);
@@ -54,11 +101,16 @@ export default function StaffCreatePage() {
await createPartnerStaff({
name: trimmedName,
phone: trimmedPhone,
smsCode: trimmedSmsCode,
});
toastSuccess('子账号创建成功');
navigate('/center/staff');
} catch (e) {
const message = e instanceof Error ? e.message : '创建失败';
if (message.includes('验证码')) {
setFieldErrors({ smsCode: message });
return;
}
if (message.includes('手机号')) {
setFieldErrors({ phone: message });
return;
@@ -114,6 +166,38 @@ export default function StaffCreatePage() {
<p className="partner-field-error" role="alert">{fieldErrors.phone}</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={smsCode}
onChange={(e) => patchSmsCode(e.target.value)}
/>
</div>
<button
type="button"
className="partner-code-btn"
disabled={smsCooldown > 0 || submitting}
onClick={() => void sendPhoneCode()}
>
{smsCooldown > 0 ? `${smsCooldown}s` : '获取验证码'}
</button>
</div>
{smsHint && (
<p className="label-md text-muted" style={{ marginTop: 8 }}>{smsHint}</p>
)}
{fieldErrors.smsCode && (
<p className="partner-field-error" role="alert">{fieldErrors.smsCode}</p>
)}
</div>
</section>
<div className="partner-info-banner">