import {useEffect, useState} from "react"; import Taro from '@tarojs/taro'; import {Button, Popup, Cell, CellGroup} from '@nutui/nutui-react-taro' // import {TriangleDown} from '@nutui/icons-react-taro' import { NavBar} from '@nutui/nutui-react-taro' import {getUserInfo, getWxOpenId} from "@/api/layout"; import {TenantId, TenantName} from "@/config/app"; import {getOrganization} from "@/api/system/organization"; import {myUserVerify} from "@/api/system/userVerify"; // import { useShopInfo } from '@/hooks/useShopInfo'; import {handleInviteRelation, getStoredInviteParams} from "@/utils/invite"; import {View,Text} from '@tarojs/components' import MySearch from "./MySearch"; import './Header.scss'; import {User} from "@/api/system/user/model"; import {getShopStore, listShopStore} from "@/api/shop/shopStore"; import type {ShopStore} from "@/api/shop/shopStore/model"; import {getSelectedStoreFromStorage, saveSelectedStoreToStorage} from "@/utils/storeSelection"; const Header = (_: any) => { // 使用新的useShopInfo Hook // const { // getWebsiteLogo // } = useShopInfo(); const [IsLogin, setIsLogin] = useState(true) const [statusBarHeight, setStatusBarHeight] = useState() const [userInfo] = useState() // 门店选择:用于首页展示“最近门店”,并在下单时写入订单 storeId const [storePopupVisible, setStorePopupVisible] = useState(false) const [stores, setStores] = useState([]) const [selectedStore, setSelectedStore] = useState(getSelectedStoreFromStorage()) const [userLocation, setUserLocation] = useState<{lng: number; lat: number} | null>(null) const getTenantName = () => { return userInfo?.tenantName || TenantName } const parseStoreCoords = (s: ShopStore): {lng: number; lat: number} | null => { const raw = (s.lngAndLat || s.location || '').trim() if (!raw) return null const parts = raw.split(/[,\s]+/).filter(Boolean) if (parts.length < 2) return null const a = parseFloat(parts[0]) const b = parseFloat(parts[1]) if (Number.isNaN(a) || Number.isNaN(b)) return null // 常见格式是 "lng,lat";这里做一个简单兜底(经度范围更宽) const looksLikeLngLat = Math.abs(a) <= 180 && Math.abs(b) <= 90 const looksLikeLatLng = Math.abs(a) <= 90 && Math.abs(b) <= 180 if (looksLikeLngLat) return {lng: a, lat: b} if (looksLikeLatLng) return {lng: b, lat: a} return null } const distanceMeters = (a: {lng: number; lat: number}, b: {lng: number; lat: number}) => { const toRad = (x: number) => (x * Math.PI) / 180 const R = 6371000 // meters const dLat = toRad(b.lat - a.lat) const dLng = toRad(b.lng - a.lng) const lat1 = toRad(a.lat) const lat2 = toRad(b.lat) const sin1 = Math.sin(dLat / 2) const sin2 = Math.sin(dLng / 2) const h = sin1 * sin1 + Math.cos(lat1) * Math.cos(lat2) * sin2 * sin2 return 2 * R * Math.asin(Math.min(1, Math.sqrt(h))) } const formatDistance = (meters?: number) => { if (meters === undefined || Number.isNaN(meters)) return '' if (meters < 1000) return `${Math.round(meters)}m` return `${(meters / 1000).toFixed(1)}km` } const getStoreDistance = (s: ShopStore) => { if (!userLocation) return undefined const coords = parseStoreCoords(s) if (!coords) return undefined return distanceMeters(userLocation, coords) } const initStoreSelection = async () => { // 先读取本地已选门店,避免页面首屏抖动 const stored = getSelectedStoreFromStorage() if (stored?.id) { setSelectedStore(stored) } // 拉取门店列表(失败时允许用户手动重试/继续使用本地门店) let list: ShopStore[] = [] try { list = await listShopStore() } catch (e) { console.error('获取门店列表失败:', e) list = [] } const usable = (list || []).filter(s => s?.isDelete !== 1) setStores(usable) // 尝试获取定位,用于计算最近门店 let loc: {lng: number; lat: number} | null = null try { const r = await Taro.getLocation({type: 'gcj02'}) loc = {lng: r.longitude, lat: r.latitude} } catch (e) { // 不强制定位授权;无定位时仍允许用户手动选择 console.warn('获取定位失败,将不显示最近门店距离:', e) } setUserLocation(loc) const ensureStoreDetail = async (s: ShopStore) => { if (!s?.id) return s // 如果后端已经返回默认仓库等字段,就不额外请求 if (s.warehouseId) return s try { const full = await getShopStore(s.id) return full || s } catch (_e) { return s } } // 若用户没有选过门店,则自动选择最近门店(或第一个) const alreadySelected = stored?.id if (alreadySelected || usable.length === 0) return let autoPick: ShopStore | undefined if (loc) { autoPick = [...usable] .map(s => { const coords = parseStoreCoords(s) const d = coords ? distanceMeters(loc, coords) : undefined return {s, d} }) .sort((x, y) => (x.d ?? Number.POSITIVE_INFINITY) - (y.d ?? Number.POSITIVE_INFINITY))[0]?.s } else { autoPick = usable[0] } if (autoPick?.id) { const full = await ensureStoreDetail(autoPick) setSelectedStore(full) saveSelectedStoreToStorage(full) } } const reload = async () => { Taro.getSystemInfo({ success: (res) => { setStatusBarHeight(res.statusBarHeight) }, }) // 注意:商店信息现在通过useShopInfo自动管理,不需要手动获取 // 获取用户信息 getUserInfo().then((data) => { if (data) { setIsLogin(true); console.log('用户信息>>>', data.phone) // 保存userId Taro.setStorageSync('UserId', data.userId) // 获取openId if (!data.openid) { Taro.login({ success: (res) => { getWxOpenId({code: res.code}).then(() => { }) } }) } // 是否已认证 if (data.certification) { Taro.setStorageSync('Certification', '1') } // 机构ID Taro.setStorageSync('OrganizationId', data.organizationId) // 父级机构ID if (Number(data.organizationId) > 0) { getOrganization(Number(data.organizationId)).then(res => { Taro.setStorageSync('OrganizationParentId', res.parentId) }) } // 管理员 const isKdy = data.roles?.findIndex(item => item.roleCode == 'admin') if (isKdy != -1) { Taro.setStorageSync('RoleName', '管理') Taro.setStorageSync('RoleCode', 'admin') return false; } // 注册用户 const isUser = data.roles?.findIndex(item => item.roleCode == 'user') if (isUser != -1) { Taro.setStorageSync('RoleName', '注册用户') Taro.setStorageSync('RoleCode', 'user') return false; } } }).catch(() => { setIsLogin(false); console.log('未登录') }); // 认证信息 myUserVerify({status: 1}).then(data => { if (data?.realName) { Taro.setStorageSync('RealName', data.realName) } }) } /* 获取用户手机号 */ const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => { const {code, encryptedData, iv} = detail // 防重复登录检查 const loginKey = 'login_in_progress' const loginInProgress = Taro.getStorageSync(loginKey) if (loginInProgress && Date.now() - loginInProgress < 5000) { // 5秒内防重 return } // 标记登录开始 Taro.setStorageSync(loginKey, Date.now()) // 获取存储的邀请参数 const inviteParams = getStoredInviteParams() const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0 Taro.login({ success: function () { if (code) { Taro.request({ url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone', method: 'POST', data: { code, encryptedData, iv, notVerifyPhone: true, refereeId: refereeId, // 使用解析出的推荐人ID sceneType: 'save_referee', tenantId: TenantId }, header: { 'content-type': 'application/json', TenantId }, success: async function (res) { // 清除登录防重标记 Taro.removeStorageSync('login_in_progress') if (res.data.code == 1) { Taro.showToast({ title: res.data.message, icon: 'error', duration: 2000 }) return false; } // 登录成功 Taro.setStorageSync('access_token', res.data.data.access_token) Taro.setStorageSync('UserId', res.data.data.user.userId) setIsLogin(true) // 处理邀请关系 if (res.data.data.user?.userId) { try { await handleInviteRelation(res.data.data.user.userId) } catch (error) { console.error('处理邀请关系失败:', error) } } // 重新加载小程序 Taro.reLaunch({ url: '/pages/index/index' }) }, fail: function() { // 清除登录防重标记 Taro.removeStorageSync('login_in_progress') } }) } else { console.log('登录失败!') } } }) } // 获取小程序系统信息 // const getSystemInfo = () => { // const systemInfo = Taro.getSystemInfoSync() // // 状态栏高度 + 导航栏高度 (一般为44px) // return (systemInfo.statusBarHeight || 0) + 44 // } useEffect(() => { reload().then() initStoreSelection().then() }, []) return ( <> { }} // left={ // setStorePopupVisible(true)} // > // // // {selectedStore?.name || '请选择门店'} // // // // } right={ !IsLogin ? ( ) : null } > {getTenantName()} setStorePopupVisible(false)} > 选择门店 setStorePopupVisible(false)} > 关闭 {userLocation ? '已获取定位,按距离排序' : '未获取定位,可手动选择门店'} {[...stores] .sort((a, b) => (getStoreDistance(a) ?? Number.POSITIVE_INFINITY) - (getStoreDistance(b) ?? Number.POSITIVE_INFINITY)) .map((s) => { const d = getStoreDistance(s) const isActive = !!selectedStore?.id && selectedStore.id === s.id return ( {s.name || `门店${s.id}`} {d !== undefined && {formatDistance(d)}} } description={s.address || ''} onClick={async () => { let storeToSave = s if (s?.id) { try { const full = await getShopStore(s.id) if (full) storeToSave = full } catch (_e) { // keep base item } } setSelectedStore(storeToSave) saveSelectedStoreToStorage(storeToSave) setStorePopupVisible(false) Taro.showToast({title: '门店已切换', icon: 'success'}) }} /> ) })} ) } export default Header