fix(shop): 修复首页VIP价格显示及后端价格字段问题
- 首页index.tsx中接入useVipStatus,修复热销推荐价格未显示dealerPrice的问题 - 新增getHotDisplayPrice函数,VIP时显示dealerPrice及划线原价,并添加VIP金色角标 - 后端OrderCreateRequest.OrderGoodsItem增加price字段及校验,解决后端DTO缺失price字段问题 - 重构OrderBusinessService中价格校验逻辑,确保正确优先级及SKU库存校验 - 确认价格字段约定,前端传dealerPrice给后端下单 - Maven编译通过,后端需重启服务生效
This commit is contained in:
@@ -137,3 +137,23 @@
|
||||
- Button 样式重置:height:auto / lineHeight:normal / padding:16px / border:none / borderRadius:8px,保持原视觉(白底圆角卡片)
|
||||
- 引入:从 `@tarojs/components` 新增导入 `Button`
|
||||
- 注意:微信客服 Button 在开发者工具中无效,需真机预览测试
|
||||
|
||||
## 修复 VIP 价格首页不显示 + 后端不认 price 字段
|
||||
- **问题 1(首页)**:`src/pages/index/index.tsx`(tabBar 首页)没接入 `useVipStatus`,热销推荐价格一直显示 `product.price` 而非 `dealerPrice`。商品详情/商城列表/购物车/分类都修了,唯独漏了 tabBar 首页
|
||||
- **问题 2(后端 DTO 缺字段)**:前端 `OrderGoodsItem` 已传 `price` 字段,但后端 `OrderCreateRequest.OrderGoodsItem`(嵌套静态类)**根本没有 `price` 字段**,Jackson 反序列化直接丢弃。后端 `OrderBusinessService.validateAndCalculateTotal()` 和 `saveOrderGoods()` 都用 `goods.getPrice()` 兜底 —— 这就是「前端传的对、后端处理就错」的根本原因
|
||||
- **修复(3 个文件)**:
|
||||
1. `src/pages/index/index.tsx`
|
||||
- 引入 `useVipStatus`,加 `const { isVip } = useVipStatus()`
|
||||
- 加 `getHotDisplayPrice()`:VIP + 有 dealerPrice → `{price: dealerPrice, original: price}`,否则原逻辑
|
||||
- Price 组件传 `price + original` + 旁边加金色「VIP」角标
|
||||
2. `/Users/gxwebsoft/JAVA/guilixu-java/src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java`
|
||||
- `OrderGoodsItem` 内部类加 `private BigDecimal price;` 字段 + `@DecimalMin("0")` 校验
|
||||
3. `/Users/gxwebsoft/JAVA/guilixu-java/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java`
|
||||
- `validateAndCalculateTotal()` 和 `saveOrderGoods()` 两处重构:先取 sku(无论价格来源都要校验 sku 合法性+取库存),再用「前端 price > sku.price > goods.price」优先级决定 actualPrice
|
||||
- 注意不要把 sku 查询和 price 选择耦合在同一个 if-else 链里,否则 VIP+多规格商品会跳过 sku 库存校验
|
||||
- **编译验证**:Maven compile 成功无报错;后端需重启服务才能生效
|
||||
- **价格字段约定(再确认)**:
|
||||
- `goods.price` - 到手价(主价格)
|
||||
- `goods.salePrice` - 市场价(划掉的)
|
||||
- `goods.dealerPrice` - VIP 专享价
|
||||
- 前端传 `dealerPrice` 到 `OrderGoodsItem.price`,后端通过该字段下单
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useUser } from '@/hooks/useUser'
|
||||
import { useShare } from '@/hooks/useShare'
|
||||
import { useScrollHeight } from '@/hooks/useScrollHeight'
|
||||
import { useNewOrderDetector } from '@/hooks/useNewOrderDetector'
|
||||
import { useVipStatus } from '@/hooks/useVipStatus'
|
||||
import { getMyClerk } from '@/api/shop/shopStoreUser'
|
||||
import { pageShopOrder } from '@/api/shop/shopOrder'
|
||||
import { listCmsAd } from '@/api/cms/cmsAd'
|
||||
@@ -29,6 +30,8 @@ const IndexPage: React.FC = () => {
|
||||
const [hotProducts, setHotProducts] = useState<ShopGoods[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const scrollHeight = useScrollHeight(44)
|
||||
// VIP 状态:异步校验并更新缓存,isVip 变化时触发重渲染
|
||||
const { isVip } = useVipStatus()
|
||||
|
||||
// 首页分享:好友 + 朋友圈 + 复制链接(右上角三个按钮全亮)
|
||||
useShare({
|
||||
@@ -165,6 +168,17 @@ const IndexPage: React.FC = () => {
|
||||
Taro.navigateTo({ url: '/pages/index/notification' })
|
||||
}
|
||||
|
||||
/**
|
||||
* VIP 会员显示 dealerPrice(同时把 price 作为划线原价),非 VIP 维持原行为
|
||||
*/
|
||||
const getHotDisplayPrice = (item: ShopGoods): { price: string; original?: string } => {
|
||||
if (isVip && item.dealerPrice) {
|
||||
return { price: item.dealerPrice, original: item.price }
|
||||
}
|
||||
const original = item.salePrice && item.salePrice !== item.price ? item.salePrice : undefined
|
||||
return { price: item.price || '0', original }
|
||||
}
|
||||
|
||||
// tabBar 页面列表
|
||||
const tabBarPages = ['/pages/index/index', '/pages/shop/index', '/pages/order/list', '/pages/user/user']
|
||||
|
||||
@@ -330,11 +344,14 @@ const IndexPage: React.FC = () => {
|
||||
<Text className='text-sm text-gray-700 truncate block'>{item.goodsName || item.name}</Text>
|
||||
<View className='flex items-center mt-1'>
|
||||
<Price
|
||||
price={item.price || '0'}
|
||||
original={item.salePrice && item.salePrice !== item.price ? item.salePrice : undefined}
|
||||
price={getHotDisplayPrice(item).price}
|
||||
original={getHotDisplayPrice(item).original}
|
||||
size='small'
|
||||
loginMask
|
||||
/>
|
||||
{isVip && item.dealerPrice ? (
|
||||
<Text className='text-xs text-amber-600 ml-1'>VIP</Text>
|
||||
) : null}
|
||||
</View>
|
||||
{item.sales !== undefined && item.sales > 0 && (
|
||||
<Text className='text-xs text-gray-400 mt-1'>已售 {item.sales}</Text>
|
||||
|
||||
Reference in New Issue
Block a user