import { useEffect, useMemo, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import PageHeader from '@dukang/shared-ui/PageHeader'; import ChinaRegionPicker from '../components/ChinaRegionPicker'; import OssUploadField from '../components/OssUploadField'; import { request } from '../lib/api'; import { toastError, toastSuccess } from '../lib/toast'; import { resolveRegionBinding } from '../lib/china-region'; import { checkStorePhoneAvailable } from '../lib/storePhone'; import { fetchPartnerCities, type OpenCityOption } from '../lib/upload'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { usePartnerSession } from '../contexts/PartnerSessionContext'; import { clearStoreDraft, defaultStoreForm, loadStoreDraft, saveStoreDraft, type StoreDraftForm, validateStoreStep1, validateStoreStep2, validateStoreStep3, patchEnvPhotoAt, } from '../lib/storeDraft'; const STEPS = ['基本信息', '照片上传', '结算资质'] as const; type FieldErrors = { phone?: string; }; function isPhoneConflictMessage(message: string) { return message.includes('手机号已绑定'); } function isPhoneValidationMessage(message: string) { return message.includes('联系电话') || message.includes('手机号'); } export default function StoreCreatePage() { const navigate = useNavigate(); const { account, refresh } = usePartnerSession(); const accountId = account?.id; const wechatReady = !!account?.hasWechat; const [params, setParams] = useSearchParams(); const saved = loadStoreDraft(accountId); const [form, setForm] = useState(saved?.form ?? defaultStoreForm()); const [submitError, setSubmitError] = useState(''); const [fieldErrors, setFieldErrors] = useState({}); const [submitting, setSubmitting] = useState(false); const [checkingPhone, setCheckingPhone] = useState(false); const [cities, setCities] = useState([]); const [citiesError, setCitiesError] = useState(''); function reportFormError(message: string) { setSubmitError(message); } const stepFromUrl = Number(params.get('step') || 0); const step = stepFromUrl >= 1 && stepFromUrl <= 3 ? stepFromUrl : (saved?.step ?? 1); useEffect(() => { if (!stepFromUrl && saved?.step) { setParams({ step: String(saved.step) }, { replace: true }); } }, [stepFromUrl, saved?.step, setParams]); useEffect(() => { saveStoreDraft({ step, form }, accountId); }, [step, form, accountId]); useEffect(() => { if (step !== 2 || !isWechatEnv()) return; void refresh(); void weixinSdk.init().catch(() => { /* OssUploadField 点击时会再次初始化 */ }); }, [step, refresh]); useEffect(() => { void fetchPartnerCities() .then((items) => { setCities(items); if (!items.length) setCitiesError('暂无开城城市,请联系总部在后台配置'); }) .catch((e) => { const msg = e instanceof Error ? e.message : '加载开城城市失败'; setCitiesError(msg); }); }, []); const regionBindingHint = useMemo(() => { if (!form.regionCodes.length) return null; const binding = resolveRegionBinding(form.regionCodes, cities); if (!binding) return null; if (binding.cityId && binding.matchedCity) { return `已匹配开城城市:${binding.matchedCity.name}`; } return '所选地区暂未开城,请联系总部配置对应区划'; }, [form.regionCodes, cities]); function patchForm(patch: Partial) { setForm((prev) => ({ ...prev, ...patch })); setSubmitError(''); if ('phone' in patch) { setFieldErrors((prev) => ({ ...prev, phone: undefined })); } } function patchEnvPhotoUrl(index: number, url: string) { setForm((prev) => ({ ...prev, envPhotoUrls: patchEnvPhotoAt(prev.envPhotoUrls, index, url), })); setSubmitError(''); } function bindRegionSelection(codes: string[]) { const binding = resolveRegionBinding(codes, cities); if (!binding) { patchForm({ regionCodes: codes, cityId: '', province: '', city: '', district: '' }); return; } patchForm({ regionCodes: codes, cityId: binding.cityId ?? '', province: binding.region.province, city: binding.region.city, district: binding.region.district, }); } function goStep(n: number) { setParams({ step: String(n) }); setSubmitError(''); } async function handleNext() { if (step === 1) { const msg = validateStoreStep1(form); if (msg) { reportFormError(msg); return; } setCheckingPhone(true); setSubmitError(''); setFieldErrors({}); try { const phoneCheck = await checkStorePhoneAvailable(form.phone.trim()); if (!phoneCheck.available) { const phoneMsg = phoneCheck.message ?? '该手机号已绑定门店,请更换'; setFieldErrors({ phone: phoneMsg }); return; } } catch (e) { reportFormError(e instanceof Error ? e.message : '手机号校验失败'); return; } finally { setCheckingPhone(false); } } if (step === 2) { const msg = validateStoreStep2(form); if (msg) { reportFormError(msg); return; } } goStep(step + 1); } async function submit() { const msg = validateStoreStep3(form); if (msg) { reportFormError(msg); return; } const step1Msg = validateStoreStep1(form); if (step1Msg) { if (isPhoneValidationMessage(step1Msg)) { setFieldErrors({ phone: step1Msg }); return; } reportFormError(step1Msg); goStep(1); return; } if (!/^\d+$/.test(form.cityId.trim())) { reportFormError('所选地区未匹配到开城城市,请重新选择'); goStep(1); return; } const step2Msg = validateStoreStep2(form); if (step2Msg) { reportFormError(step2Msg); goStep(2); return; } setSubmitting(true); setSubmitError(''); setFieldErrors({}); try { const phoneCheck = await checkStorePhoneAvailable(form.phone.trim()); if (!phoneCheck.available) { setFieldErrors({ phone: phoneCheck.message ?? '该手机号已绑定门店,请更换', }); return; } } catch (e) { reportFormError(e instanceof Error ? e.message : '手机号校验失败'); return; } try { const envPhotoUrls = form.envPhotoUrls.map((u) => u.trim()).filter(Boolean); const result = await request<{ store: { id: string } }>('PARTNER_H5', '/partner/stores', { method: 'POST', body: JSON.stringify({ cityId: form.cityId, province: form.province, city: form.city, name: form.name.trim(), phone: form.phone.trim(), district: form.district.trim(), address: form.address.trim(), intro: form.intro.trim() || undefined, coverUrl: form.coverUrl.trim() || undefined, envPhotoUrls: envPhotoUrls.length ? envPhotoUrls : undefined, contractUrl: form.contractUrl.trim() || undefined, bankAccountName: form.bankAccountName.trim(), bankAccountNo: form.bankAccountNo.replace(/\s/g, ''), bankBranch: form.bankBranch.trim(), }), }); clearStoreDraft(accountId); toastSuccess('门店录入成功'); navigate(`/stores/${result.store.id}`); } catch (e) { const message = e instanceof Error ? e.message : '提交失败'; if (isPhoneConflictMessage(message)) { setFieldErrors({ phone: message }); return; } setSubmitError(message); toastError(message); } finally { setSubmitting(false); } } const progress = step === 1 ? 0 : step === 2 ? 50 : 100; const nextDisabled = submitting || checkingPhone; return (
navigate('/stores')} /> {(submitError || citiesError) && (

{submitError || citiesError}

)} {step === 1 && ( <>

门店基本信息

{regionBindingHint && (

{regionBindingHint}

)}
store patchForm({ name: e.target.value })} />
call patchForm({ phone: e.target.value })} />
{fieldErrors.phone && (

{fieldErrors.phone}

)}