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:
2026-07-15 14:50:06 +08:00
parent ca6ba52993
commit af645c058e
2 changed files with 39 additions and 2 deletions

View File

@@ -137,3 +137,23 @@
- Button 样式重置height:auto / lineHeight:normal / padding:16px / border:none / borderRadius:8px保持原视觉白底圆角卡片 - Button 样式重置height:auto / lineHeight:normal / padding:16px / border:none / borderRadius:8px保持原视觉白底圆角卡片
- 引入:从 `@tarojs/components` 新增导入 `Button` - 引入:从 `@tarojs/components` 新增导入 `Button`
- 注意:微信客服 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`,后端通过该字段下单

View File

@@ -5,6 +5,7 @@ import { useUser } from '@/hooks/useUser'
import { useShare } from '@/hooks/useShare' import { useShare } from '@/hooks/useShare'
import { useScrollHeight } from '@/hooks/useScrollHeight' import { useScrollHeight } from '@/hooks/useScrollHeight'
import { useNewOrderDetector } from '@/hooks/useNewOrderDetector' import { useNewOrderDetector } from '@/hooks/useNewOrderDetector'
import { useVipStatus } from '@/hooks/useVipStatus'
import { getMyClerk } from '@/api/shop/shopStoreUser' import { getMyClerk } from '@/api/shop/shopStoreUser'
import { pageShopOrder } from '@/api/shop/shopOrder' import { pageShopOrder } from '@/api/shop/shopOrder'
import { listCmsAd } from '@/api/cms/cmsAd' import { listCmsAd } from '@/api/cms/cmsAd'
@@ -29,6 +30,8 @@ const IndexPage: React.FC = () => {
const [hotProducts, setHotProducts] = useState<ShopGoods[]>([]) const [hotProducts, setHotProducts] = useState<ShopGoods[]>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const scrollHeight = useScrollHeight(44) const scrollHeight = useScrollHeight(44)
// VIP 状态异步校验并更新缓存isVip 变化时触发重渲染
const { isVip } = useVipStatus()
// 首页分享:好友 + 朋友圈 + 复制链接(右上角三个按钮全亮) // 首页分享:好友 + 朋友圈 + 复制链接(右上角三个按钮全亮)
useShare({ useShare({
@@ -165,6 +168,17 @@ const IndexPage: React.FC = () => {
Taro.navigateTo({ url: '/pages/index/notification' }) 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 页面列表 // tabBar 页面列表
const tabBarPages = ['/pages/index/index', '/pages/shop/index', '/pages/order/list', '/pages/user/user'] 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> <Text className='text-sm text-gray-700 truncate block'>{item.goodsName || item.name}</Text>
<View className='flex items-center mt-1'> <View className='flex items-center mt-1'>
<Price <Price
price={item.price || '0'} price={getHotDisplayPrice(item).price}
original={item.salePrice && item.salePrice !== item.price ? item.salePrice : undefined} original={getHotDisplayPrice(item).original}
size='small' size='small'
loginMask loginMask
/> />
{isVip && item.dealerPrice ? (
<Text className='text-xs text-amber-600 ml-1'>VIP</Text>
) : null}
</View> </View>
{item.sales !== undefined && item.sales > 0 && ( {item.sales !== undefined && item.sales > 0 && (
<Text className='text-xs text-gray-400 mt-1'> {item.sales}</Text> <Text className='text-xs text-gray-400 mt-1'> {item.sales}</Text>