Files
xinlong-shop-taro/src/hooks/useAddress.ts
赵忠林 3f1931e420 feat(address): 修复默认地址唯一性并新增用户浏览记录功能
- 后端新增 clearDefault 方法,保存默认地址前清除其他默认状态,避免多默认地址
- 前端地址编辑页保存成功后若为默认,调用 setDefaultAddress 触发后端清理逻辑
- useAddress hook 加入 ensureSingleDefault,前端保证地址数据中只有一个默认地址
- 新增 shop_goods_browse 表及相关实体、Mapper、服务和控制器,实现用户商品浏览记录功能
- 商品详情页接入浏览记录上报,支持登录用户浏览记录服务端同步
- 浏览历史页支持登录用户服务端分页查询,未登录用户使用本地存储兜底
- 浏览历史页支持浏览记录单条删除、清空、上拉加载和下拉刷新功能
- 调整用户中心入口,恢复浏览历史菜单项显示
- 文档和内存文件更新,包括数据库建表 SQL、接口定义和项目编译说明
2026-07-15 12:52:25 +08:00

121 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback } from 'react'
import {
listShopUserAddress,
getShopUserAddress,
addShopUserAddress,
updateShopUserAddress,
removeShopUserAddress,
setDefaultAddress,
} from '@/api/shop/shopUserAddress'
import type { ShopUserAddress } from '@/api/shop/shopUserAddress/model'
interface UseAddressReturn {
addresses: ShopUserAddress[]
loading: boolean
defaultAddress: ShopUserAddress | null
loadAddresses: () => Promise<void>
getAddress: (id: number) => Promise<ShopUserAddress | null>
addAddress: (data: Partial<ShopUserAddress>) => Promise<boolean>
updateAddress: (data: ShopUserAddress) => Promise<boolean>
deleteAddress: (id: number) => Promise<boolean>
setDefault: (id: number) => Promise<boolean>
}
/**
* 兜底:保证列表中最多只有一个地址是 isDefault=true。
* 兼容历史脏数据(之前没有"取消其他默认"逻辑时遗留下来的多个默认)。
* 保留最早创建id 最小)的为默认;其余的在前端展示时按非默认处理。
*/
function ensureSingleDefault(list: ShopUserAddress[]): ShopUserAddress[] {
const defaults = list.filter(a => a.isDefault)
if (defaults.length <= 1) return list
// 多个默认:保留 createTime 最早的;如果都缺 createTime则保留 id 最小的
const sorted = [...defaults].sort((a, b) => {
const at = a.createTime || ''
const bt = b.createTime || ''
if (at && bt) return at < bt ? -1 : at > bt ? 1 : 0
if (at) return -1
if (bt) return 1
return (a.id || 0) - (b.id || 0)
})
const keepId = sorted[0].id
return list.map(a => {
if (a.isDefault && a.id !== keepId) {
return {...a, isDefault: false}
}
return a
})
}
export function useAddress(): UseAddressReturn {
const [addresses, setAddresses] = useState<ShopUserAddress[]>([])
const [loading, setLoading] = useState(false)
const loadAddresses = useCallback(async () => {
setLoading(true)
try {
const list = await listShopUserAddress()
const raw: ShopUserAddress[] = Array.isArray(list) ? list : (list as any)?.data || []
setAddresses(ensureSingleDefault(raw))
} catch (error) {
console.error('Load addresses error:', error)
setAddresses([])
} finally {
setLoading(false)
}
}, [])
/** 根据 ID 获取单个地址 */
const getAddress = useCallback(async (id: number): Promise<ShopUserAddress | null> => {
try {
return await getShopUserAddress(id)
} catch {
return null
}
}, [])
const addAddress = useCallback(async (data: Partial<ShopUserAddress>): Promise<boolean> => {
try {
await addShopUserAddress(data as any)
await loadAddresses()
return true
} catch {
return false
}
}, [loadAddresses])
const updateAddress = useCallback(async (data: ShopUserAddress): Promise<boolean> => {
try {
await updateShopUserAddress(data)
await loadAddresses()
return true
} catch {
return false
}
}, [loadAddresses])
const deleteAddress = useCallback(async (id: number): Promise<boolean> => {
try {
await removeShopUserAddress(id)
setAddresses(prev => prev.filter(a => a.id !== id))
return true
} catch {
return false
}
}, [])
const setDefault = useCallback(async (id: number): Promise<boolean> => {
try {
await setDefaultAddress(id)
await loadAddresses()
return true
} catch {
return false
}
}, [loadAddresses])
const defaultAddress = addresses.find(a => a.isDefault) || addresses[0] || null
return { addresses, loading, defaultAddress, loadAddresses, getAddress, addAddress, updateAddress, deleteAddress, setDefault }
}