226 lines
7.3 KiB
TypeScript
226 lines
7.3 KiB
TypeScript
import { useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import PageHeader from '@dukang/shared-ui/PageHeader';
|
|
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>({});
|
|
|
|
function patchName(value: string) {
|
|
setName(value);
|
|
setSubmitError('');
|
|
setFieldErrors((prev) => ({ ...prev, name: undefined }));
|
|
}
|
|
|
|
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 = '请填写真实姓名';
|
|
if (!trimmedPhone) {
|
|
nextFieldErrors.phone = '请填写手机号码';
|
|
} 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);
|
|
return;
|
|
}
|
|
|
|
setSubmitting(true);
|
|
setSubmitError('');
|
|
setFieldErrors({});
|
|
|
|
try {
|
|
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;
|
|
}
|
|
setSubmitError(message);
|
|
toastError(message);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="partner-page-sticky">
|
|
<PageHeader title="添加子账号" onBack={() => navigate('/center/staff')} />
|
|
|
|
{submitError && (
|
|
<p className="partner-form-error" role="alert">{submitError}</p>
|
|
)}
|
|
|
|
<section className="partner-form-card">
|
|
<div className="partner-section-title">
|
|
<div className="partner-section-bar" />
|
|
<h2 className="headline-md">子账号信息</h2>
|
|
</div>
|
|
|
|
<div className="partner-field">
|
|
<label>真实姓名 <span className="text-primary">*</span></label>
|
|
<div className="partner-field-input">
|
|
<span className="material-symbols-outlined">person</span>
|
|
<input
|
|
placeholder="请输入员工姓名"
|
|
value={name}
|
|
onChange={(e) => patchName(e.target.value)}
|
|
/>
|
|
</div>
|
|
{fieldErrors.name && (
|
|
<p className="partner-field-error" role="alert">{fieldErrors.name}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="partner-field">
|
|
<label>手机号码 <span className="text-primary">*</span></label>
|
|
<div className="partner-field-input">
|
|
<span className="material-symbols-outlined">call</span>
|
|
<input
|
|
type="tel"
|
|
placeholder="请输入11位手机号"
|
|
value={phone}
|
|
onChange={(e) => patchPhone(e.target.value)}
|
|
/>
|
|
</div>
|
|
{fieldErrors.phone && (
|
|
<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">
|
|
<div className="partner-bills-icon">
|
|
<span className="material-symbols-outlined">info</span>
|
|
</div>
|
|
<p className="label-md text-variant" style={{ marginTop: 0, lineHeight: 1.5 }}>
|
|
子账号创建后默认禁用,需在子账号列表中手动启用后方可登录。
|
|
</p>
|
|
</div>
|
|
|
|
<footer className="partner-sticky-footer">
|
|
<button
|
|
type="button"
|
|
className="partner-btn-primary"
|
|
disabled={submitting}
|
|
onClick={() => void submit()}
|
|
>
|
|
<span className="material-symbols-outlined" style={{ fontSize: 20 }}>person_add</span>
|
|
{submitting ? '创建中…' : '确认创建'}
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|