250 lines
10 KiB
TypeScript
250 lines
10 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
||
import Taro, { useRouter } from '@tarojs/taro'
|
||
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
||
import { Empty, Popup } from '@antmjs/vantui'
|
||
import { getBillDetailApi } from '@/services/bill'
|
||
import { getOrderDetailApi } from '@/services/order'
|
||
import { ORDER_STATUS_TEXT } from '@/types/order'
|
||
import {formatRetailPrice, formatSpec, resolveFileUrl} from '@/utils/format'
|
||
import type { OrderDetail } from '@/types/order'
|
||
import type { BillDetail } from '@/services/bill'
|
||
import './index.less'
|
||
|
||
/**
|
||
* 账单详情
|
||
* 账单信息 + 金额构成(商品/配送费/筐托盘附加)+ 合并商品明细 + 关联订单(可下钻订单明细)
|
||
*/
|
||
export default function BillDetailPage() {
|
||
const router = useRouter()
|
||
const id = Number(router.params.id ?? 0)
|
||
|
||
const [detail, setDetail] = useState<BillDetail | null>(null)
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
/** 关联订单明细弹层 */
|
||
const [showOrder, setShowOrder] = useState(false)
|
||
const [orderDetail, setOrderDetail] = useState<OrderDetail | null>(null)
|
||
|
||
useEffect(() => {
|
||
if (!id) return
|
||
setLoading(true)
|
||
getBillDetailApi(id)
|
||
.then(res => setDetail(res.data))
|
||
.catch(() => {})
|
||
.finally(() => setLoading(false))
|
||
}, [id])
|
||
|
||
/** 下钻关联订单明细 */
|
||
const handleOrderTap = useCallback(async (orderId: number) => {
|
||
try {
|
||
const res = await getOrderDetailApi(orderId)
|
||
setOrderDetail(res.data)
|
||
setShowOrder(true)
|
||
} catch {
|
||
// 错误已由 request 层 toast
|
||
}
|
||
}, [])
|
||
|
||
/** 去付款 → 发起付款页(预选本账单) */
|
||
const goPay = () => {
|
||
Taro.navigateTo({ url: `/pages/payment/index` })
|
||
}
|
||
|
||
if (loading && !detail) {
|
||
return <View className='bill-detail'><Empty description='加载中...' /></View>
|
||
}
|
||
if (!detail) {
|
||
return <View className='bill-detail'><Empty description='账单不存在' /></View>
|
||
}
|
||
|
||
const { bill, items, orders } = detail
|
||
|
||
/** 筐/托盘明细:正压负回,数量取绝对值(单价为出账时快照) */
|
||
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' : ''}`}>
|
||
{/* ===== 账单信息 ===== */}
|
||
<View className='bill-card'>
|
||
<View className='bill-card__header'>
|
||
<Text className='bill-card__no'>{bill.bill_no}</Text>
|
||
<Text className={`bill-card__status bill-card__status--${bill.pay_state}`}>
|
||
{bill.pay_state_name}
|
||
</Text>
|
||
</View>
|
||
{bill.pay_state === 1 && (
|
||
<Text className='bill-card__tip'>已提交付款凭证,待后台审核;审核拒绝后将回到待支付</Text>
|
||
)}
|
||
<View className='bill-card__row'>
|
||
<Text className='bill-card__label'>账单日期</Text>
|
||
<Text className='bill-card__value'>{bill.bill_date}</Text>
|
||
</View>
|
||
{bill.purchase && (
|
||
<View className='bill-card__row'>
|
||
<Text className='bill-card__label'>关联采购单</Text>
|
||
<Text className='bill-card__value'>
|
||
{bill.purchase.purchase_no}({bill.purchase.purchase_date})
|
||
</Text>
|
||
</View>
|
||
)}
|
||
<View className='bill-card__row'>
|
||
<Text className='bill-card__label'>应结算日期</Text>
|
||
<Text className='bill-card__value'>{bill.settlement_date}</Text>
|
||
</View>
|
||
{bill.pay_state === 2 && bill.paid_at && (
|
||
<View className='bill-card__row'>
|
||
<Text className='bill-card__label'>付款时间</Text>
|
||
<Text className='bill-card__value'>{bill.paid_at}</Text>
|
||
</View>
|
||
)}
|
||
{bill.pay_remark && (
|
||
<View className='bill-card__row'>
|
||
<Text className='bill-card__label'>付款备注</Text>
|
||
<Text className='bill-card__value'>{bill.pay_remark}</Text>
|
||
</View>
|
||
)}
|
||
{bill.remark && (
|
||
<View className='bill-card__row'>
|
||
<Text className='bill-card__label'>账单备注</Text>
|
||
<Text className='bill-card__value'>{bill.remark}</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
{/* ===== 金额构成 ===== */}
|
||
<View className='bill-card'>
|
||
<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>
|
||
</View>
|
||
<View className='bill-card__row'>
|
||
<Text className='bill-card__label'>配送费</Text>
|
||
<Text className='bill-card__value'>¥{bill.delivery_fee}</Text>
|
||
</View>
|
||
<View className='bill-card__row'>
|
||
<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'>
|
||
<Text className='bill-card__label'>账单总额</Text>
|
||
<Text className='bill-card__total'>¥{bill.total_amount}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* ===== 商品明细(跨订单按商品合并) ===== */}
|
||
<View className='bill-card'>
|
||
<Text className='bill-section__title'>商品明细({items?.length ?? 0})</Text>
|
||
{(items ?? []).map(item => (
|
||
<View key={item.product_id} className='bill-goods'>
|
||
{!!item.image && (
|
||
<Image
|
||
className='bill-goods__img'
|
||
src={resolveFileUrl(item.image)}
|
||
mode='aspectFill'
|
||
lazyLoad
|
||
/>
|
||
)}
|
||
<View className='bill-goods__main'>
|
||
<View className='bill-goods__name'>{item.product_name}</View>
|
||
<View className='bill-goods__spec'>
|
||
{formatSpec(item.product_spec, item.unit)}
|
||
</View>
|
||
<View className='bill-goods__spec'>
|
||
单价:{formatRetailPrice(item.price, item.spec)} {item.price_unit}
|
||
</View>
|
||
</View>
|
||
<View className='bill-goods__side'>
|
||
<Text className='bill-goods__price'>¥{item.price} × {item.quantity}</Text>
|
||
<View className='bill-goods__amount'>¥{item.amount}</View>
|
||
</View>
|
||
</View>
|
||
))}
|
||
{(items ?? []).length === 0 && <Empty description='暂无商品记录' />}
|
||
</View>
|
||
|
||
{/* ===== 关联订单 ===== */}
|
||
<View className='bill-card'>
|
||
<Text className='bill-section__title'>关联订单({orders?.length ?? 0})</Text>
|
||
{(orders ?? []).map(order => (
|
||
<View key={order.id} className='bill-order' onClick={() => handleOrderTap(order.id)}>
|
||
<View className='bill-order__main'>
|
||
<Text className='bill-order__no'>{order.order_no}</Text>
|
||
<Text className='bill-order__date'>{order.order_date}</Text>
|
||
</View>
|
||
<Text className='bill-order__status'>{ORDER_STATUS_TEXT[order.status] || ''}</Text>
|
||
<Text className='bill-order__amount'>¥{order.total_amount}</Text>
|
||
</View>
|
||
))}
|
||
{(orders ?? []).length === 0 && <Empty description='暂无订单记录' />}
|
||
</View>
|
||
|
||
{/* ===== 订单明细弹层 ===== */}
|
||
<Popup
|
||
show={showOrder}
|
||
position='bottom'
|
||
round
|
||
closeable
|
||
closeOnClickOverlay
|
||
safeAreaInsetBottom
|
||
onClose={() => setShowOrder(false)}
|
||
>
|
||
{orderDetail && (
|
||
<View className='order-popup'>
|
||
<Text className='order-popup__title'>{orderDetail.order_no}</Text>
|
||
<View className='order-popup__meta'>
|
||
<Text>{orderDetail.order_date}</Text>
|
||
<Text className='order-popup__status'>
|
||
{ORDER_STATUS_TEXT[orderDetail.status] || ''}
|
||
</Text>
|
||
</View>
|
||
<ScrollView scrollY className='order-popup__list'>
|
||
{(orderDetail.items ?? []).map(item => (
|
||
<View key={item.id} className='order-popup__item'>
|
||
<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)}{' '}
|
||
单价:{formatRetailPrice(item.price, item.product_spec)} {item.price_unit}
|
||
</Text>
|
||
</View>
|
||
<Text className='order-popup__item-amount'>¥{item.price} × {item.quantity}</Text>
|
||
</View>
|
||
))}
|
||
</ScrollView>
|
||
<View className='order-popup__footer'>
|
||
<Text className='order-popup__total'>合计 ¥{orderDetail.total_amount}</Text>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</Popup>
|
||
|
||
{/* ===== 去付款操作栏(可付款账单) ===== */}
|
||
{bill.can_pay && (
|
||
<View className='bill-pay-bar'>
|
||
<View className='bill-pay-bar__info'>
|
||
<Text className='bill-pay-bar__label'>账单总额</Text>
|
||
<Text className='bill-pay-bar__amount'>¥{bill.total_amount}</Text>
|
||
</View>
|
||
<View className='bill-pay-bar__btn' onClick={goPay}>去付款</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|