显示格式优化

This commit is contained in:
liu
2026-08-21 11:35:05 +08:00
parent f0d8f6bac4
commit ffe983b9da
10 changed files with 122 additions and 26 deletions
+21
View File
@@ -0,0 +1,21 @@
// ===== 零售价标注:小字置灰 =====
.price-text__retail {
font-size: 20rpx;
color: #969799;
font-weight: 400;
line-height: 1.4;
white-space: nowrap;
}
// inline 模式:与大价格同行,左间距分隔
.price-text__retail--inline {
margin-left: 8rpx;
}
// block 模式:大价格 / 零售价上下两行(窄卡片布局)
.price-text {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: 1.3;
}
+47
View File
@@ -0,0 +1,47 @@
import { Text, View } from '@tarojs/components'
import { formatRetailPrice } from '@/utils/format'
import './index.less'
interface PriceTextProps {
/** 售价(展示为 ¥price */
price: string | number
/** 包规(用于计算零售价;无法计算时不展示零售价) */
spec?: string | number | null
/** 大价格样式类(字号 / 颜色由调用方控制) */
className?: string
/**
* 零售价布局:
* - inline 跟随大价格同行(宽裕区域:商品详情、各类弹层行)
* - block 独占一行(窄卡片:首页推荐、商品列表、购物车)
*/
mode?: 'inline' | 'block'
}
/**
* 商品价格:售价 + 零售价标注(小字)
* price=30、spec=15 → ¥30 零售价:¥2
*/
export default function PriceText({ price, spec, className, mode = 'inline' }: PriceTextProps) {
const retail = formatRetailPrice(price, spec)
// 窄卡片:大价格 / 零售价上下两行,避免与右侧按钮(+/步进器)挤压换行
if (mode === 'block') {
return (
<View className={`price-text ${className ?? ''}`}>
<Text className='price-text__main'>{price}</Text>
{retail !== null && (
<Text className='price-text__retail'>{retail}</Text>
)}
</View>
)
}
return (
<Text className={className}>
{price}
{retail !== null && (
<Text className='price-text__retail price-text__retail--inline'>{retail}</Text>
)}
</Text>
)
}
+6 -3
View File
@@ -5,7 +5,8 @@ import { Empty, Popup } from '@antmjs/vantui'
import { getBillDetailApi } from '@/services/bill' import { getBillDetailApi } from '@/services/bill'
import { getOrderDetailApi } from '@/services/order' import { getOrderDetailApi } from '@/services/order'
import { ORDER_STATUS_TEXT } from '@/types/order' import { ORDER_STATUS_TEXT } from '@/types/order'
import { resolveFileUrl } from '@/utils/format' import { formatSpec, resolveFileUrl } from '@/utils/format'
import PriceText from '@/components/PriceText'
import type { OrderDetail } from '@/types/order' import type { OrderDetail } from '@/types/order'
import type { BillDetail } from '@/services/bill' import type { BillDetail } from '@/services/bill'
import './index.less' import './index.less'
@@ -161,7 +162,8 @@ export default function BillDetailPage() {
<View className='bill-goods__main'> <View className='bill-goods__main'>
<Text className='bill-goods__name'>{item.product_name}</Text> <Text className='bill-goods__name'>{item.product_name}</Text>
<Text className='bill-goods__spec'> <Text className='bill-goods__spec'>
{item.product_spec ? `${item.product_spec} ` : ''}{item.price}/{item.unit} {formatSpec(item.product_spec, item.unit)}{' '}
<PriceText price={item.price} spec={item.product_spec} />
</Text> </Text>
</View> </View>
<View className='bill-goods__side'> <View className='bill-goods__side'>
@@ -214,7 +216,8 @@ export default function BillDetailPage() {
<View className='order-popup__item-info'> <View className='order-popup__item-info'>
<Text className='order-popup__item-name'>{item.product_name}</Text> <Text className='order-popup__item-name'>{item.product_name}</Text>
<Text className='order-popup__item-spec'> <Text className='order-popup__item-spec'>
{item.product_spec ? `${item.product_spec} ` : ''}{item.price}/{item.unit} × {item.quantity} {formatSpec(item.product_spec, item.unit)}{' '}
<PriceText price={item.price} spec={item.product_spec} /> × {item.quantity}
</Text> </Text>
</View> </View>
<Text className='order-popup__item-amount'>{item.amount}</Text> <Text className='order-popup__item-amount'>{item.amount}</Text>
+5 -3
View File
@@ -5,6 +5,8 @@ import { Button, Empty, Icon, Popup, Stepper } from '@antmjs/vantui'
import useCartStore from '@/stores/cart/useCartStore' import useCartStore from '@/stores/cart/useCartStore'
import { createOrderApi } from '@/services/order' import { createOrderApi } from '@/services/order'
import { getStoreInfoApi } from '@/services/store' import { getStoreInfoApi } from '@/services/store'
import PriceText from '@/components/PriceText'
import { formatSpec } from '@/utils/format'
import type { CartItem } from '@/types/cart' import type { CartItem } from '@/types/cart'
import type { StoreDetail } from '@/types/store' import type { StoreDetail } from '@/types/store'
import './index.less' import './index.less'
@@ -205,10 +207,10 @@ export default function CartPage() {
<Text className='cart-item__name'>{item.name}</Text> <Text className='cart-item__name'>{item.name}</Text>
{item.status === 0 && <Text className='cart-item__invalid-tag'></Text>} {item.status === 0 && <Text className='cart-item__invalid-tag'></Text>}
</View> </View>
<Text className='cart-item__spec'>{item.spec} / {item.unit}</Text> <Text className='cart-item__spec'>{formatSpec(item.spec, item.unit)}</Text>
<View className='cart-item__bottom'> <View className='cart-item__bottom'>
{item.price !== null ? ( {item.price !== null ? (
<Text className='cart-item__price'>{item.price}</Text> <PriceText className='cart-item__price' price={item.price} spec={item.spec} mode='block' />
) : ( ) : (
<Text className='cart-item__price cart-item__price--none'></Text> <Text className='cart-item__price cart-item__price--none'></Text>
)} )}
@@ -302,7 +304,7 @@ export default function CartPage() {
<Image className='order-popup__item-image' src={item.image} mode='aspectFill' lazyLoad /> <Image className='order-popup__item-image' src={item.image} mode='aspectFill' lazyLoad />
<View className='order-popup__item-title'> <View className='order-popup__item-title'>
<Text className='order-popup__item-name'>{item.name}</Text> <Text className='order-popup__item-name'>{item.name}</Text>
<Text className='order-popup__item-spec'>{item.spec} / {item.unit}</Text> <Text className='order-popup__item-spec'>{formatSpec(item.spec, item.unit)}</Text>
</View> </View>
</View> </View>
<View className='order-popup__item-right'> <View className='order-popup__item-right'>
+4 -2
View File
@@ -9,6 +9,8 @@ import type { HomeConfig } from '@/services/home'
import { getProductListApi } from '@/services/product' import { getProductListApi } from '@/services/product'
import { getProductCover } from '@/types/product' import { getProductCover } from '@/types/product'
import type { Product } from '@/types/product' import type { Product } from '@/types/product'
import PriceText from '@/components/PriceText'
import { formatSpec } from '@/utils/format'
import './index.less' import './index.less'
/** 首页 → 商品页的本地存储传参 key(switchTab 无法带参) */ /** 首页 → 商品页的本地存储传参 key(switchTab 无法带参) */
@@ -247,10 +249,10 @@ export default function IndexPage() {
/> />
<View className='product-card__info'> <View className='product-card__info'>
<Text className='product-card__name'>{product.name}</Text> <Text className='product-card__name'>{product.name}</Text>
<Text className='product-card__spec'>{product.spec} / {product.unit}</Text> <Text className='product-card__spec'>{formatSpec(product.spec, product.unit)}</Text>
<View className='product-card__bottom'> <View className='product-card__bottom'>
{product.price !== null ? ( {product.price !== null ? (
<Text className='product-card__price'>{product.price}</Text> <PriceText className='product-card__price' price={product.price} spec={product.spec} mode='block' />
) : ( ) : (
<Text className='product-card__price product-card__price--none'></Text> <Text className='product-card__price product-card__price--none'></Text>
)} )}
+6 -4
View File
@@ -5,7 +5,8 @@ import { Empty, Popup } from '@antmjs/vantui'
import useAuthStore from '@/stores/auth/useAuthStore' import useAuthStore from '@/stores/auth/useAuthStore'
import { cancelOrderApi, getOrderDetailApi, getOrderListApi } from '@/services/order' import { cancelOrderApi, getOrderDetailApi, getOrderListApi } from '@/services/order'
import { ORDER_STATUS_FILTERS, ORDER_STATUS_TEXT } from '@/types/order' import { ORDER_STATUS_FILTERS, ORDER_STATUS_TEXT } from '@/types/order'
import { resolveFileUrl } from '@/utils/format' import { formatSpec, resolveFileUrl } from '@/utils/format'
import PriceText from '@/components/PriceText'
import type { OrderDetail, OrderListItem, OrderStatus } from '@/types/order' import type { OrderDetail, OrderListItem, OrderStatus } from '@/types/order'
import './index.less' import './index.less'
@@ -184,8 +185,8 @@ export default function OrderListPage() {
)} )}
<View className='order-item__goods-info'> <View className='order-item__goods-info'>
<Text className='order-item__goods-name'>{i.product_name}</Text> <Text className='order-item__goods-name'>{i.product_name}</Text>
{!!i.product_spec && ( {!!formatSpec(i.product_spec, i.unit) && (
<Text className='order-item__goods-spec'>{i.product_spec} {i.unit}</Text> <Text className='order-item__goods-spec'>{formatSpec(i.product_spec, i.unit)}</Text>
)} )}
</View> </View>
<Text className='order-item__goods-qty'>×{i.quantity} </Text> <Text className='order-item__goods-qty'>×{i.quantity} </Text>
@@ -262,7 +263,8 @@ export default function OrderListPage() {
<View className='detail-popup__item-info'> <View className='detail-popup__item-info'>
<Text className='detail-popup__item-name'>{item.product_name}</Text> <Text className='detail-popup__item-name'>{item.product_name}</Text>
<Text className='detail-popup__item-spec'> <Text className='detail-popup__item-spec'>
{item.product_spec ? `${item.product_spec}${item.unit} ` : ''}{item.price} × {item.quantity} {formatSpec(item.product_spec, item.unit)}{' '}
<PriceText price={item.price} spec={item.product_spec} /> × {item.quantity}
</Text> </Text>
</View> </View>
<Text className='detail-popup__item-amount'>{item.amount}</Text> <Text className='detail-popup__item-amount'>{item.amount}</Text>
-6
View File
@@ -61,12 +61,6 @@
} }
} }
&__unit {
margin-left: 8rpx;
font-size: 24rpx;
color: #969799;
}
&__name { &__name {
display: block; display: block;
margin-top: 16rpx; margin-top: 16rpx;
+4 -4
View File
@@ -5,7 +5,8 @@ import { Empty, Stepper, Swiper, SwiperItem } from '@antmjs/vantui'
import useAuthStore from '@/stores/auth/useAuthStore' import useAuthStore from '@/stores/auth/useAuthStore'
import useCartStore from '@/stores/cart/useCartStore' import useCartStore from '@/stores/cart/useCartStore'
import { getProductDetailApi } from '@/services/product' import { getProductDetailApi } from '@/services/product'
import { resolveFileUrl } from '@/utils/format' import { formatSpec, resolveFileUrl } from '@/utils/format'
import PriceText from '@/components/PriceText'
import type { Product } from '@/types/product' import type { Product } from '@/types/product'
import './index.less' import './index.less'
@@ -115,16 +116,15 @@ export default function ProductDetailPage() {
<View className='goods-card'> <View className='goods-card'>
<View className='goods-card__price-row'> <View className='goods-card__price-row'>
{product.price !== null ? ( {product.price !== null ? (
<Text className='goods-card__price'>{product.price}</Text> <PriceText className='goods-card__price' price={product.price} spec={product.spec} />
) : ( ) : (
<Text className='goods-card__price goods-card__price--none'> <Text className='goods-card__price goods-card__price--none'>
{loggedIn ? '价格待定' : '登录后查看价格'} {loggedIn ? '价格待定' : '登录后查看价格'}
</Text> </Text>
)} )}
<Text className='goods-card__unit'>/{product.unit}</Text>
</View> </View>
<Text className='goods-card__name'>{product.name}</Text> <Text className='goods-card__name'>{product.name}</Text>
<Text className='goods-card__spec'>{product.spec}</Text> <Text className='goods-card__spec'>{formatSpec(product.spec, product.unit)}</Text>
<View className='goods-card__meta'> <View className='goods-card__meta'>
{!!product.shelf_life && product.shelf_life > 0 && ( {!!product.shelf_life && product.shelf_life > 0 && (
<Text className='goods-card__tag'> {product.shelf_life} </Text> <Text className='goods-card__tag'> {product.shelf_life} </Text>
+6 -4
View File
@@ -7,6 +7,8 @@ import { getCategoriesApi, getProductListApi } from '@/services/product'
import type { ProductListParams } from '@/services/product' import type { ProductListParams } from '@/services/product'
import { getProductCover } from '@/types/product' import { getProductCover } from '@/types/product'
import type { Category, Product } from '@/types/product' import type { Category, Product } from '@/types/product'
import PriceText from '@/components/PriceText'
import { formatSpec } from '@/utils/format'
import './index.less' import './index.less'
const PAGE_SIZE = 10 const PAGE_SIZE = 10
@@ -288,10 +290,10 @@ export default function ProductPage() {
/> />
<View className='product-item__info'> <View className='product-item__info'>
<Text className='product-item__name'>{product.name}</Text> <Text className='product-item__name'>{product.name}</Text>
<Text className='product-item__spec'>{product.spec} / {product.unit}</Text> <Text className='product-item__spec'>{formatSpec(product.spec, product.unit)}</Text>
<View className='product-item__bottom'> <View className='product-item__bottom'>
{product.price !== null ? ( {product.price !== null ? (
<Text className='product-item__price'>{product.price}</Text> <PriceText className='product-item__price' price={product.price} spec={product.spec} mode='block' />
) : ( ) : (
<Text className='product-item__price product-item__price--none'></Text> <Text className='product-item__price product-item__price--none'></Text>
)} )}
@@ -339,9 +341,9 @@ export default function ProductPage() {
/> />
<View className='add-popup__info'> <View className='add-popup__info'>
<Text className='add-popup__name'>{current.name}</Text> <Text className='add-popup__name'>{current.name}</Text>
<Text className='add-popup__spec'>{current.spec} / {current.unit}</Text> <Text className='add-popup__spec'>{formatSpec(current.spec, current.unit)}</Text>
{current.price !== null ? ( {current.price !== null ? (
<Text className='add-popup__price'>{current.price}</Text> <PriceText className='add-popup__price' price={current.price} spec={current.spec} />
) : ( ) : (
<Text className='add-popup__price add-popup__price--none'></Text> <Text className='add-popup__price add-popup__price--none'></Text>
)} )}
+23
View File
@@ -55,3 +55,26 @@ export function resolveFileUrl(url?: string): string {
export function resolveAvatarUrl(avatar?: string): string { export function resolveAvatarUrl(avatar?: string): string {
return resolveFileUrl(avatar) return resolveFileUrl(avatar)
} }
/**
* 商品规格展示:包规与单位直接拼接
* spec=20、unit=斤/箱 → 20斤/箱
*/
export function formatSpec(spec?: string | number | null, unit?: string | null): string {
const s = spec === null || spec === undefined ? '' : String(spec).trim()
const u = (unit ?? '').trim()
return `${s}${u}`
}
/**
* 零售价 = 售价 ÷ 包规(如 30¥/箱 ÷ 20斤/箱 = 2¥/斤)
* 保留两位小数并去掉尾零(2 → "2"2.50 → "2.5"
* 售价为空、包规非数字或 ≤0 时返回 null(不展示零售价)
*/
export function formatRetailPrice(price?: string | number | null, spec?: string | number | null): string | null {
if (price === null || price === undefined || price === '') return null
const p = Number(price)
const s = Number(spec)
if (!Number.isFinite(p) || !Number.isFinite(s) || s <= 0) return null
return String(Math.round((p / s) * 100) / 100)
}