diff --git a/.workbuddy/memory/2026-07-17.md b/.workbuddy/memory/2026-07-17.md index b1f54c4..018ba66 100644 --- a/.workbuddy/memory/2026-07-17.md +++ b/.workbuddy/memory/2026-07-17.md @@ -29,3 +29,26 @@ ### 验证 - `npx taro build --type weapp` 构建成功 + +## 地址/购物车问题修复 + +### 5. 地图选择首次打开列表不显示(src/pages/user/address-edit.tsx) +- **问题**:鸿蒙手机首次打开 `Taro.chooseLocation` 时地图 POI 列表不显示,需拖动才出现 +- **根因**:未传入经纬度时地图默认定位到北京,鸿蒙系统不会自动获取用户位置 +- **修复**:调 `chooseLocation` 前先 `Taro.getLocation({ type: 'gcj02' })` 获取当前位置,将 latitude/longitude 传给地图,确保首次打开就在用户位置附近,POI 列表立即加载 + +### 6. 收货地址编辑不加载旧数据 +- **Bug 1(API 路径不匹配)**:前端 `updateShopUserAddress` 调 `PUT /shop/shop-user-address/{id}`,但后端 `@PutMapping()` 无 `/{id}` 路径 → 404 + - 修复:改为 `PUT /shop/shop-user-address`(去掉 `/{id}`,id 通过 body 传递) +- **Bug 2(加载防御)**:`setFormData(addr)` 直接替换 state,若后端返回 null 字段会覆盖初始 `''` + - 修复:改为 `setFormData(prev => ({...prev, ...addr, name: addr.name || '', ...}))` 保证字段非 null + +### 7. 新用户注册后首次加购购物车不刷新 +- **根因**:注册页 `register.tsx` 和登录页 `login.tsx` 只调 `saveStorageByLoginUser()` 存 storage,未调 `UserContext.loginUser()` 更新 React 状态 → `isLoggedIn` 仍为 `false` → 购物车页 `useDidShow` 中 `if (isLoggedIn) refresh()` 不执行 +- **修复**: + 1. `register.tsx` 和 `login.tsx`:`saveStorageByLoginUser` 后加 `loginUser(token, user)` 同步 UserContext + 2. `sms-login.tsx`:`loginBySms` 后加 `syncFromStorage()` 同步状态 + 3. 防御性措施:`cart.tsx`、`product-detail.tsx`、`index/index.tsx` 的 `useDidShow` 加 `syncFromStorage()` 确保页面显示时从 storage 同步用户状态 + +### 验证 +- `npx taro build --type weapp` 构建成功 diff --git a/src/api/shop/shopUserAddress/index.ts b/src/api/shop/shopUserAddress/index.ts index dde2623..46243f6 100644 --- a/src/api/shop/shopUserAddress/index.ts +++ b/src/api/shop/shopUserAddress/index.ts @@ -46,10 +46,11 @@ export async function addShopUserAddress(data: ShopUserAddress) { /** * 修改收货地址 + * 后端 @PutMapping() 无 /{id} 路径,id 通过 body 传递 */ export async function updateShopUserAddress(data: ShopUserAddress) { const res = await request.put>( - '/shop/shop-user-address/' + data.id, + '/shop/shop-user-address', data ); if (res.code === 0) { diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index 3fb6bf6..3f31881 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -25,7 +25,7 @@ definePageConfig({ }) const IndexPage: React.FC = () => { - const { user, isLoggedIn } = useUser() + const { user, isLoggedIn, syncFromStorage } = useUser() const [banners, setBanners] = useState([]) const [bannerWrapWidth, setBannerWrapWidth] = useState(0) const [announcements, setAnnouncements] = useState([]) @@ -68,6 +68,8 @@ const IndexPage: React.FC = () => { // 页面重新显示时也刷新店员身份(可能从其他页面回来时状态变了) useDidShow(() => { + // 先从 storage 同步用户状态(解决注册/登录后 UserContext 状态未更新的问题) + syncFromStorage() if (isLoggedIn) { getMyClerk() .then(data => { diff --git a/src/pages/shop/cart.tsx b/src/pages/shop/cart.tsx index bcd01dc..d2180d9 100644 --- a/src/pages/shop/cart.tsx +++ b/src/pages/shop/cart.tsx @@ -18,7 +18,7 @@ definePageConfig({ const CartPage: React.FC = () => { const { items, selectedCount, selectedPrice, updateQuantity, toggleSelect, selectAll, removeItem, refresh, loading, removeSelected, addItem } = useCartContext() - const { isLoggedIn } = useUserContext() + const { isLoggedIn, syncFromStorage } = useUserContext() // VIP 状态:异步校验并更新缓存,isVip 变化时触发重渲染 const { isVip } = useVipStatus() const [recommendGoods, setRecommendGoods] = useState([]) @@ -34,7 +34,9 @@ const CartPage: React.FC = () => { // 页面再次显示时刷新购物车数据(从其他页面加购后回到购物车) useDidShow(() => { - if (isLoggedIn) { + // 先从 storage 同步用户状态(解决注册/登录后 UserContext 状态未更新的问题) + const loggedIn = syncFromStorage() + if (loggedIn) { refresh() } }) diff --git a/src/pages/shop/product-detail.tsx b/src/pages/shop/product-detail.tsx index f59b9e5..f1f419e 100644 --- a/src/pages/shop/product-detail.tsx +++ b/src/pages/shop/product-detail.tsx @@ -37,7 +37,7 @@ const ProductDetailPage: React.FC = () => { const [skuMode, setSkuMode] = useState<'cart' | 'buy'>('cart') const [isFavorite, setIsFavorite] = useState(false) const { addItem, totalCount, refresh } = useCartContext() - const { isLoggedIn } = useUserContext() + const { isLoggedIn, syncFromStorage } = useUserContext() // VIP 状态:异步校验并更新缓存,isVip 变化时触发重渲染 const { isVip } = useVipStatus() // 底部操作栏高度约 90px(含按钮 + 安全区),动态计算 ScrollView 可用高度 @@ -64,6 +64,8 @@ const ProductDetailPage: React.FC = () => { // 页面显示时刷新购物车数量,确保底部购物车角标准确 useDidShow(() => { + // 先从 storage 同步用户状态(解决注册/登录后 UserContext 状态未更新的问题) + syncFromStorage() if (isLoggedIn) refresh() }) diff --git a/src/pages/user/address-edit.tsx b/src/pages/user/address-edit.tsx index 496c272..bdc5dc2 100644 --- a/src/pages/user/address-edit.tsx +++ b/src/pages/user/address-edit.tsx @@ -313,11 +313,35 @@ const AddressEditPage: React.FC = () => { return } - // 3. 仅在有有效经纬度时传入初始化参数,避免 undefined 触发部分机型失败 - const initLat = selectedLocation?.lat ? Number(selectedLocation.lat) : undefined - const initLng = selectedLocation?.lng ? Number(selectedLocation.lng) : undefined - const latitude = typeof initLat === 'number' && Number.isFinite(initLat) ? initLat : undefined - const longitude = typeof initLng === 'number' && Number.isFinite(initLng) ? initLng : undefined + // 3. 优先使用已有经纬度;没有则先调 getLocation 获取当前位置 + // 解决鸿蒙等机型首次打开地图时 POI 列表不显示的问题 + let latitude: number | undefined + let longitude: number | undefined + + if (selectedLocation?.lat && selectedLocation?.lng) { + const initLat = Number(selectedLocation.lat) + const initLng = Number(selectedLocation.lng) + if (Number.isFinite(initLat) && Number.isFinite(initLng)) { + latitude = initLat + longitude = initLng + } + } + + // 没有已有定位时,主动获取当前位置传给地图,确保地图打开就在用户位置附近 + // 这样地图的 POI 列表能立即加载,不需要用户手动拖动 + if (latitude === undefined || longitude === undefined) { + try { + const loc: any = await Taro.getLocation({ type: 'gcj02' }) + if (loc && Number.isFinite(loc.latitude) && Number.isFinite(loc.longitude)) { + latitude = loc.latitude + longitude = loc.longitude + } + } catch (locErr) { + // getLocation 失败不阻断流程,chooseLocation 仍可打开(只是默认定位到北京) + console.warn('getLocation 失败,地图将以默认位置打开:', locErr) + } + } + const params: any = {} if (typeof latitude === 'number') params.latitude = latitude if (typeof longitude === 'number') params.longitude = longitude @@ -470,14 +494,28 @@ const AddressEditPage: React.FC = () => { if (isEditMode && addressId) { try { const addr = (await getShopUserAddress(addressId)) as ShopUserAddress - setFormData(addr) - const p = String((addr as any)?.province || '').trim() - const c = String((addr as any)?.city || '').trim() - const r = String((addr as any)?.region || '').trim() - setRegionText([p, c, r].filter(Boolean).join(' ')) - if (hasValidLngLat(addr)) { - setSelectedLocation({lng: String((addr as any).lng), lat: String((addr as any).lat)}) - setRegionLocked(true) + if (addr) { + // 合并到 formData,保证初始字段不被 null 覆盖为 undefined + setFormData(prev => ({ + ...prev, + ...addr, + name: addr.name || prev.name || '', + phone: addr.phone || prev.phone || '', + address: addr.address || prev.address || '', + province: addr.province || prev.province || '', + city: addr.city || prev.city || '', + region: addr.region || prev.region || '', + country: addr.country || prev.country || '中国', + isDefault: addr.isDefault ?? false, + })) + const p = String(addr?.province || '').trim() + const c = String(addr?.city || '').trim() + const r = String(addr?.region || '').trim() + setRegionText([p, c, r].filter(Boolean).join(' ')) + if (hasValidLngLat(addr)) { + setSelectedLocation({lng: String((addr as any).lng), lat: String((addr as any).lat)}) + setRegionLocked(true) + } } } catch (error) { console.error('加载地址失败:', error) diff --git a/src/passport/login.tsx b/src/passport/login.tsx index 179b470..99ce862 100644 --- a/src/passport/login.tsx +++ b/src/passport/login.tsx @@ -6,6 +6,7 @@ import { getWxOpenId } from '@/api/layout' import { getUserInfo } from '@/api/layout' import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server' import { isUserDisabled } from '@/utils/auth' +import { useUserContext } from '@/contexts/UserContext' import request from '@/utils/request' import { getPhoneAuthCooldownRemaining, @@ -108,6 +109,7 @@ const Login = () => { const [phoneAuthPending, setPhoneAuthPending] = useState(false) const [phoneAuthCooling, setPhoneAuthCooling] = useState(isPhoneAuthCoolingDown()) const [showContent, setShowContent] = useState(false) + const { loginUser } = useUserContext() const phoneAuthInFlightRef = useRef(false) const phoneAuthPendingRef = useRef(false) const phoneAuthTimerRef = useRef | null>(null) @@ -346,6 +348,8 @@ const Login = () => { } saveStorageByLoginUser(token, user) + // 同步更新 UserContext 状态,确保后续页面 isLoggedIn 为 true + loginUser(token, user) // 绑定 openid + 处理邀请关系 await ensureWxOpenIdSaved() diff --git a/src/passport/register.tsx b/src/passport/register.tsx index 16bc224..d7a1e4d 100644 --- a/src/passport/register.tsx +++ b/src/passport/register.tsx @@ -6,6 +6,7 @@ import { TenantId } from '@/config/app' import { getUserInfo, getWxOpenId } from '@/api/layout' import { saveStorageByLoginUser } from '@/utils/server' import { isUserDisabled } from '@/utils/auth' +import { useUserContext } from '@/contexts/UserContext' import { getStoredInviteParams, parseInviteParams, @@ -98,6 +99,7 @@ function isTabBarUrl(url: string) { const Register = () => { const [isAgree, setIsAgree] = useState(false) const [loading, setLoading] = useState(false) + const { loginUser } = useUserContext() // 短信验证码登录仅在非微信小程序端展示 const isWeapp = useMemo(() => { @@ -234,6 +236,8 @@ const Register = () => { } saveStorageByLoginUser(token, user) + // 同步更新 UserContext 状态,确保后续页面 isLoggedIn 为 true + loginUser(token, user) // 注册/登录成功后,立即补齐 openid(JSAPI 支付必需) try { diff --git a/src/passport/sms-login.tsx b/src/passport/sms-login.tsx index 461af3c..2217458 100644 --- a/src/passport/sms-login.tsx +++ b/src/passport/sms-login.tsx @@ -4,12 +4,14 @@ import { View, Text, Input } from '@tarojs/components' import {loginBySms, sendSmsCaptcha} from "@/api/passport/login"; import {LoginParam} from "@/api/passport/login/model"; import {checkAndHandleInviteRelation, hasPendingInvite, parseInviteParams, saveInviteParams, trackInviteSource} from "@/utils/invite"; +import { useUserContext } from '@/contexts/UserContext' import './sms-login.scss' const SmsLogin = () => { const [loading, setLoading] = useState(false) const [sendingCode, setSendingCode] = useState(false) const [countdown, setCountdown] = useState(0) + const { syncFromStorage } = useUserContext() const [formData, setFormData] = useState({ phone: '', code: '' @@ -182,6 +184,9 @@ const SmsLogin = () => { code: formData.code }) + // loginBySms 内部已调 saveStorageByLoginUser,这里同步 UserContext 状态 + syncFromStorage() + // 登录成功后(可能是新注册用户),检查是否存在待处理的邀请关系并尝试绑定 if (hasPendingInvite()) { try {