diff --git a/apps/admin-web/src/lib/storeCreate.ts b/apps/admin-web/src/lib/storeCreate.ts index 0e62857..cc68ce3 100644 --- a/apps/admin-web/src/lib/storeCreate.ts +++ b/apps/admin-web/src/lib/storeCreate.ts @@ -11,6 +11,8 @@ export type StoreCreateForm = { name: string; phone: string; address: string; + latitude?: number | null; + longitude?: number | null; intro?: string; openTime?: string; closeTime?: string; diff --git a/apps/admin-web/src/pages/StoresPage.tsx b/apps/admin-web/src/pages/StoresPage.tsx index 5c0b9d5..38182a4 100644 --- a/apps/admin-web/src/pages/StoresPage.tsx +++ b/apps/admin-web/src/pages/StoresPage.tsx @@ -319,6 +319,7 @@ export default function StoresPage() { const [createOpen, setCreateOpen] = useState(false); const [createStep, setCreateStep] = useState(0); const [createError, setCreateError] = useState(''); + const [locating, setLocating] = useState(false); const [partners, setPartners] = useState([]); const [cities, setCities] = useState([]); const [categoryTree, setCategoryTree] = useState([]); @@ -328,6 +329,8 @@ export default function StoresPage() { const selectedRegionCodes = Form.useWatch('regionCodes', createForm); const selectedCityId = Form.useWatch('cityId', createForm); const selectedCategoryParentId = Form.useWatch('categoryParentId', createForm); + const watchedLat = Form.useWatch('latitude', createForm); + const watchedLng = Form.useWatch('longitude', createForm); const categoryParentOptions = useMemo( () => @@ -465,6 +468,15 @@ export default function StoresPage() { phone: 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, openTime: values.openTime?.trim() || '10:00', closeTime: values.closeTime?.trim() || '22:00', @@ -854,6 +866,51 @@ export default function StoresPage() { + + + + + + {watchedLat != null && + watchedLng != null && + Number.isFinite(Number(watchedLat)) && + Number.isFinite(Number(watchedLng)) + ? `坐标:${Number(watchedLat).toFixed(6)}, ${Number(watchedLng).toFixed(6)}` + : '未定位(可选,便于用户端导航与距离)'} + + diff --git a/apps/h5-partner/src/lib/storeDraft.ts b/apps/h5-partner/src/lib/storeDraft.ts index 71cf74b..899f880 100644 --- a/apps/h5-partner/src/lib/storeDraft.ts +++ b/apps/h5-partner/src/lib/storeDraft.ts @@ -8,6 +8,9 @@ export type StoreDraftForm = { phone: string; storeSmsCode: string; address: string; + /** 门店坐标(定位或地理编码) */ + latitude: string; + longitude: string; openTime: string; closeTime: string; /** 是否启用第二段营业时间 */ @@ -47,6 +50,8 @@ export const defaultStoreForm = (): StoreDraftForm => ({ phone: '', storeSmsCode: '', address: '', + latitude: '', + longitude: '', openTime: '10:00', closeTime: '22:00', dualHours: false, @@ -87,6 +92,8 @@ export function normalizeStoreDraftForm(raw: Partial | null | un ...raw, regionCodes: Array.isArray(raw.regionCodes) ? raw.regionCodes.map(String) : base.regionCodes, cityId: String(raw.cityId ?? base.cityId), + latitude: raw.latitude != null && raw.latitude !== '' ? String(raw.latitude) : base.latitude, + longitude: raw.longitude != null && raw.longitude !== '' ? String(raw.longitude) : base.longitude, openTime: String(raw.openTime ?? base.openTime), closeTime: String(raw.closeTime ?? base.closeTime), dualHours: Boolean(raw.dualHours), diff --git a/apps/h5-partner/src/lib/storeLocate.ts b/apps/h5-partner/src/lib/storeLocate.ts new file mode 100644 index 0000000..e40a641 --- /dev/null +++ b/apps/h5-partner/src/lib/storeLocate.ts @@ -0,0 +1,26 @@ +import { weixinSdk } from './weixin'; + +export type StorePosition = { + latitude: number; + longitude: number; +}; + +/** 获取当前 GPS 坐标(微信 JSSDK / 浏览器 Geolocation) */ +export async function locateStorePosition(): Promise { + const outcome = await weixinSdk.getLocationDetailed(); + if (!outcome.location) { + throw new Error(outcome.errMsg || '定位失败,请允许位置权限后重试'); + } + const { latitude, longitude } = outcome.location; + if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) { + throw new Error('定位结果无效'); + } + return { latitude, longitude }; +} + +export function formatStoreCoords(lat?: string | number | null, lng?: string | number | null): string { + const a = lat != null && lat !== '' ? Number(lat) : NaN; + const b = lng != null && lng !== '' ? Number(lng) : NaN; + if (!Number.isFinite(a) || !Number.isFinite(b)) return ''; + return `${a.toFixed(6)}, ${b.toFixed(6)}`; +} diff --git a/apps/h5-partner/src/pages/StoreCreatePage.tsx b/apps/h5-partner/src/pages/StoreCreatePage.tsx index 7f2474c..6e626c7 100644 --- a/apps/h5-partner/src/pages/StoreCreatePage.tsx +++ b/apps/h5-partner/src/pages/StoreCreatePage.tsx @@ -13,6 +13,8 @@ import { resolveRegionBinding } from '../lib/china-region'; import { checkStorePhoneAvailable, sendStorePhoneSms } from '../lib/storePhone'; +import { formatStoreCoords, locateStorePosition } from '../lib/storeLocate'; + import { fetchPartnerCities, type OpenCityOption } from '../lib/upload'; import { usePartnerSession } from '../contexts/PartnerSessionContext'; @@ -102,6 +104,8 @@ export default function StoreCreatePage() { const [categoryTree, setCategoryTree] = useState([]); + const [locating, setLocating] = useState(false); + const draftSaveDisabledRef = useRef(false); function reportFormError(message: string) { @@ -508,6 +512,13 @@ export default function StoreCreatePage() { address: form.address.trim(), + ...(form.latitude.trim() && form.longitude.trim() + ? { + latitude: Number(form.latitude), + longitude: Number(form.longitude), + } + : {}), + openTime: form.openTime.trim(), closeTime: form.closeTime.trim(), @@ -740,6 +751,39 @@ export default function StoreCreatePage() {