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 { resolveRegionBinding } from '../lib/china-region'; import { checkStorePhoneAvailable } from '../lib/storePhone'; import { fetchPartnerCities, type OpenCityOption } from '../lib/upload'; import { fetchPartnerProfile, handlePartnerWechatCallback, savePartnerWechatAuth, } from '../lib/wechat-auth'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk'; import { clearStoreDraft, defaultStoreForm, loadStoreDraft, saveStoreDraft, type StoreDraftForm, validateStoreStep1, validateStoreStep2, validateStoreStep3, } from '../lib/storeDraft'; const STEPS = ['基本信息', '照片上传', '结算资质'] as const; type FieldErrors = { phone?: string; }; function isPhoneConflictMessage(message: string) { return message.includes('手机号已绑定'); } export default function StoreCreatePage() { const navigate = useNavigate(); const [params, setParams] = useSearchParams(); const saved = loadStoreDraft(); 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(''); const [wechatReady, setWechatReady] = useState(false); 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 }); }, [step, form]); useEffect(() => { if (step !== 2 || !isWechatEnv()) return; void fetchPartnerProfile() .then((me) => setWechatReady(!!me.hasWechat)) .catch(() => setWechatReady(false)); void weixinSdk.init().catch(() => { /* OssUploadField 点击时会再次初始化 */ }); }, [step]); useEffect(() => { if (!isWechatEnv() || !params.get('code')) return; void handlePartnerWechatCallback() .then((result) => { if (result && savePartnerWechatAuth(result)) { setWechatReady(true); } stripOAuthParamsFromLocation(); const next = new URLSearchParams(params); next.delete('code'); next.delete('state'); setParams(next, { replace: true }); void weixinSdk.init().catch(() => { /* OssUploadField 点击时会再次初始化 */ }); }) .catch((e) => setSubmitError(e instanceof Error ? e.message : '微信授权失败')); }, [params, setParams]); useEffect(() => { void fetchPartnerCities() .then((items) => { setCities(items); if (!items.length) setCitiesError('暂无开城城市,请联系总部在后台配置'); }) .catch((e) => setCitiesError(e instanceof Error ? e.message : '加载开城城市失败')); }, []); 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 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) { setSubmitError(msg); return; } setCheckingPhone(true); setSubmitError(''); setFieldErrors({}); try { const phoneCheck = await checkStorePhoneAvailable(form.phone.trim()); if (!phoneCheck.available) { setFieldErrors({ phone: phoneCheck.message ?? '该手机号已绑定门店,请更换', }); return; } } catch (e) { setSubmitError(e instanceof Error ? e.message : '手机号校验失败'); return; } finally { setCheckingPhone(false); } } if (step === 2) { const msg = validateStoreStep2(form); if (msg) { setSubmitError(msg); return; } } goStep(step + 1); } async function submit() { const msg = validateStoreStep3(form); if (msg) { setSubmitError(msg); return; } const step1Msg = validateStoreStep1(form); if (step1Msg) { setSubmitError(step1Msg); goStep(1); return; } const step2Msg = validateStoreStep2(form); if (step2Msg) { setSubmitError(step2Msg); goStep(2); return; } setSubmitting(true); setSubmitError(''); setFieldErrors({}); try { const phoneCheck = await checkStorePhoneAvailable(form.phone.trim()); if (!phoneCheck.available) { setFieldErrors({ phone: phoneCheck.message ?? '该手机号已绑定门店,请更换', }); goStep(1); return; } 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(); navigate(`/stores/${result.store.id}`); } catch (e) { const message = e instanceof Error ? e.message : '提交失败'; if (isPhoneConflictMessage(message)) { setFieldErrors({ phone: message }); goStep(1); return; } setSubmitError(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}

)}