diff --git a/src/components/PriceText/index.less b/src/components/PriceText/index.less new file mode 100644 index 0000000..d1fca35 --- /dev/null +++ b/src/components/PriceText/index.less @@ -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; +} diff --git a/src/components/PriceText/index.tsx b/src/components/PriceText/index.tsx new file mode 100644 index 0000000..f2afd3d --- /dev/null +++ b/src/components/PriceText/index.tsx @@ -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 ( + + ¥{price} + {retail !== null && ( + 零售价:¥{retail} + )} + + ) + } + + return ( + + ¥{price} + {retail !== null && ( + 零售价:¥{retail} + )} + + ) +} diff --git a/src/pages/bill-detail/index.tsx b/src/pages/bill-detail/index.tsx index ea91a4a..2930d8c 100644 --- a/src/pages/bill-detail/index.tsx +++ b/src/pages/bill-detail/index.tsx @@ -5,7 +5,8 @@ import { Empty, Popup } from '@antmjs/vantui' import { getBillDetailApi } from '@/services/bill' import { getOrderDetailApi } from '@/services/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 { BillDetail } from '@/services/bill' import './index.less' @@ -161,7 +162,8 @@ export default function BillDetailPage() { {item.product_name} - {item.product_spec ? `${item.product_spec} ` : ''}¥{item.price}/{item.unit} + {formatSpec(item.product_spec, item.unit)}{' '} + @@ -214,7 +216,8 @@ export default function BillDetailPage() { {item.product_name} - {item.product_spec ? `${item.product_spec} ` : ''}¥{item.price}/{item.unit} × {item.quantity} + {formatSpec(item.product_spec, item.unit)}{' '} + × {item.quantity} ¥{item.amount} diff --git a/src/pages/cart/index.tsx b/src/pages/cart/index.tsx index 0db53b3..c5b6ba3 100644 --- a/src/pages/cart/index.tsx +++ b/src/pages/cart/index.tsx @@ -5,6 +5,8 @@ import { Button, Empty, Icon, Popup, Stepper } from '@antmjs/vantui' import useCartStore from '@/stores/cart/useCartStore' import { createOrderApi } from '@/services/order' import { getStoreInfoApi } from '@/services/store' +import PriceText from '@/components/PriceText' +import { formatSpec } from '@/utils/format' import type { CartItem } from '@/types/cart' import type { StoreDetail } from '@/types/store' import './index.less' @@ -205,10 +207,10 @@ export default function CartPage() { {item.name} {item.status === 0 && 已失效} - {item.spec} / {item.unit} + {formatSpec(item.spec, item.unit)} {item.price !== null ? ( - ¥{item.price} + ) : ( 价格待定 )} @@ -302,7 +304,7 @@ export default function CartPage() { {item.name} - {item.spec} / {item.unit} + {formatSpec(item.spec, item.unit)} diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index 0a00ae1..48aa728 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -9,6 +9,8 @@ import type { HomeConfig } from '@/services/home' import { getProductListApi } from '@/services/product' import { getProductCover } from '@/types/product' import type { Product } from '@/types/product' +import PriceText from '@/components/PriceText' +import { formatSpec } from '@/utils/format' import './index.less' /** 首页 → 商品页的本地存储传参 key(switchTab 无法带参) */ @@ -247,10 +249,10 @@ export default function IndexPage() { /> {product.name} - {product.spec} / {product.unit} + {formatSpec(product.spec, product.unit)} {product.price !== null ? ( - ¥{product.price} + ) : ( 登陆后查看价格 )} diff --git a/src/pages/order-list/index.tsx b/src/pages/order-list/index.tsx index 6c2623d..807d057 100644 --- a/src/pages/order-list/index.tsx +++ b/src/pages/order-list/index.tsx @@ -5,7 +5,8 @@ import { Empty, Popup } from '@antmjs/vantui' import useAuthStore from '@/stores/auth/useAuthStore' import { cancelOrderApi, getOrderDetailApi, getOrderListApi } from '@/services/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 './index.less' @@ -184,8 +185,8 @@ export default function OrderListPage() { )} {i.product_name} - {!!i.product_spec && ( - {i.product_spec} {i.unit} + {!!formatSpec(i.product_spec, i.unit) && ( + {formatSpec(i.product_spec, i.unit)} )} ×{i.quantity} @@ -262,7 +263,8 @@ export default function OrderListPage() { {item.product_name} - {item.product_spec ? `${item.product_spec}${item.unit} ` : ''}¥{item.price} × {item.quantity} + {formatSpec(item.product_spec, item.unit)}{' '} + × {item.quantity} ¥{item.amount} diff --git a/src/pages/product-detail/index.less b/src/pages/product-detail/index.less index 3a483cb..05b6a48 100644 --- a/src/pages/product-detail/index.less +++ b/src/pages/product-detail/index.less @@ -61,12 +61,6 @@ } } - &__unit { - margin-left: 8rpx; - font-size: 24rpx; - color: #969799; - } - &__name { display: block; margin-top: 16rpx; diff --git a/src/pages/product-detail/index.tsx b/src/pages/product-detail/index.tsx index 168ad0b..2b28652 100644 --- a/src/pages/product-detail/index.tsx +++ b/src/pages/product-detail/index.tsx @@ -5,7 +5,8 @@ import { Empty, Stepper, Swiper, SwiperItem } from '@antmjs/vantui' import useAuthStore from '@/stores/auth/useAuthStore' import useCartStore from '@/stores/cart/useCartStore' 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 './index.less' @@ -115,16 +116,15 @@ export default function ProductDetailPage() { {product.price !== null ? ( - ¥{product.price} + ) : ( {loggedIn ? '价格待定' : '登录后查看价格'} )} - /{product.unit} {product.name} - {product.spec} + {formatSpec(product.spec, product.unit)} {!!product.shelf_life && product.shelf_life > 0 && ( 保质期 {product.shelf_life} 天 diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx index 961b03d..b147753 100644 --- a/src/pages/product/index.tsx +++ b/src/pages/product/index.tsx @@ -7,6 +7,8 @@ import { getCategoriesApi, getProductListApi } from '@/services/product' import type { ProductListParams } from '@/services/product' import { getProductCover } from '@/types/product' import type { Category, Product } from '@/types/product' +import PriceText from '@/components/PriceText' +import { formatSpec } from '@/utils/format' import './index.less' const PAGE_SIZE = 10 @@ -288,10 +290,10 @@ export default function ProductPage() { /> {product.name} - {product.spec} / {product.unit} + {formatSpec(product.spec, product.unit)} {product.price !== null ? ( - ¥{product.price} + ) : ( 价格待定 )} @@ -339,9 +341,9 @@ export default function ProductPage() { /> {current.name} - {current.spec} / {current.unit} + {formatSpec(current.spec, current.unit)} {current.price !== null ? ( - ¥{current.price} + ) : ( 价格待定 )} diff --git a/src/utils/format.ts b/src/utils/format.ts index 61b47d0..6279767 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -55,3 +55,26 @@ export function resolveFileUrl(url?: string): string { export function resolveAvatarUrl(avatar?: string): string { 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) +}