import { useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { Alert, Badge, Button, Checkbox, Descriptions, Drawer, Form, Image, Input, InputNumber, Modal, Select, Space, Steps, Switch, Table, Tabs, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import type { FormInstance } from 'antd/es/form'; import { EnvironmentOutlined } from '@ant-design/icons'; import { isStoreContactPhone, STORE_CONTACT_PHONE_HINT } from '@dukang/domain'; import { request, type Paginated } from '../lib/api'; import { ADMIN_OPTIONS_PAGE_SIZE, STORE_AUDIT_STATUS_LABELS, STORE_STATUS_LABELS, fmtTime, } from '../lib/constants'; import { validateStoreCreateStep1, validateStoreCreateStep3, type StoreCreateForm, } from '../lib/storeCreate'; import { useAdminList } from '../lib/useAdminList'; import { resolveRegionBinding } from '../lib/china-region'; import ChinaRegionCascader from '../components/ChinaRegionCascader'; import OssUpload from '../components/OssUpload'; import MultiImageUpload from '../components/MultiImageUpload'; import TencentLocPickerModal from '../components/TencentLocPickerModal'; import AdminStorePackagesSection, { type AdminStorePackagesHandle, } from '../components/AdminStorePackagesSection'; import StorePackageAuditPanel, { auditStorePackageRequest, } from '../components/StorePackageAuditPanel'; const CREATE_STEPS = [ { title: '基本信息' }, { title: '照片上传' }, { title: '结算资质' }, ]; function fillGeolocation( setCoords: (lat: number, lng: number) => void, setLoading: (v: boolean) => void, ) { if (!navigator.geolocation) { message.error('当前浏览器不支持定位'); return; } setLoading(true); navigator.geolocation.getCurrentPosition( (pos) => { setCoords(pos.coords.latitude, pos.coords.longitude); message.success( `已获取坐标 ${pos.coords.latitude.toFixed(6)}, ${pos.coords.longitude.toFixed(6)}`, ); setLoading(false); }, (err) => { message.error(err.message || '定位失败'); setLoading(false); }, { enableHighAccuracy: true, timeout: 10000 }, ); } type StoreMediaItem = { id?: string; bizType?: string; mediaType?: string; url?: string | null; }; function collectMediaUrls(detail: Record) { const media = Array.isArray(detail.media) ? (detail.media as StoreMediaItem[]) : []; const byType = (bizType: string) => media .filter((item) => String(item.bizType || '').toUpperCase() === bizType) .map((item) => ({ id: String(item.id || item.url || ''), url: String(item.url || '').trim(), mediaType: item.mediaType ? String(item.mediaType) : undefined, })) .filter((item) => item.url); const covers = byType('COVER'); const coverUrl = detail.coverUrl ? String(detail.coverUrl).trim() : ''; if (coverUrl && !covers.some((item) => item.url === coverUrl)) { covers.unshift({ id: 'cover', url: coverUrl, mediaType: 'IMAGE' }); } return { covers, envs: byType('ENV'), contracts: byType('CONTRACT'), }; } function StoreAuditMediaEditor() { return (
); } function StoreVisibilityWhitelistFields({ form }: { form: FormInstance }) { const enabled = Form.useWatch('visibilityWhitelistEnabled', form); return ( <> {enabled ? ( 可见手机号见白名单管理 ) : null} ); } type StoreRow = { id: string; name: string; phone: string; status: string; auditStatus?: string; rejectReason?: string | null; cityName: string; district: string; address: string; intro: string | null; coverUrl: string | null; createdAt: string; visibilityWhitelistEnabled?: boolean; visibilityPhones?: string[]; /** 该门店当前待审核套餐变更的 requestId(无则为空) */ pendingPackageAuditId?: string | null; /** 该门店当前待审核信息变更的 requestId(无则为空) */ pendingInfoChangeId?: string | null; isTest?: boolean; cityRef?: { name: string; code: string }; partner?: { id?: string; companyName?: string | null; name?: string | null; phone?: string | null }; account?: { phone: string; name: string; status: string; bankAccountName?: string | null; bankAccountNo?: string | null; bankBranch?: string | null; }; openTime?: string | null; closeTime?: string | null; openTime2?: string | null; closeTime2?: string | null; avgPrice?: number | null; settlementRate?: number; sortOrder?: number; category?: { id: string; name: string; parentId?: string | null } | null; }; type PartnerOption = { id: string; companyName?: string | null; name?: string | null; phone?: string | null }; function partnerOptionLabel(p: PartnerOption): string { const company = (p.companyName || '').trim(); const person = (p.name || '').trim(); const phone = (p.phone || '').trim(); if (company && person) return `${company}-${person}`; return company || person || phone || p.id; } type CityOption = { id: string; name: string; code: string; partnerBindings?: Array<{ partnerAccountId: string; partnerCompanyName?: string }>; }; type CategoryNode = { id: string; name: string; status?: string; children?: CategoryNode[]; }; export default function StoresPage() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const initialCityId = searchParams.get('cityId') ?? ''; const initialPartnerId = searchParams.get('partnerId') ?? ''; const initialAuditStatus = searchParams.get('auditStatus') ?? ''; const initialStoreId = searchParams.get('storeId') ?? ''; const [form] = Form.useForm(); const [editForm] = Form.useForm(); const [createForm] = Form.useForm(); const [filters, setFilters] = useState>(() => { const init: Record = {}; if (initialCityId) init.cityId = initialCityId; if (initialPartnerId) init.partnerId = initialPartnerId; if (initialAuditStatus) init.auditStatus = initialAuditStatus; return init; }); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/stores', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', String(filters.name)); if (filters.status) qs.set('status', String(filters.status)); if (filters.auditStatus) qs.set('auditStatus', String(filters.auditStatus)); if (filters.phone) qs.set('phone', String(filters.phone)); if (filters.cityId) qs.set('cityId', String(filters.cityId)); if (filters.partnerId) qs.set('partnerId', String(filters.partnerId)); if (filters.excludeTest) qs.set('excludeTest', 'true'); return qs; }, [filters], ); const [filterCities, setFilterCities] = useState([]); const [filterPartners, setFilterPartners] = useState([]); const [detail, setDetail] = useState | null>(null); const [drawerOpen, setDrawerOpen] = useState(false); const [detailTab, setDetailTab] = useState('basic'); const packagesRef = useRef(null); const [packageRejectOpen, setPackageRejectOpen] = useState(false); const [packageRejectReason, setPackageRejectReason] = useState(''); const [packageAuditing, setPackageAuditing] = useState(false); const [rejectOpen, setRejectOpen] = useState(false); const [rejectReason, setRejectReason] = useState(''); const [auditing, setAuditing] = useState(false); const [createOpen, setCreateOpen] = useState(false); const [createStep, setCreateStep] = useState(0); const [createError, setCreateError] = useState(''); const [locating, setLocating] = useState(false); const [mapPickerOpen, setMapPickerOpen] = useState(false); const [mapPickerTarget, setMapPickerTarget] = useState<'create' | 'edit'>('create'); const [partners, setPartners] = useState([]); const [cities, setCities] = useState([]); const [categoryTree, setCategoryTree] = useState([]); const [optionsLoading, setOptionsLoading] = useState(false); const [saving, setSaving] = useState(false); const [phoneMismatch, setPhoneMismatch] = useState(null); const deepLinkStoreOpenedRef = useRef(false); useEffect(() => { void request>(`/admin/cities?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`) .then((res) => setFilterCities(res.items)) .catch(() => {}); void request>(`/admin/partners?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`) .then((res) => setFilterPartners(res.items)) .catch(() => {}); }, []); useEffect(() => { if (initialAuditStatus) { form.setFieldsValue({ auditStatus: initialAuditStatus }); } }, [form, initialAuditStatus]); useEffect(() => { const cityId = searchParams.get('cityId') ?? ''; const partnerId = searchParams.get('partnerId') ?? ''; let changed = false; setFilters((prev) => { const next = { ...prev }; if ((prev.cityId ?? '') !== cityId) { changed = true; if (cityId) next.cityId = cityId; else delete next.cityId; } if ((prev.partnerId ?? '') !== partnerId) { changed = true; if (partnerId) next.partnerId = partnerId; else delete next.partnerId; } return changed ? next : prev; }); form.setFieldsValue({ cityId: cityId || undefined, partnerId: partnerId || undefined, }); if (cityId || partnerId) setPage(1); }, [searchParams, form, setPage]); const selectedPartnerId = Form.useWatch('partnerAccountId', createForm); const selectedRegionCodes = Form.useWatch('regionCodes', createForm); const selectedCityId = Form.useWatch('cityId', createForm); const selectedCategoryParentId = Form.useWatch('categoryParentId', createForm); const editCategoryParentId = Form.useWatch('categoryParentId', editForm); const categoryParentOptions = useMemo( () => categoryTree .filter((n) => n.status !== 'INACTIVE') .map((n) => ({ value: n.id, label: n.name })), [categoryTree], ); const categoryChildOptions = useMemo(() => { const parent = categoryTree.find((n) => n.id === selectedCategoryParentId); return (parent?.children ?? []) .filter((n) => n.status !== 'INACTIVE') .map((n) => ({ value: n.id, label: n.name })); }, [categoryTree, selectedCategoryParentId]); const editCategoryChildOptions = useMemo(() => { const parent = categoryTree.find((n) => n.id === editCategoryParentId); return (parent?.children ?? []) .filter((n) => n.status !== 'INACTIVE') .map((n) => ({ value: n.id, label: n.name })); }, [categoryTree, editCategoryParentId]); async function openStoreDetail(row: StoreRow, opts?: { tab?: string }) { const [d, cats] = await Promise.all([ request>(`/admin/stores/${row.id}`), request('/admin/store-categories').catch(() => [] as CategoryNode[]), ]); setCategoryTree(Array.isArray(cats) ? cats : []); setDetail({ ...d, pendingPackageAuditId: row.pendingPackageAuditId ?? d.pendingPackageAuditId, pendingInfoChangeId: row.pendingInfoChangeId ?? d.pendingInfoChangeId, }); setDetailTab(opts?.tab || 'basic'); const category = d.category && typeof d.category === 'object' ? (d.category as { id?: string; parentId?: string | null }) : null; const account = d.account && typeof d.account === 'object' ? (d.account as { phone?: string | null; bankAccountName?: string | null; bankAccountNo?: string | null; bankBranch?: string | null; }) : null; const categoryId = category?.id != null ? String(category.id) : undefined; let parentId = category?.parentId != null ? String(category.parentId) : undefined; if (!parentId && categoryId) { for (const parent of cats) { if (parent.id === categoryId) { parentId = parent.id; break; } if ((parent.children ?? []).some((c) => c.id === categoryId)) { parentId = parent.id; break; } } } const loginPhone = (typeof d.loginPhone === 'string' && d.loginPhone) || account?.phone || (typeof d.phone === 'string' ? d.phone : undefined); const storePhone = typeof d.phone === 'string' ? d.phone : undefined; const contactPhone = (typeof d.contactPhone === 'string' && d.contactPhone.trim()) || storePhone || loginPhone || ''; const phoneMismatchNow = !!(loginPhone && storePhone && loginPhone !== storePhone); setPhoneMismatch(phoneMismatchNow ? String(loginPhone) : null); editForm.setFieldsValue({ name: d.name, // 登录手机号(老板);与 StoreAccount 同步 phone: loginPhone || storePhone, contactPhone, intro: d.intro, benefitUsageRule: d.benefitUsageRule != null && String(d.benefitUsageRule).trim() && !/^null$/i.test(String(d.benefitUsageRule).trim()) ? String(d.benefitUsageRule) : '', coverUrl: d.coverUrl, envPhotoUrls: (() => { const { envs } = collectMediaUrls(d); const urls = envs.map((item) => item.url).filter(Boolean); return urls; })(), contractUrls: (() => { const { contracts } = collectMediaUrls(d); return contracts.map((item) => item.url).filter(Boolean); })(), province: d.province, city: d.cityName, district: d.district, address: d.address, categoryParentId: parentId, categoryId, latitude: d.latitude != null ? Number(d.latitude) : undefined, longitude: d.longitude != null ? Number(d.longitude) : undefined, settlementRate: d.settlementRate != null ? Number(d.settlementRate) * 100 : 60, openTime: d.openTime || '10:00', closeTime: d.closeTime || '22:00', openTime2: d.openTime2 || undefined, closeTime2: d.closeTime2 || undefined, avgPrice: d.avgPrice != null ? Number(d.avgPrice) : undefined, bankAccountName: account?.bankAccountName || undefined, bankAccountNo: account?.bankAccountNo || undefined, bankBranch: account?.bankBranch || undefined, visibilityWhitelistEnabled: !!d.visibilityWhitelistEnabled, visibilityPhones: Array.isArray(d.visibilityPhones) ? (d.visibilityPhones as string[]) : [], isTest: !!d.isTest, sortOrder: d.sortOrder != null ? Number(d.sortOrder) : 0, }); setDrawerOpen(true); } useEffect(() => { if (!initialStoreId || deepLinkStoreOpenedRef.current || loading) return; const row = data?.items?.find((s) => String(s.id) === initialStoreId); if (row) { deepLinkStoreOpenedRef.current = true; void openStoreDetail(row); } else if (data && (data.items?.length ?? 0) >= 0) { // 列表无该店时仍尝试直拉详情 deepLinkStoreOpenedRef.current = true; void openStoreDetail({ id: initialStoreId } as StoreRow); } }, [data, initialStoreId, loading]); async function saveStoreDetail() { if (!detail) return; setSaving(true); try { const v = await editForm.validateFields(); const hasCoords = v.latitude != null && v.longitude != null && Number.isFinite(Number(v.latitude)) && Number.isFinite(Number(v.longitude)); const payload = { name: v.name, phone: v.phone, contactPhone: String(v.contactPhone || '').trim() || v.phone, coverUrl: v.coverUrl ?? '', envPhotoUrls: Array.isArray(v.envPhotoUrls) ? v.envPhotoUrls.map((u: string) => String(u || '').trim()).filter(Boolean) : [], contractUrls: Array.isArray(v.contractUrls) ? v.contractUrls.map((u: string) => String(u || '').trim()).filter(Boolean) : [], intro: v.intro, benefitUsageRule: typeof v.benefitUsageRule === 'string' && v.benefitUsageRule.trim() && !/^null$/i.test(v.benefitUsageRule.trim()) ? v.benefitUsageRule.trim() : null, categoryId: v.categoryId, province: v.province, city: v.city, district: v.district, address: v.address, avgPrice: v.avgPrice, openTime: v.openTime, closeTime: v.closeTime, openTime2: v.openTime2 || null, closeTime2: v.closeTime2 || null, settlementRate: v.settlementRate != null ? Number(v.settlementRate) / 100 : undefined, bankAccountName: v.bankAccountName ?? null, bankAccountNo: v.bankAccountNo ?? null, bankBranch: v.bankBranch ?? null, visibilityWhitelistEnabled: !!v.visibilityWhitelistEnabled, isTest: !!v.isTest, sortOrder: v.sortOrder != null ? Number(v.sortOrder) : 0, ...(hasCoords ? { latitude: Number(v.latitude), longitude: Number(v.longitude) } : {}), }; const updated = await request>(`/admin/stores/${detail.id}`, { method: 'PUT', body: JSON.stringify(payload), }); const packagesResult = await packagesRef.current?.saveIfLoaded({ quiet: true }); message.success( packagesResult?.skipped === false ? '门店信息与套餐已保存' : '门店信息已保存', ); setDetail(updated); setPhoneMismatch(null); void reload(); } catch (e) { if (e && typeof e === 'object' && 'errorFields' in e) return; message.error(e instanceof Error ? e.message : '保存失败'); } finally { setSaving(false); } } function bindRegionSelection(codes: string[], partnerAccountId?: string) { const binding = resolveRegionBinding(codes, cities, partnerAccountId ?? selectedPartnerId); if (!binding) { createForm.setFieldsValue({ regionCodes: codes, cityId: undefined }); return; } createForm.setFieldsValue({ regionCodes: codes, province: binding.region.province, city: binding.region.city, district: binding.region.district, districtCode: binding.region.districtCode, cityId: binding.cityId, }); } const regionBindingHint = useMemo(() => { if (!selectedRegionCodes?.length) return null; const binding = resolveRegionBinding(selectedRegionCodes, cities, selectedPartnerId); if (!binding) return null; if (binding.cityId && binding.matchedCity) { return `已匹配开城城市:${binding.matchedCity.name}(区划 ${binding.cityCode},区县 ${binding.region.districtCode})`; } return `区划 ${binding.cityCode} 暂未开城,请先在「开城 → 城市」添加`; }, [selectedRegionCodes, cities, selectedPartnerId]); async function loadOptions(opts?: { quiet?: boolean }) { setOptionsLoading(true); try { const qs = `pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`; const [p, c, cats] = await Promise.all([ request>(`/admin/partners?${qs}`), request>(`/admin/cities?${qs}`), request('/admin/store-categories'), ]); setPartners(p.items); setCities(c.items); setCategoryTree(Array.isArray(cats) ? cats : []); if (!opts?.quiet) { if (!p.items.length) message.warning('暂无开城合伙人,请先在「开城 → 城市」详情中创建合伙人'); if (!c.items.length) message.warning('暂无开城城市,请先在「开城 → 城市」中创建'); if (!Array.isArray(cats) || !cats.length) { message.warning('暂无门店分类,请先在「门店 → 门店分类」中配置'); } } } catch (e) { message.error(e instanceof Error ? e.message : '加载合伙人/城市/分类失败'); } finally { setOptionsLoading(false); } } function closeCreateModal() { setCreateOpen(false); setCreateStep(0); setCreateError(''); createForm.resetFields(); } function openCreateModal() { void loadOptions(); createForm.setFieldsValue({ envPhotoUrls: [], settlementRate: 60, sortOrder: 0, openTime: '10:00', closeTime: '22:00', openTime2: undefined, closeTime2: undefined, avgPrice: undefined, visibilityWhitelistEnabled: false, visibilityPhones: [], }); setCreateStep(0); setCreateError(''); setCreateOpen(true); } async function handleCreateNext() { const values = createForm.getFieldsValue(); if (createStep === 0) { const msg = validateStoreCreateStep1(values); if (msg) { setCreateError(msg); return; } try { await createForm.validateFields([ 'partnerAccountId', 'regionCodes', 'cityId', 'categoryParentId', 'categoryId', 'name', 'phone', 'address', ]); } catch { return; } } setCreateError(''); setCreateStep((s) => s + 1); } async function handleCreateSubmit() { try { const values = await createForm.validateFields(); const step3Msg = validateStoreCreateStep3(values); if (step3Msg) { setCreateError(step3Msg); return; } const envPhotoUrls = (values.envPhotoUrls ?? []).map((u: string) => u?.trim()).filter(Boolean) as string[]; const contractUrls = (values.contractUrls ?? []).map((u: string) => u?.trim()).filter(Boolean) as string[]; await request('/admin/stores', { method: 'POST', body: JSON.stringify({ partnerAccountId: values.partnerAccountId, cityId: values.cityId, categoryId: values.categoryId, province: values.province, city: values.city, name: values.name.trim(), phone: values.phone.trim(), contactPhone: String(values.contactPhone || values.phone || '').trim(), district: values.district.trim(), address: values.address.trim(), ...(values.latitude != null && values.longitude != null && Number.isFinite(Number(values.latitude)) && Number.isFinite(Number(values.longitude)) ? { latitude: Number(values.latitude), longitude: Number(values.longitude), } : {}), intro: values.intro?.trim() || undefined, benefitUsageRule: values.benefitUsageRule?.trim() || undefined, openTime: values.openTime?.trim() || '10:00', closeTime: values.closeTime?.trim() || '22:00', ...(values.openTime2?.trim() && values.closeTime2?.trim() ? { openTime2: values.openTime2.trim(), closeTime2: values.closeTime2.trim() } : {}), ...(values.avgPrice != null && values.avgPrice !== '' ? { avgPrice: Number(values.avgPrice) } : {}), coverUrl: values.coverUrl?.trim() || undefined, envPhotoUrls: envPhotoUrls.length ? envPhotoUrls : undefined, contractUrls: contractUrls.length ? contractUrls : undefined, bankAccountName: values.bankAccountName.trim(), bankAccountNo: values.bankAccountNo.replace(/\s/g, ''), bankBranch: values.bankBranch.trim(), settlementRate: Number(values.settlementRate ?? 60) / 100, sortOrder: values.sortOrder != null ? Number(values.sortOrder) : 0, visibilityWhitelistEnabled: !!values.visibilityWhitelistEnabled, }), }); message.success('门店已创建'); closeCreateModal(); void reload(); } catch (e) { if (e && typeof e === 'object' && 'errorFields' in e) { const fields = e as { errorFields?: Array<{ name: string[] }> }; const first = fields.errorFields?.[0]?.name?.[0]; if (first === 'partnerAccountId' || first === 'cityId' || first === 'regionCodes' || first === 'name' || first === 'phone') { setCreateStep(0); } return; } message.error(e instanceof Error ? e.message : '创建失败'); } } const columns: ColumnsType = [ { title: '封面', dataIndex: 'coverUrl', width: 72, render: (url) => url ? : '—', }, { title: '门店名', dataIndex: 'name', width: 180, ellipsis: { showTitle: false }, render: (v: string, row) => { const name = v || '—'; return ( {name} {row.isTest ? 测试 : null} ); }, }, { title: '分类', width: 100, ellipsis: true, render: (_, row) => row.category?.name || '—', }, { title: '城市', dataIndex: 'cityName', width: 80, ellipsis: true }, { title: '登录号', dataIndex: 'phone', width: 120 }, { title: '联系电话', dataIndex: 'contactPhone', width: 120, render: (v: string | null | undefined, row) => v || row.phone, }, { title: '营业状态', dataIndex: 'status', width: 90, render: (s) => {STORE_STATUS_LABELS[s] || s}, }, { title: '审核', dataIndex: 'auditStatus', width: 100, render: (s, row) => { const status = s || 'APPROVED'; const color = status === 'PENDING' ? 'orange' : status === 'REJECTED' ? 'red' : 'green'; return ( {STORE_AUDIT_STATUS_LABELS[status] || status} {status === 'REJECTED' && row.rejectReason ? ( {row.rejectReason} ) : null} ); }, }, { title: '开城合伙人', dataIndex: 'partner', width: 140, ellipsis: { showTitle: false }, render: (partner: StoreRow['partner']) => { if (!partner) return '—'; const label = partnerOptionLabel({ id: partner.id ?? '', ...partner }); return ( {label} ); }, }, { title: '可见', dataIndex: 'visibilityWhitelistEnabled', width: 90, render: (v) => v ? 限测 : 公开, }, { title: '介绍', dataIndex: 'intro', width: 160, ellipsis: true, render: (v) => v || '—' }, { title: '排序', dataIndex: 'sortOrder', width: 70 }, { title: '店长', dataIndex: ['account', 'name'], width: 90, ellipsis: true, render: (v) => v || '—' }, { title: '创建', dataIndex: 'createdAt', width: 160, render: fmtTime }, { title: '操作', width: 280, fixed: 'right', render: (_, row) => ( {row.pendingPackageAuditId ? ( ) : null} {row.pendingInfoChangeId ? ( <> ) : null} ), }, ]; return (
门店 共 {data?.total ?? 0} 家门店(含合伙人录入)
{ setFilters(v); setPage(1); }}> ({ value: p.id, label: partnerOptionLabel(p) }))} /> ({ value, label }))} /> 过滤测试账号
{ setPage(p); setPageSize(ps); }, }} /> setDrawerOpen(false)} extra={detail && ( {String(detail.auditStatus || '') === 'PENDING' || String(detail.auditStatus || '') === 'REJECTED' ? ( <> ) : null} {detail.pendingPackageAuditId ? ( <> ) : null} isStoreContactPhone(String(value || '')) ? Promise.resolve() : Promise.reject(new Error(STORE_CONTACT_PHONE_HINT)), }, ]} extra="用户端门店详情展示与拨号使用此号码,可与登录号不同,支持座机" > ), }, { key: 'settlement', label: '结算资质', forceRender: true, children: ( <> { const v = String(value || '').trim(); if (!v) return; if (!/^\d{16,19}$/.test(v)) { throw new Error('银行卡号须为 16–19 位数字'); } }, }, ]} > 修改后点右上角「保存修改」一并提交。 ), }, { key: 'media', label: '审核材料', forceRender: true, children: , }, { key: 'packages', label: detail.pendingPackageAuditId ? ( 套餐 ) : ( '套餐' ), forceRender: true, children: ( <> {detail.pendingPackageAuditId ? (
待审核套餐变更 { setDetail({ ...detail, pendingPackageAuditId: null }); void reload(); }} />
) : null} ), }, ]} /> )}
setPackageRejectOpen(false)} onOk={async () => { if (!detail?.pendingPackageAuditId) return; if (!packageRejectReason.trim()) { message.warning('请填写驳回原因'); return; } setPackageAuditing(true); try { await auditStorePackageRequest( String(detail.pendingPackageAuditId), 'REJECT', packageRejectReason.trim(), ); message.success('套餐已驳回'); setPackageRejectOpen(false); setDetail({ ...detail, pendingPackageAuditId: null }); void reload(); } catch (e) { message.error(e instanceof Error ? e.message : '套餐驳回失败'); } finally { setPackageAuditing(false); } }} > setPackageRejectReason(e.target.value)} /> {createStep > 0 && } {createStep < 2 ? ( ) : ( )} )} > {createError && ( )}
{ const raw = String(value || '').trim(); if (!raw || isStoreContactPhone(raw)) return Promise.resolve(); return Promise.reject(new Error(STORE_CONTACT_PHONE_HINT)); }, }, ]} > 可手动填写 / 定位 / 腾讯地图选点填入坐标
preV1 照片上传为选填,可直接下一步(与合伙人端一致)。
setRejectOpen(false)} onOk={async () => { if (!detail || !rejectReason.trim()) return; setAuditing(true); try { const updated = await request>(`/admin/stores/${detail.id}/audit`, { method: 'PUT', body: JSON.stringify({ approved: false, remark: rejectReason.trim() }), }); message.success('已驳回,原因已同步合伙人端'); setDetail({ ...detail, ...updated, auditStatus: 'REJECTED', rejectReason: rejectReason.trim() }); setRejectOpen(false); void reload(); } finally { setAuditing(false); } }} > 驳回原因将展示给合伙人,请说明需修改的内容。 setRejectReason(e.target.value)} /> setMapPickerOpen(false)} latitude={ mapPickerTarget === 'edit' ? Number(editForm.getFieldValue('latitude') ?? detail?.latitude) : Number(createForm.getFieldValue('latitude')) } longitude={ mapPickerTarget === 'edit' ? Number(editForm.getFieldValue('longitude') ?? detail?.longitude) : Number(createForm.getFieldValue('longitude')) } onPick={(loc) => { if (mapPickerTarget === 'edit') { editForm.setFieldsValue({ latitude: loc.latitude, longitude: loc.longitude, ...(loc.address && !editForm.getFieldValue('address') ? { address: loc.address } : {}), }); } else { createForm.setFieldsValue({ latitude: loc.latitude, longitude: loc.longitude, ...(loc.address && !createForm.getFieldValue('address') ? { address: loc.address } : {}), }); } message.success( `已选点 ${loc.latitude.toFixed(6)}, ${loc.longitude.toFixed(6)}${loc.name ? `(${loc.name})` : ''}`, ); }} /> ); }