优化金额显示等

This commit is contained in:
liu
2026-08-29 13:52:57 +08:00
parent 2317c9d973
commit 130ef2c949
17 changed files with 93 additions and 159 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
// 购物车悬浮球:右下角,底部避让自定义 tabBar(110rpx + 安全区)
.cart-ball {
position: fixed;
right: 24rpx;
left: 24rpx;
bottom: calc(150rpx + env(safe-area-inset-bottom));
z-index: 998;
display: flex;
+5 -3
View File
@@ -15,13 +15,15 @@ interface PriceTextProps {
* - block 独占一行(窄卡片:首页推荐、商品列表、购物车)
*/
mode?: 'inline' | 'block'
/** 单位 */
price_unit?: string | null
}
/**
* 商品价格:售价 + 零售价标注(小字)
* price=30、spec=15 → ¥30 零售价:¥2
*/
export default function PriceText({ price, spec, className, mode = 'inline' }: PriceTextProps) {
export default function PriceText({ price, spec, className, mode = 'inline', price_unit }: PriceTextProps) {
const retail = formatRetailPrice(price, spec)
// 窄卡片:大价格 / 零售价上下两行,避免与右侧按钮(+/步进器)挤压换行
@@ -30,7 +32,7 @@ export default function PriceText({ price, spec, className, mode = 'inline' }: P
<View className={`price-text ${className ?? ''}`}>
<Text className='price-text__main'>{price}</Text>
{retail !== null && (
<Text className='price-text__retail'>{retail}</Text>
<Text className='price-text__retail'>{retail} {price_unit}</Text>
)}
</View>
)
@@ -40,7 +42,7 @@ export default function PriceText({ price, spec, className, mode = 'inline' }: P
<Text className={className}>
{price}
{retail !== null && (
<Text className='price-text__retail price-text__retail--inline'>{retail}</Text>
<Text className='price-text__retail price-text__retail--inline'>{retail} {price_unit}</Text>
)}
</Text>
)
-6
View File
@@ -147,12 +147,6 @@
flex-shrink: 0;
}
&__qty {
font-size: 24rpx;
color: #969799;
display: block;
}
&__amount {
font-size: 28rpx;
color: #323233;
+24 -25
View File
@@ -5,8 +5,7 @@ import { Empty, Popup } from '@antmjs/vantui'
import { getBillDetailApi } from '@/services/bill'
import { getOrderDetailApi } from '@/services/order'
import { ORDER_STATUS_TEXT } from '@/types/order'
import { formatSpec, resolveFileUrl } from '@/utils/format'
import PriceText from '@/components/PriceText'
import {formatRetailPrice, formatSpec, resolveFileUrl} from '@/utils/format'
import type { OrderDetail } from '@/types/order'
import type { BillDetail } from '@/services/bill'
import './index.less'
@@ -60,18 +59,9 @@ export default function BillDetailPage() {
const { bill, items, orders } = detail
/** 附加金额:正=压筐附加,负=回筐抵扣(0 不带头符号) */
const addedNum = Number(bill.added_amount)
const addedLabel = addedNum < 0 ? '回筐抵扣' : '压筐附加'
const addedText =
addedNum < 0
? `-¥${Math.abs(addedNum).toFixed(2)}`
: addedNum > 0
? `+¥${addedNum.toFixed(2)}`
: '¥0.00'
/** 筐/托盘明细:正压负回,数量取绝对值(单价为出账时快照) */
const boxPart = `${bill.box_num < 0 ? '回筐' : '压筐'} ${Math.abs(bill.box_num)}×¥${bill.box_price}`
const trayPart = `${bill.tray_num < 0 ? '回托盘' : '压托盘'} ${Math.abs(bill.tray_num)}×¥${bill.tray_price}`
const boxTotalPrice = (Number(bill.box_price) * bill.box_num).toFixed(2)
const trayTotalPrice = (Number(bill.tray_price) * bill.tray_num).toFixed(2)
return (
<View className={`bill-detail ${bill.can_pay ? 'bill-detail--pay' : ''}`}>
@@ -124,7 +114,7 @@ export default function BillDetailPage() {
{/* ===== 金额构成 ===== */}
<View className='bill-card'>
<Text className='bill-section__title'></Text>
<Text className='bill-section__title'></Text>
<View className='bill-card__row'>
<Text className='bill-card__label'></Text>
<Text className='bill-card__value'>{bill.product_amount}</Text>
@@ -134,9 +124,21 @@ export default function BillDetailPage() {
<Text className='bill-card__value'>{bill.delivery_fee}</Text>
</View>
<View className='bill-card__row'>
<Text className='bill-card__label'>{addedLabel}{boxPart}{trayPart}</Text>
<Text className={`bill-card__value ${addedNum < 0 ? 'bill-card__value--return' : ''}`}>
{addedText}
<Text className='bill-card__label'>{bill.box_price} × {bill.box_num}</Text>
<Text className={`bill-card__value ${Number(boxTotalPrice) < 0 ? 'bill-card__value--return' : ''}`}>
{Number(boxTotalPrice) < 0 ? `- ¥${boxTotalPrice}` : `${boxTotalPrice}`}
</Text>
</View>
<View className='bill-card__row'>
<Text className='bill-card__label'>{bill.tray_price} × {bill.tray_num}</Text>
<Text className={`bill-card__value ${Number(trayTotalPrice) < 0 ? 'bill-card__value--return' : ''}`}>
{Number(trayTotalPrice) < 0 ? `- ¥${trayTotalPrice}` : `${trayTotalPrice}`}
</Text>
</View>
<View className='bill-card__row'>
<Text className='bill-card__label'></Text>
<Text className={`bill-card__value ${Number(bill.after_sale) < 0 ? 'bill-card__value--return' : ''}`}>
{Number(bill.after_sale) < 0 ? `- ¥${bill.after_sale}` : `${bill.after_sale}`}
</Text>
</View>
<View className='bill-card__row bill-card__row--total'>
@@ -148,7 +150,6 @@ export default function BillDetailPage() {
{/* ===== 商品明细(跨订单按商品合并) ===== */}
<View className='bill-card'>
<Text className='bill-section__title'>{items?.length ?? 0}</Text>
<Text className='bill-section__desc'></Text>
{(items ?? []).map(item => (
<View key={item.product_id} className='bill-goods'>
{!!item.image && (
@@ -162,13 +163,11 @@ export default function BillDetailPage() {
<View className='bill-goods__main'>
<Text className='bill-goods__name'>{item.product_name}</Text>
<Text className='bill-goods__spec'>
{formatSpec(item.product_spec, item.unit)}{' '}
<PriceText price={item.price} spec={item.product_spec} />
{formatSpec(item.product_spec, item.unit)}{' '}{item.price}
</Text>
</View>
<View className='bill-goods__side'>
<Text className='bill-goods__qty'>×{item.quantity}</Text>
<Text className='bill-goods__amount'>{item.amount}</Text>
<Text className='bill-goods__amount'>{item.price} × {item.quantity}</Text>
</View>
</View>
))}
@@ -216,11 +215,11 @@ export default function BillDetailPage() {
<View className='order-popup__item-info'>
<Text className='order-popup__item-name'>{item.product_name}</Text>
<Text className='order-popup__item-spec'>
{formatSpec(item.product_spec, item.unit)}{' '}
<PriceText price={item.price} spec={item.product_spec} /> × {item.quantity}
{formatSpec(item.product_spec, item.unit)}{' '}
{formatRetailPrice(item.price, item.product_spec)} {item.price_unit}
</Text>
</View>
<Text className='order-popup__item-amount'>{item.amount}</Text>
<Text className='order-popup__item-amount'>{item.price} × {item.quantity}</Text>
</View>
))}
</ScrollView>
+10
View File
@@ -88,6 +88,15 @@
white-space: nowrap;
}
&__spec-tag {
margin-left: 12rpx;
font-size: 24rpx;
color: #969799;
border-radius: 6rpx;
padding: 2rpx 8rpx;
flex-shrink: 0;
}
&__invalid-tag {
margin-left: 12rpx;
font-size: 20rpx;
@@ -167,6 +176,7 @@
padding: 16rpx 24rpx;
border-top: 1rpx solid #ebedf0;
box-sizing: border-box;
z-index: 99;
&__total {
flex: 1;
+10 -10
View File
@@ -6,8 +6,7 @@ import useAuthStore from '@/stores/auth/useAuthStore'
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 {formatRetailPrice, formatSpec} from '@/utils/format'
import type { CartItem } from '@/types/cart'
import type { StoreDetail } from '@/types/store'
import './index.less'
@@ -159,7 +158,7 @@ export default function CartPage() {
if (submitting) return
setSubmitting(true)
try {
const res = await createOrderApi({
await createOrderApi({
items: purchasable.map(item => ({
product_id: item.product_id,
quantity: Number(qtyMap[item.id] ?? item.quantity),
@@ -221,13 +220,12 @@ export default function CartPage() {
<Text className='cart-item__name'>{item.name}</Text>
{item.status === 0 && <Text className='cart-item__invalid-tag'></Text>}
</View>
<Text className='cart-item__spec'>{formatSpec(item.spec, item.unit)}</Text>
<Text className='cart-item__spec'>
{formatSpec(item.spec, item.unit)}{' '}
{formatRetailPrice(item.price, item.spec)} {item.price_unit}
</Text>
<View className='cart-item__bottom'>
{item.price !== null ? (
<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'>{item.price}</Text>
{item.status === 1 ? (
<Stepper
value={displayQty(item)}
@@ -252,6 +250,8 @@ export default function CartPage() {
))
)}
<View style={{ height: 100 }}></View>
{loggedIn && hasInvalid && (
<View className='cart-invalid-hint'>
<Text></Text>
@@ -262,7 +262,7 @@ export default function CartPage() {
{loggedIn && items.length > 0 && (
<View className='cart-footer'>
<View className='cart-footer__total'>
<Text className='cart-footer__label'>{totalQuantity}</Text>
<Text className='cart-footer__label'>{totalQuantity}</Text>
<Text className='cart-footer__amount'>{totalAmount}</Text>
</View>
<Button type='danger' className='cart-footer__submit' onClick={handleOrderTap}>
+1 -2
View File
@@ -274,8 +274,8 @@
font-size: 28rpx;
color: #323233;
font-weight: 500;
display: block;
overflow: hidden;
margin-right: 20rpx;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -284,7 +284,6 @@
margin-top: 8rpx;
font-size: 22rpx;
color: #969799;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+8 -5
View File
@@ -2,17 +2,15 @@ import { useCallback, useMemo, useState } from 'react'
import Taro, { useDidShow } from '@tarojs/taro'
import { View, Text, Image } from '@tarojs/components'
import { Grid, GridItem, Icon, Search, Swiper, SwiperItem } from '@antmjs/vantui'
import useAuthStore from '@/stores/auth/useAuthStore'
import useCartStore from '@/stores/cart/useCartStore'
import { getHomeConfigApi } from '@/services/home'
import type { HomeConfig } from '@/services/home'
import { getProductListApi } from '@/services/product'
import { getProductCover } from '@/types/product'
import type { Product, ProductCartPatch } from '@/types/product'
import PriceText from '@/components/PriceText'
import CartBall from '@/components/CartBall'
import CartStepper from '@/components/CartStepper'
import { formatSpec } from '@/utils/format'
import {formatRetailPrice, formatSpec} from '@/utils/format'
import './index.less'
/** 首页 → 商品页的本地存储传参 key(switchTab 无法带参) */
@@ -265,10 +263,15 @@ export default function IndexPage() {
/>
<View className='product-card__info'>
<Text className='product-card__name'>{product.name}</Text>
<Text className='product-card__spec'>{formatSpec(product.spec, product.unit)}</Text>
<View className='product-card__spec'>
{formatSpec(product.spec, product.unit)}{' '}
{product.price !== null && <>
{formatRetailPrice(product.price, product.spec)} {product.price_unit}
</>}
</View>
<View className='product-card__bottom'>
{product.price !== null ? (
<PriceText className='product-card__price' price={product.price} spec={product.spec} mode='block' />
<Text className='product-card__price'>{product.price}</Text>
) : (
<Text className='product-card__price product-card__price--none'></Text>
)}
+3 -3
View File
@@ -5,7 +5,7 @@ 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 { formatSpec, resolveFileUrl } from '@/utils/format'
import {formatRetailPrice, formatSpec, resolveFileUrl} from '@/utils/format'
import PriceText from '@/components/PriceText'
import type { OrderDetail, OrderListItem, OrderStatus } from '@/types/order'
import './index.less'
@@ -264,10 +264,10 @@ export default function OrderListPage() {
<Text className='detail-popup__item-name'>{item.product_name}</Text>
<Text className='detail-popup__item-spec'>
{formatSpec(item.product_spec, item.unit)}{' '}
<PriceText price={item.price} spec={item.product_spec} /> × {item.quantity}
{formatRetailPrice(item.price, item.product_spec)} {item.price_unit}
</Text>
</View>
<Text className='detail-popup__item-amount'>{item.amount}</Text>
<Text className='detail-popup__item-amount'>{item.price} × {item.quantity}</Text>
</View>
))}
</ScrollView>
+1 -18
View File
@@ -32,24 +32,11 @@ const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc:
/**
* 发起付款页(合并付款)
* 选择本店可付款账单(?payable=1)→ 选择支付方式 → 提交:
* - 在线支付(仅小程序):wx.login 取 code → 后端经旺铺网关下单 → 调起微信支付 → 主动查询同步结果(回调兜底)
* - 线下凭证:展示收款码 / 对公账户 → 上传汇款凭证 → 提交,后台审核
* 支持 ?ids=1,2 预选账单(账单详情页"去付款"跳转)
*/
export default function PaymentPage() {
const router = useRouter()
const token = useAuthStore(s => s.token)
const loggedIn = !!token
/** 预选账单(路由参数,仅在首次加载时消费一次) */
const presetRef = useRef<number[] | null>(
(router.params.ids || '')
.split(',')
.map(Number)
.filter(n => n > 0),
)
const [bills, setBills] = useState<Bill[]>([])
const [selectedIds, setSelectedIds] = useState<number[]>([])
const [page, setPage] = useState(1)
@@ -79,11 +66,7 @@ export default function PaymentPage() {
setBills(prev => (reset ? data : [...prev, ...data]))
setPage(pageNum)
setFinished(pageNum * PAGE_SIZE >= total)
if (presetRef.current) {
const preset = presetRef.current
presetRef.current = null
setSelectedIds(prev => Array.from(new Set([...prev, ...preset])))
}
setSelectedIds(prev => Array.from(new Set([...prev, ...data.map(i => i.id)])))
} catch {
// 错误已由 request 层 toast
} finally {
+8 -4
View File
@@ -5,8 +5,7 @@ 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 { formatSpec, resolveFileUrl } from '@/utils/format'
import PriceText from '@/components/PriceText'
import {formatRetailPrice, formatSpec, resolveFileUrl} from '@/utils/format'
import type { Product } from '@/types/product'
import './index.less'
@@ -116,7 +115,7 @@ export default function ProductDetailPage() {
<View className='goods-card'>
<View className='goods-card__price-row'>
{product.price !== null ? (
<PriceText className='goods-card__price' price={product.price} spec={product.spec} />
<Text className='goods-card__price'>{product.price}</Text>
) : (
<Text className='goods-card__price goods-card__price--none'>
{loggedIn ? '价格待定' : '登录后查看价格'}
@@ -124,7 +123,12 @@ export default function ProductDetailPage() {
)}
</View>
<Text className='goods-card__name'>{product.name}</Text>
<Text className='goods-card__spec'>{formatSpec(product.spec, product.unit)}</Text>
<Text className='goods-card__spec'>
{formatSpec(product.spec, product.unit)}{' '}
{product.price !== null && <>
{formatRetailPrice(product.price, product.spec)} {product.price_unit}
</>}
</Text>
<View className='goods-card__meta'>
{!!product.shelf_life && product.shelf_life > 0 && (
<Text className='goods-card__tag'> {product.shelf_life} </Text>
+15 -80
View File
@@ -1,16 +1,15 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import Taro, { useDidShow } from '@tarojs/taro'
import { View, Text, Image, ScrollView } from '@tarojs/components'
import { Button, Empty, Popup, Search, Stepper } from '@antmjs/vantui'
import { Empty, Search } from '@antmjs/vantui'
import useCartStore from '@/stores/cart/useCartStore'
import { getCategoriesApi, getProductListApi } from '@/services/product'
import type { ProductListParams } from '@/services/product'
import { getProductCover } from '@/types/product'
import type { Category, Product, ProductCartPatch } from '@/types/product'
import PriceText from '@/components/PriceText'
import CartBall from '@/components/CartBall'
import CartStepper from '@/components/CartStepper'
import { formatSpec } from '@/utils/format'
import {formatRetailPrice, formatSpec} from '@/utils/format'
import './index.less'
const PAGE_SIZE = 10
@@ -43,12 +42,6 @@ export default function ProductPage() {
/** 是否有请求进行中(仅用于避免"加载更多"并发) */
const loadingRef = useRef(false)
/** 加购弹层 */
const [showPopup, setShowPopup] = useState(false)
const [current, setCurrent] = useState<Product | null>(null)
const [qty, setQty] = useState(1)
const addingRef = useRef(false)
/** 当前选中二级分类所属的一级分类ID(用于父级高亮) */
const activeParentId = useMemo(() => {
if (activeId == null) return null
@@ -191,13 +184,6 @@ export default function ProductPage() {
setSearchKey('')
}, [])
/** 打开加购弹层 */
const handleAddTap = useCallback((product: Product) => {
setCurrent(product)
setQty(1)
setShowPopup(true)
}, [])
/** 行内加减购确认后回写列表项的购物车字段 */
const handleRowSync = useCallback((productId: number, patch: ProductCartPatch) => {
setProducts(prev => prev.map(p => (p.id === productId ? { ...p, ...patch } : p)))
@@ -209,20 +195,15 @@ export default function ProductPage() {
}, [])
/** 确认加购(用返回的购物车行回写列表项,行内随即展示加减器) */
const handleConfirmAdd = useCallback(async () => {
if (!current || addingRef.current) return
addingRef.current = true
const handleConfirmAdd = useCallback(async (product: Product) => {
try {
const res = await addItem(current.id, qty)
handleRowSync(current.id, { cart_id: res.id, cart_quantity: res.quantity })
const res = await addItem(product.id, 1)
handleRowSync(product.id, { cart_id: res.id, cart_quantity: res.quantity })
Taro.showToast({ title: '已加入购物车', icon: 'success' })
setShowPopup(false)
} catch {
// 错误(未设等级价/数量上限)已由 request 层 toast
} finally {
addingRef.current = false
}
}, [current, qty, addItem, handleRowSync])
}, [addItem, handleRowSync])
return (
<View className='product-page'>
@@ -307,10 +288,15 @@ export default function ProductPage() {
/>
<View className='product-item__info'>
<Text className='product-item__name'>{product.name}</Text>
<Text className='product-item__spec'>{formatSpec(product.spec, product.unit)}</Text>
<Text className='product-item__spec'>
{formatSpec(product.spec, product.unit)}{' '}
{product.price !== null && <>
{formatRetailPrice(product.price, product.spec)} {product.price_unit}
</>}
</Text>
<View className='product-item__bottom'>
{product.price !== null ? (
<PriceText className='product-item__price' price={product.price} spec={product.spec} mode='block' />
<Text className='product-item__price'>{product.price}</Text>
) : (
<Text className='product-item__price product-item__price--none'></Text>
)}
@@ -322,7 +308,7 @@ export default function ProductPage() {
className='product-item__add'
onClick={e => {
e.stopPropagation()
handleAddTap(product)
handleConfirmAdd(product)
}}
>
<Text className='product-item__add-icon'></Text>
@@ -343,57 +329,6 @@ export default function ProductPage() {
</ScrollView>
</View>
{/* ========== 加购弹层 ========== */}
<Popup
show={showPopup}
position='bottom'
round
closeable
closeOnClickOverlay
safeAreaInsetBottom
style={{ paddingBottom: '110px' }}
onClose={() => setShowPopup(false)}
>
{current && (
<View className='add-popup'>
<View className='add-popup__product'>
<Image
className='add-popup__image'
src={getProductCover(current) || 'https://img.yzcdn.cn/vant/cat.jpeg'}
mode='aspectFill'
/>
<View className='add-popup__info'>
<Text className='add-popup__name'>{current.name}</Text>
<Text className='add-popup__spec'>{formatSpec(current.spec, current.unit)}</Text>
{current.price !== null ? (
<PriceText className='add-popup__price' price={current.price} spec={current.spec} />
) : (
<Text className='add-popup__price add-popup__price--none'></Text>
)}
</View>
</View>
<View className='add-popup__row'>
<Text className='add-popup__label'></Text>
<Stepper
value={qty}
min={1}
max={99999999.99}
onChange={e => setQty(Number(e.detail))}
/>
</View>
<Button
type='danger'
block
round
className='add-popup__submit'
onClick={handleConfirmAdd}
>
</Button>
</View>
)}
</Popup>
{/* ========== 购物车悬浮球 ========== */}
<CartBall />
</View>
+2
View File
@@ -44,6 +44,8 @@ export interface Bill {
settlement_date: string
/** 付款时间(已支付时非空) */
paid_at: string | null
/** 售后金额 */
after_sale: string
/** 付款备注 */
pay_remark: string
/** 账单备注 */
+1
View File
@@ -15,6 +15,7 @@ export interface CartItem {
amount: string | null
/** 1 可购 / 0 商品下架、缺失或未设等级价 */
status: number
price_unit: string
}
/** 购物车列表数据 */
+1
View File
@@ -85,6 +85,7 @@ export interface OrderItem {
product_name: string
product_spec: string
unit: string
price_unit: string
/** 下单时门店等级实际价快照 */
price: string
quantity: number
+1
View File
@@ -27,6 +27,7 @@ export interface Product {
content: string
/** 当前门店等级的实际销售价(未登录/未绑店/未设等级为 null) */
price: string | null
price_unit: string | null
images_arr: ProductImage[]
/** 所属分类(详情接口 with 返回) */
category?: { id: number; name: string } | null
+2 -2
View File
@@ -10,8 +10,8 @@ const LOGIN_PATH = '/pages/login/index'
/** 默认请求超时(ms */
const DEFAULT_TIMEOUT = 15000
/** 接口根地址(uploadFile 等原生请求同样使用) */
// export const BASE_URL = "http://localhost:8000/index.php"
export const BASE_URL = "https://purchase.henanklkj.com/index.php"
export const BASE_URL = "http://localhost:8000/index.php"
// export const BASE_URL = "https://purchase.henanklkj.com/index.php"
/**
* HTTP 状态码 → 错误提示映射