账单
This commit is contained in:
+2
-2
@@ -6,8 +6,8 @@ export default defineAppConfig({
|
||||
'pages/message/index',
|
||||
'pages/profile/index',
|
||||
'pages/order-list/index',
|
||||
'pages/statement/index',
|
||||
'pages/statement-detail/index',
|
||||
'pages/bill/index',
|
||||
'pages/bill-detail/index',
|
||||
'pages/settings/index',
|
||||
'pages/login/index',
|
||||
'pages/register/index',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.bill-detail {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding: 20rpx 24rpx 120rpx;
|
||||
padding: 20rpx 24rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.bill-card {
|
||||
@@ -26,13 +26,20 @@
|
||||
&__status {
|
||||
font-size: 24rpx;
|
||||
|
||||
&--unpaid {
|
||||
color: #ee0a24;
|
||||
}
|
||||
// 0 待支付 / 1 审核中 / 2 已支付
|
||||
&--0 { color: #ee0a24; }
|
||||
&--1 { color: #ff976a; }
|
||||
&--2 { color: #07c160; }
|
||||
}
|
||||
|
||||
&--paid {
|
||||
color: #07c160;
|
||||
}
|
||||
&__tip {
|
||||
display: block;
|
||||
margin: -4rpx 0 16rpx;
|
||||
padding: 12rpx 16rpx;
|
||||
background: #fffbe8;
|
||||
border-radius: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ed6a0c;
|
||||
}
|
||||
|
||||
&__row {
|
||||
@@ -51,15 +58,15 @@
|
||||
&__label {
|
||||
font-size: 26rpx;
|
||||
color: #969799;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 26rpx;
|
||||
color: #323233;
|
||||
|
||||
&--credit {
|
||||
color: #07c160;
|
||||
}
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__total {
|
||||
@@ -118,6 +125,7 @@
|
||||
&__side {
|
||||
text-align: right;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__qty {
|
||||
@@ -145,35 +153,112 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__no {
|
||||
font-size: 26rpx;
|
||||
color: #323233;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 22rpx;
|
||||
color: #969799;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
margin: 0 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-size: 26rpx;
|
||||
color: #323233;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bill-pay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
// ===== 订单明细弹层 =====
|
||||
.order-popup {
|
||||
padding: 32rpx 32rpx 24rpx;
|
||||
|
||||
&__btn {
|
||||
border: none;
|
||||
&__title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
margin-top: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__status {
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
&__list {
|
||||
max-height: 480rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f2f3f5;
|
||||
|
||||
&-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 28rpx;
|
||||
color: #323233;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-spec {
|
||||
margin-top: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&-amount {
|
||||
font-size: 28rpx;
|
||||
color: #323233;
|
||||
font-weight: 500;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: 24rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__total {
|
||||
font-size: 30rpx;
|
||||
color: #ee0a24;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useRouter } from '@tarojs/taro'
|
||||
import { View, Text, 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 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
|
||||
}
|
||||
}, [])
|
||||
|
||||
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
|
||||
|
||||
return (
|
||||
<View className='bill-detail'>
|
||||
{/* ===== 账单信息 ===== */}
|
||||
<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_num}×¥{bill.box_price},托盘 {bill.tray_num}×¥{bill.tray_price})
|
||||
</Text>
|
||||
<Text className='bill-card__value'>¥{bill.added_amount}</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>
|
||||
<Text className='bill-section__desc'>同一商品跨订单合并,单价为加权平均价</Text>
|
||||
{(items ?? []).map(item => (
|
||||
<View key={item.product_id} className='bill-goods'>
|
||||
<View className='bill-goods__main'>
|
||||
<Text className='bill-goods__name'>{item.product_name}</Text>
|
||||
<Text className='bill-goods__spec'>
|
||||
{item.product_spec ? `${item.product_spec} ` : ''}¥{item.price}/{item.unit}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='bill-goods__side'>
|
||||
<Text className='bill-goods__qty'>×{item.quantity}</Text>
|
||||
<Text className='bill-goods__amount'>¥{item.amount}</Text>
|
||||
</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'>
|
||||
{item.product_spec ? `${item.product_spec} ` : ''}¥{item.price}/{item.unit} × {item.quantity}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className='order-popup__item-amount'>¥{item.amount}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
<View className='order-popup__footer'>
|
||||
<Text className='order-popup__total'>合计 ¥{orderDetail.total_amount}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</Popup>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
.bill-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding: 20rpx 24rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
// ===== 待支付汇总 =====
|
||||
.bill-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: linear-gradient(135deg, #ee0a24, #ff4d4f);
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
color: #fff;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&__count {
|
||||
margin-top: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 状态筛选 =====
|
||||
.status-scroll {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.status-chip {
|
||||
display: inline-flex;
|
||||
padding: 12rpx 28rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #fff;
|
||||
font-size: 26rpx;
|
||||
color: #646566;
|
||||
|
||||
&.active {
|
||||
background: #ee0a24;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.bill-empty {
|
||||
padding-top: 120rpx;
|
||||
|
||||
&__btn {
|
||||
margin-top: 24rpx;
|
||||
padding: 14rpx 60rpx;
|
||||
background: #ee0a24;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.bill-loading {
|
||||
padding: 30rpx 0;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #c8c9cc;
|
||||
}
|
||||
|
||||
// ===== 账单单项 =====
|
||||
.bill-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__no {
|
||||
font-size: 28rpx;
|
||||
color: #323233;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 24rpx;
|
||||
|
||||
// 0 待支付 / 1 审核中 / 2 已支付
|
||||
&--0 { color: #ee0a24; }
|
||||
&--1 { color: #ff976a; }
|
||||
&--2 { color: #07c160; }
|
||||
}
|
||||
|
||||
&__body {
|
||||
margin-top: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__purchase {
|
||||
margin-top: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: #c8c9cc;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-size: 32rpx;
|
||||
color: #323233;
|
||||
font-weight: 600;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__settle {
|
||||
font-size: 22rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__paid {
|
||||
font-size: 22rpx;
|
||||
color: #07c160;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import Taro, { useDidShow, useReachBottom } from '@tarojs/taro'
|
||||
import { View, Text, ScrollView } from '@tarojs/components'
|
||||
import { Empty } from '@antmjs/vantui'
|
||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||
import { getBillListApi } from '@/services/bill'
|
||||
import type { Bill, BillSummary } from '@/services/bill'
|
||||
import './index.less'
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
/** 状态筛选(接口 status 为原始支付状态:0 未支付含审核中 / 1 已支付) */
|
||||
const STATUS_FILTERS: Array<{ value: 0 | 1 | undefined; label: string }> = [
|
||||
{ value: undefined, label: '全部' },
|
||||
{ value: 0, label: '未支付' },
|
||||
{ value: 1, label: '已支付' },
|
||||
]
|
||||
|
||||
/**
|
||||
* 账单列表页
|
||||
* 采购单完成后由后台按门店生成(只读);头部 summary 为门店口径待支付汇总(含审核中,不受筛选影响)
|
||||
*/
|
||||
export default function BillListPage() {
|
||||
const token = useAuthStore(s => s.token)
|
||||
|
||||
const [status, setStatus] = useState<0 | 1 | undefined>(undefined)
|
||||
const [bills, setBills] = useState<Bill[]>([])
|
||||
const [summary, setSummary] = useState<BillSummary | null>(null)
|
||||
const [page, setPage] = useState(1)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [finished, setFinished] = useState(false)
|
||||
const loadingRef = useRef(false)
|
||||
|
||||
const loggedIn = !!token
|
||||
|
||||
/** 拉取账单列表(summary 每次随响应刷新) */
|
||||
const loadList = useCallback(
|
||||
async (pageNum: number, reset: boolean, statusParam?: 0 | 1) => {
|
||||
if (!loggedIn || loadingRef.current) return
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await getBillListApi({ status: statusParam, page: pageNum, pageSize: PAGE_SIZE })
|
||||
const { data, total, summary: sum } = res.data
|
||||
setBills(prev => (reset ? data : [...prev, ...data]))
|
||||
setSummary(sum)
|
||||
setPage(pageNum)
|
||||
setFinished(pageNum * PAGE_SIZE >= total)
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
} finally {
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
}
|
||||
},
|
||||
[loggedIn],
|
||||
)
|
||||
|
||||
useDidShow(() => {
|
||||
loadList(1, true, status)
|
||||
})
|
||||
|
||||
useReachBottom(() => {
|
||||
if (!finished && !loadingRef.current && loggedIn) {
|
||||
loadList(page + 1, false, status)
|
||||
}
|
||||
})
|
||||
|
||||
/** 切换状态筛选 */
|
||||
const handleStatusTap = useCallback(
|
||||
(value?: 0 | 1) => {
|
||||
setStatus(value)
|
||||
setFinished(false)
|
||||
loadList(1, true, value)
|
||||
},
|
||||
[loadList],
|
||||
)
|
||||
|
||||
const goLogin = useCallback(() => {
|
||||
Taro.navigateTo({ url: '/pages/login/index' })
|
||||
}, [])
|
||||
|
||||
const goDetail = useCallback((id: number) => {
|
||||
Taro.navigateTo({ url: `/pages/bill-detail/index?id=${id}` })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className='bill-page'>
|
||||
{/* ========== 待支付汇总(门店口径,含审核中) ========== */}
|
||||
{loggedIn && summary && summary.unpaid_count > 0 && (
|
||||
<View className='bill-summary'>
|
||||
<View className='bill-summary__info'>
|
||||
<Text className='bill-summary__label'>待支付账单(含审核中)</Text>
|
||||
<Text className='bill-summary__count'>{summary.unpaid_count} 笔</Text>
|
||||
</View>
|
||||
<Text className='bill-summary__amount'>¥{summary.unpaid_amount}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ========== 状态筛选 ========== */}
|
||||
<ScrollView scrollX className='status-scroll'>
|
||||
{STATUS_FILTERS.map(item => (
|
||||
<View
|
||||
key={item.label}
|
||||
className={`status-chip ${status === item.value ? 'active' : ''}`}
|
||||
onClick={() => handleStatusTap(item.value)}
|
||||
>
|
||||
<Text>{item.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
{/* ========== 账单列表 ========== */}
|
||||
{!loggedIn ? (
|
||||
<Empty description='登录后查看账单' className='bill-empty'>
|
||||
<View className='bill-empty__btn' onClick={goLogin}>去登录</View>
|
||||
</Empty>
|
||||
) : bills.length === 0 ? (
|
||||
loading ? (
|
||||
<View className='bill-loading'><Text>加载中...</Text></View>
|
||||
) : (
|
||||
<Empty description='暂无账单' className='bill-empty' />
|
||||
)
|
||||
) : (
|
||||
bills.map(bill => (
|
||||
<View key={bill.id} className='bill-item' onClick={() => goDetail(bill.id)}>
|
||||
<View className='bill-item__header'>
|
||||
<Text className='bill-item__no'>{bill.bill_no}</Text>
|
||||
<Text className={`bill-item__status bill-item__status--${bill.pay_state}`}>
|
||||
{bill.pay_state_name}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='bill-item__body'>
|
||||
<View className='bill-item__meta'>
|
||||
<Text className='bill-item__date'>账单日期 {bill.bill_date}</Text>
|
||||
{bill.purchase && (
|
||||
<Text className='bill-item__purchase'>采购单 {bill.purchase.purchase_no}</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text className='bill-item__amount'>¥{bill.total_amount}</Text>
|
||||
</View>
|
||||
<View className='bill-item__footer'>
|
||||
<Text className='bill-item__settle'>应结算 {bill.settlement_date}</Text>
|
||||
{bill.pay_state === 2 && bill.paid_at && (
|
||||
<Text className='bill-item__paid'>已于 {bill.paid_at} 支付</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
|
||||
{loggedIn && finished && bills.length > 0 && (
|
||||
<View className='bill-loading'><Text>没有更多了</Text></View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -66,7 +66,24 @@
|
||||
|
||||
&__status {
|
||||
font-size: 24rpx;
|
||||
color: #ee0a24;
|
||||
|
||||
// 0 待接单 / 1 已接单 / 2 采购中 / 3 配送中 / 4 已完成 / 9 已取消
|
||||
&--0 { color: #ee0a24; }
|
||||
&--1 { color: #1989fa; }
|
||||
&--2 { color: #1989fa; }
|
||||
&--3 { color: #ff976a; }
|
||||
&--4 { color: #07c160; }
|
||||
&--9 { color: #969799; }
|
||||
}
|
||||
|
||||
&__preview {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #646566;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@@ -105,8 +122,23 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
&__footer {
|
||||
margin-top: 16rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
&__bill {
|
||||
display: inline-flex;
|
||||
padding: 8rpx 24rpx;
|
||||
border: 1rpx solid #1989fa;
|
||||
color: #1989fa;
|
||||
border-radius: 999rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
display: inline-flex;
|
||||
padding: 8rpx 24rpx;
|
||||
border: 1rpx solid #ee0a24;
|
||||
@@ -180,6 +212,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__remark {
|
||||
margin-top: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: #969799;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: 24rpx;
|
||||
display: flex;
|
||||
|
||||
@@ -4,46 +4,46 @@ import { View, Text, ScrollView } from '@tarojs/components'
|
||||
import { Empty, Popup } from '@antmjs/vantui'
|
||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||
import { cancelOrderApi, getOrderDetailApi, getOrderListApi } from '@/services/order'
|
||||
import { ORDER_NAV_ITEMS, ORDER_STATUS_MAP } from '@/types/order'
|
||||
import type { Order, OrderStatus } from '@/types/order'
|
||||
import { ORDER_STATUS_FILTERS, ORDER_STATUS_TEXT } from '@/types/order'
|
||||
import type { OrderDetail, OrderListItem, OrderStatus } from '@/types/order'
|
||||
import './index.less'
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
/**
|
||||
* 订单列表页(框架)
|
||||
* 入口:/pages/order-list/index?status=0|1|2|3|9|all
|
||||
* status 与 ORDER_NAV_ITEMS 映射(业务语言 → 后端枚举),缺省为全部
|
||||
* 订单列表页
|
||||
* 入口:/pages/order-list/index?status=0|1|2|3|4|9|all
|
||||
* 状态文案一律使用接口返回的 status_name(详情接口无该字段时沿用列表行)
|
||||
*/
|
||||
export default function OrderListPage() {
|
||||
const router = useRouter()
|
||||
const token = useAuthStore(s => s.token)
|
||||
|
||||
/** 当前状态筛选(undefined = 全部) */
|
||||
const [status, setStatus] = useState<number | undefined>(undefined)
|
||||
const [orders, setOrders] = useState<Order[]>([])
|
||||
const [status, setStatus] = useState<OrderStatus | undefined>(undefined)
|
||||
const [orders, setOrders] = useState<OrderListItem[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
const [total, setTotal] = useState(0)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [finished, setFinished] = useState(false)
|
||||
const loadingRef = useRef(false)
|
||||
|
||||
/** 订单详情弹层 */
|
||||
/** 订单详情弹层(statusName 沿用列表行,详情接口不返回 status_name) */
|
||||
const [showDetail, setShowDetail] = useState(false)
|
||||
const [orderDetail, setOrderDetail] = useState<Order | null>(null)
|
||||
const [orderDetail, setOrderDetail] = useState<OrderDetail | null>(null)
|
||||
const [detailStatusName, setDetailStatusName] = useState('')
|
||||
|
||||
const loggedIn = !!token
|
||||
|
||||
/** 解析路由参数中的 status('all' / 数字 / 缺省 → undefined) */
|
||||
const parseStatus = useCallback((raw?: string): number | undefined => {
|
||||
const parseStatus = useCallback((raw?: string): OrderStatus | undefined => {
|
||||
if (!raw || raw === 'all' || raw === '') return undefined
|
||||
const num = Number(raw)
|
||||
return Number.isNaN(num) ? undefined : num
|
||||
return Number.isNaN(num) ? undefined : (num as OrderStatus)
|
||||
}, [])
|
||||
|
||||
/** 拉取订单列表 */
|
||||
const loadOrders = useCallback(
|
||||
async (pageNum: number, reset: boolean, statusParam?: number) => {
|
||||
async (pageNum: number, reset: boolean, statusParam?: OrderStatus) => {
|
||||
if (!loggedIn || loadingRef.current) return
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
@@ -53,11 +53,10 @@ export default function OrderListPage() {
|
||||
page: pageNum,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const { data, total: totalCount } = res.data
|
||||
const { data, total } = res.data
|
||||
setOrders(prev => (reset ? data : [...prev, ...data]))
|
||||
setTotal(totalCount)
|
||||
setPage(pageNum)
|
||||
setFinished(pageNum * PAGE_SIZE >= totalCount)
|
||||
setFinished(pageNum * PAGE_SIZE >= total)
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
} finally {
|
||||
@@ -82,7 +81,7 @@ export default function OrderListPage() {
|
||||
|
||||
/** 切换状态筛选 */
|
||||
const handleStatusTap = useCallback(
|
||||
(value?: number) => {
|
||||
(value?: OrderStatus) => {
|
||||
setStatus(value)
|
||||
setFinished(false)
|
||||
loadOrders(1, true, value)
|
||||
@@ -91,19 +90,20 @@ export default function OrderListPage() {
|
||||
)
|
||||
|
||||
/** 查看订单详情 */
|
||||
const handleOrderTap = useCallback(async (order: Order) => {
|
||||
const handleOrderTap = useCallback(async (order: OrderListItem) => {
|
||||
try {
|
||||
const res = await getOrderDetailApi(order.id)
|
||||
setOrderDetail(res.data)
|
||||
setDetailStatusName(order.status_name)
|
||||
setShowDetail(true)
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
}
|
||||
}, [])
|
||||
|
||||
/** 取消订单(仅待汇总可取消) */
|
||||
/** 取消订单(仅待接单可取消,以 can_cancel 为准) */
|
||||
const handleCancelOrder = useCallback(
|
||||
(order: Order) => {
|
||||
(order: OrderListItem) => {
|
||||
Taro.showModal({
|
||||
title: '取消订单',
|
||||
content: `确定取消订单 ${order.order_no} 吗?`,
|
||||
@@ -123,6 +123,11 @@ export default function OrderListPage() {
|
||||
[loadOrders, status],
|
||||
)
|
||||
|
||||
/** 跳转关联账单详情 */
|
||||
const handleBillTap = useCallback((billId: number) => {
|
||||
Taro.navigateTo({ url: `/pages/bill-detail/index?id=${billId}` })
|
||||
}, [])
|
||||
|
||||
const goLogin = useCallback(() => {
|
||||
Taro.navigateTo({ url: '/pages/login/index' })
|
||||
}, [])
|
||||
@@ -131,17 +136,11 @@ export default function OrderListPage() {
|
||||
<View className='order-list-page'>
|
||||
{/* ========== 状态筛选 ========== */}
|
||||
<ScrollView scrollX className='status-scroll'>
|
||||
<View
|
||||
className={`status-chip ${status === undefined ? 'active' : ''}`}
|
||||
onClick={() => handleStatusTap(undefined)}
|
||||
>
|
||||
<Text>全部</Text>
|
||||
</View>
|
||||
{ORDER_NAV_ITEMS.map(item => (
|
||||
{ORDER_STATUS_FILTERS.map(item => (
|
||||
<View
|
||||
key={item.key}
|
||||
className={`status-chip ${status === item.status ? 'active' : ''}`}
|
||||
onClick={() => handleStatusTap(item.status)}
|
||||
key={item.label}
|
||||
className={`status-chip ${status === item.value ? 'active' : ''}`}
|
||||
onClick={() => handleStatusTap(item.value)}
|
||||
>
|
||||
<Text>{item.label}</Text>
|
||||
</View>
|
||||
@@ -164,25 +163,47 @@ export default function OrderListPage() {
|
||||
<View key={order.id} className='order-item' onClick={() => handleOrderTap(order)}>
|
||||
<View className='order-item__header'>
|
||||
<Text className='order-item__no'>{order.order_no}</Text>
|
||||
<Text className='order-item__status'>{ORDER_STATUS_MAP[order.status]}</Text>
|
||||
<Text className={`order-item__status order-item__status--${order.status}`}>
|
||||
{order.status_name}
|
||||
</Text>
|
||||
</View>
|
||||
{/* 商品预览(仅前 3 条,完整明细见详情) */}
|
||||
<Text className='order-item__preview'>
|
||||
{order.items.map(i => `${i.product_name}×${i.quantity}`).join('、')}
|
||||
{order.item_count > 3 ? ` 等${order.item_count}种` : ''}
|
||||
</Text>
|
||||
<View className='order-item__body'>
|
||||
<Text className='order-item__date'>{order.order_date}</Text>
|
||||
<Text className='order-item__date'>订货日期 {order.order_date}</Text>
|
||||
<View className='order-item__amounts'>
|
||||
<Text className='order-item__qty'>共 {order.total_quantity} 件</Text>
|
||||
<Text className='order-item__amount'>¥{order.total_amount}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{order.remark && <Text className='order-item__remark'>备注:{order.remark}</Text>}
|
||||
{order.status === 0 && (
|
||||
<View
|
||||
className='order-item__cancel'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleCancelOrder(order)
|
||||
}}
|
||||
>
|
||||
取消订单
|
||||
{(order.can_cancel || order.bill_id > 0) && (
|
||||
<View className='order-item__footer'>
|
||||
{order.bill_id > 0 && (
|
||||
<View
|
||||
className='order-item__bill'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleBillTap(order.bill_id)
|
||||
}}
|
||||
>
|
||||
查看账单
|
||||
</View>
|
||||
)}
|
||||
{order.can_cancel && (
|
||||
<View
|
||||
className='order-item__cancel'
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
handleCancelOrder(order)
|
||||
}}
|
||||
>
|
||||
取消订单
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
@@ -207,8 +228,10 @@ export default function OrderListPage() {
|
||||
<View className='detail-popup'>
|
||||
<Text className='detail-popup__title'>{orderDetail.order_no}</Text>
|
||||
<View className='detail-popup__meta'>
|
||||
<Text>{orderDetail.order_date}</Text>
|
||||
<Text className='detail-popup__status'>{ORDER_STATUS_MAP[orderDetail.status]}</Text>
|
||||
<Text>{orderDetail.order_date} 下单 {orderDetail.created_at}</Text>
|
||||
<Text className='detail-popup__status'>
|
||||
{detailStatusName || ORDER_STATUS_TEXT[orderDetail.status] || ''}
|
||||
</Text>
|
||||
</View>
|
||||
<ScrollView scrollY className='detail-popup__list'>
|
||||
{(orderDetail.items ?? []).map(item => (
|
||||
@@ -216,13 +239,16 @@ export default function OrderListPage() {
|
||||
<View className='detail-popup__item-info'>
|
||||
<Text className='detail-popup__item-name'>{item.product_name}</Text>
|
||||
<Text className='detail-popup__item-spec'>
|
||||
{item.product_spec} ¥{item.price} × {item.quantity}
|
||||
{item.product_spec ? `${item.product_spec} ` : ''}¥{item.price}/{item.unit} × {item.quantity}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className='detail-popup__item-amount'>¥{item.amount}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
{orderDetail.remark && (
|
||||
<Text className='detail-popup__remark'>备注:{orderDetail.remark}</Text>
|
||||
)}
|
||||
<View className='detail-popup__footer'>
|
||||
<Text className='detail-popup__total'>合计 ¥{orderDetail.total_amount}</Text>
|
||||
</View>
|
||||
|
||||
@@ -144,6 +144,70 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 账单入口 =====
|
||||
.bill-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__icon {
|
||||
width: 84rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #fff0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 28rpx;
|
||||
color: #323233;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
margin-top: 6rpx;
|
||||
font-size: 22rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__badge {
|
||||
min-width: 36rpx;
|
||||
height: 36rpx;
|
||||
padding: 0 10rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #ee0a24;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
font-size: 32rpx;
|
||||
color: #c8c9cc;
|
||||
line-height: 1;
|
||||
margin-left: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 功能菜单 =====
|
||||
.menu-cell {
|
||||
display: flex;
|
||||
|
||||
+46
-14
@@ -1,28 +1,18 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import { Icon } from '@antmjs/vantui'
|
||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||
import { getUserInfoApi } from '@/services/auth'
|
||||
import { getBillListApi } from '@/services/bill'
|
||||
import type { BillSummary } from '@/services/bill'
|
||||
import { ORDER_NAV_ITEMS } from '@/types/order'
|
||||
import { resolveAvatarUrl } from '@/utils/format'
|
||||
import type { UserType } from '@/types/user'
|
||||
import './index.less'
|
||||
|
||||
/** 菜单项(后续单独页面开发时在此追加) */
|
||||
/** 菜单项(订单/账单入口已由上方专区承载,后续单独页面开发时在此追加) */
|
||||
const MENU_ITEMS = [
|
||||
{
|
||||
key: 'orders',
|
||||
label: '所有订单',
|
||||
icon: 'orders-o',
|
||||
onClick: () => Taro.navigateTo({ url: '/pages/order-list/index?status=all' }),
|
||||
},
|
||||
{
|
||||
key: 'statement',
|
||||
label: '账单',
|
||||
icon: 'balance-list-o',
|
||||
onClick: () => Taro.navigateTo({ url: '/pages/statement/index' }),
|
||||
},
|
||||
{
|
||||
key: 'message',
|
||||
label: '消息中心',
|
||||
@@ -43,6 +33,9 @@ export default function ProfilePage() {
|
||||
const setUser = useAuthStore(s => s.setUser)
|
||||
const logout = useAuthStore(s => s.logout)
|
||||
|
||||
/** 账单待支付汇总(门店口径,含审核中) */
|
||||
const [billSummary, setBillSummary] = useState<BillSummary | null>(null)
|
||||
|
||||
const loggedIn = !!token && !!user
|
||||
|
||||
/** 功能菜单(门店账号追加「门店信息」入口) */
|
||||
@@ -65,6 +58,10 @@ export default function ProfilePage() {
|
||||
getUserInfoApi()
|
||||
.then(res => setUser(res.data))
|
||||
.catch(() => {})
|
||||
// 刷新账单待支付汇总(pageSize=1 仅取 summary)
|
||||
getBillListApi({ page: 1, pageSize: 1 })
|
||||
.then(res => setBillSummary(res.data.summary))
|
||||
.catch(() => {})
|
||||
})
|
||||
|
||||
/** 身份标签 */
|
||||
@@ -79,6 +76,11 @@ export default function ProfilePage() {
|
||||
Taro.navigateTo({ url: `/pages/order-list/index?status=${status ?? 'all'}` })
|
||||
}, [])
|
||||
|
||||
/** 账单入口 → 账单列表页 */
|
||||
const goBill = useCallback(() => {
|
||||
Taro.navigateTo({ url: '/pages/bill/index' })
|
||||
}, [])
|
||||
|
||||
/** 退出登录 */
|
||||
const handleLogout = useCallback(() => {
|
||||
Taro.showModal({
|
||||
@@ -169,6 +171,36 @@ export default function ProfilePage() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ========== 我的账单 ========== */}
|
||||
<View className='profile-section'>
|
||||
<View className='profile-section__header'>
|
||||
<Text className='profile-section__title'>我的账单</Text>
|
||||
<Text className='profile-section__more' onClick={goBill}>全部 ›</Text>
|
||||
</View>
|
||||
<View className='bill-entry' onClick={goBill}>
|
||||
<View className='bill-entry__icon'>
|
||||
<Icon name='balance-list-o' size={28} color='#ee0a24' />
|
||||
</View>
|
||||
{loggedIn && billSummary && billSummary.unpaid_count > 0 ? (
|
||||
<>
|
||||
<View className='bill-entry__info'>
|
||||
<Text className='bill-entry__amount'>¥{billSummary.unpaid_amount}</Text>
|
||||
<Text className='bill-entry__desc'>待支付 {billSummary.unpaid_count} 笔(含审核中)</Text>
|
||||
</View>
|
||||
<View className='bill-entry__badge'>{billSummary.unpaid_count}</View>
|
||||
</>
|
||||
) : (
|
||||
<View className='bill-entry__info'>
|
||||
<Text className='bill-entry__title'>
|
||||
{loggedIn ? '暂无待支付账单' : '登录后查看账单'}
|
||||
</Text>
|
||||
<Text className='bill-entry__desc'>账单由后台按采购单自动生成</Text>
|
||||
</View>
|
||||
)}
|
||||
<Text className='bill-entry__arrow'>›</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ========== 功能菜单 ========== */}
|
||||
<View className='profile-section profile-section--menu'>
|
||||
{menuItems.map(item => (
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Taro, { useRouter } from '@tarojs/taro'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { Button, Empty } from '@antmjs/vantui'
|
||||
import { getStatementDetailApi, payStatementApi } from '@/services/statement'
|
||||
import { STATEMENT_STATUS_MAP } from '@/types/statement'
|
||||
import type { StatementDetail } from '@/types/statement'
|
||||
import './index.less'
|
||||
|
||||
/**
|
||||
* 账单详情
|
||||
* 商品汇总(金额直接合并,单价明细见订单记录)+ 回筐计费 + 关联订单 + 付款确认
|
||||
*/
|
||||
export default function StatementDetailPage() {
|
||||
const router = useRouter()
|
||||
const id = Number(router.params.id ?? 0)
|
||||
|
||||
const [detail, setDetail] = useState<StatementDetail | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [paying, setPaying] = useState(false)
|
||||
|
||||
const loadDetail = useCallback(async () => {
|
||||
if (!id) return
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await getStatementDetailApi(id)
|
||||
setDetail(res.data)
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [id])
|
||||
|
||||
useEffect(() => {
|
||||
loadDetail()
|
||||
}, [loadDetail])
|
||||
|
||||
/** 付款确认 */
|
||||
const handlePay = useCallback(async () => {
|
||||
if (!detail || paying) return
|
||||
const confirm = await Taro.showModal({
|
||||
title: '付款确认',
|
||||
content: `确认已支付账单 ${detail.statement_no} 共 ¥${detail.total_amount}?`,
|
||||
confirmText: '确认付款',
|
||||
})
|
||||
if (!confirm.confirm) return
|
||||
setPaying(true)
|
||||
try {
|
||||
await payStatementApi(detail.id)
|
||||
Taro.showToast({ title: '付款确认成功', icon: 'success' })
|
||||
loadDetail()
|
||||
} catch {
|
||||
// 错误(重复付款等)已由 request 层 toast
|
||||
} finally {
|
||||
setPaying(false)
|
||||
}
|
||||
}, [detail, paying, loadDetail])
|
||||
|
||||
if (loading && !detail) {
|
||||
return <View className='bill-detail'><Empty description='加载中...' /></View>
|
||||
}
|
||||
if (!detail) {
|
||||
return <View className='bill-detail'><Empty description='账单不存在' /></View>
|
||||
}
|
||||
|
||||
const crateNegative = Number(detail.crate_amount) < 0
|
||||
|
||||
return (
|
||||
<View className='bill-detail'>
|
||||
{/* ===== 账单信息 ===== */}
|
||||
<View className='bill-card'>
|
||||
<View className='bill-card__header'>
|
||||
<Text className='bill-card__no'>{detail.statement_no}</Text>
|
||||
<Text
|
||||
className={detail.status === 0 ? 'bill-card__status bill-card__status--unpaid' : 'bill-card__status bill-card__status--paid'}
|
||||
>
|
||||
{STATEMENT_STATUS_MAP[detail.status]}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='bill-card__row'>
|
||||
<Text className='bill-card__label'>账单周期</Text>
|
||||
<Text className='bill-card__value'>{detail.period_start} ~ {detail.period_end}</Text>
|
||||
</View>
|
||||
<View className='bill-card__row'>
|
||||
<Text className='bill-card__label'>应结算日期</Text>
|
||||
<Text className='bill-card__value'>{detail.settlement_date ?? '-'}</Text>
|
||||
</View>
|
||||
{detail.status === 1 && detail.settled_at && (
|
||||
<View className='bill-card__row'>
|
||||
<Text className='bill-card__label'>付款时间</Text>
|
||||
<Text className='bill-card__value'>{detail.settled_at}</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='bill-card__row'>
|
||||
<Text className='bill-card__label'>商品金额</Text>
|
||||
<Text className='bill-card__value'>¥{detail.goods_amount}</Text>
|
||||
</View>
|
||||
<View className='bill-card__row'>
|
||||
<Text className='bill-card__label'>回筐金额</Text>
|
||||
<Text className={crateNegative ? 'bill-card__value bill-card__value--credit' : 'bill-card__value'}>
|
||||
{crateNegative ? `-¥${Math.abs(Number(detail.crate_amount)).toFixed(2)}` : `¥${detail.crate_amount}`}
|
||||
{crateNegative ? '(抵扣)' : ''}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='bill-card__row bill-card__row--total'>
|
||||
<Text className='bill-card__label'>账单总额</Text>
|
||||
<Text className='bill-card__total'>¥{detail.total_amount}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ===== 商品汇总 ===== */}
|
||||
<View className='bill-card'>
|
||||
<Text className='bill-section__title'>商品汇总({detail.goods?.length ?? 0})</Text>
|
||||
<Text className='bill-section__desc'>同一商品多次订单金额直接合并;单价明细请查看订单记录</Text>
|
||||
{(detail.goods ?? []).map((item, index) => (
|
||||
<View key={index} className='bill-goods'>
|
||||
<View className='bill-goods__main'>
|
||||
<Text className='bill-goods__name'>{item.product_name}</Text>
|
||||
<Text className='bill-goods__spec'>{item.product_spec || ''}</Text>
|
||||
</View>
|
||||
<View className='bill-goods__side'>
|
||||
<Text className='bill-goods__qty'>{item.quantity} {item.unit}</Text>
|
||||
<Text className='bill-goods__amount'>¥{item.amount}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
{(detail.goods ?? []).length === 0 && <Empty description='暂无商品记录' />}
|
||||
</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'>{detail.box_num} 个{detail.box_num < 0 ? '(抵扣)' : ''}</Text>
|
||||
</View>
|
||||
<View className='bill-card__row'>
|
||||
<Text className='bill-card__label'>托盘</Text>
|
||||
<Text className='bill-card__value'>{detail.tray_num} 个{detail.tray_num < 0 ? '(抵扣)' : ''}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ===== 关联订单 ===== */}
|
||||
<View className='bill-card'>
|
||||
<Text className='bill-section__title'>关联订单({detail.orders?.length ?? 0})</Text>
|
||||
{(detail.orders ?? []).map(order => (
|
||||
<View
|
||||
key={order.id}
|
||||
className='bill-order'
|
||||
onClick={() => Taro.navigateTo({ url: `/pages/order-list/index` })}
|
||||
>
|
||||
<Text className='bill-order__no'>{order.order_no}</Text>
|
||||
<Text className='bill-order__date'>{order.order_date}</Text>
|
||||
<Text className='bill-order__amount'>¥{order.total_amount}</Text>
|
||||
</View>
|
||||
))}
|
||||
{(detail.orders ?? []).length === 0 && <Empty description='暂无订单记录' />}
|
||||
</View>
|
||||
|
||||
{/* ===== 付款按钮 ===== */}
|
||||
{detail.status === 0 && (
|
||||
<View className='bill-pay'>
|
||||
<Button
|
||||
type='danger'
|
||||
block
|
||||
round
|
||||
loading={paying}
|
||||
className='bill-pay__btn'
|
||||
onClick={handlePay}
|
||||
>
|
||||
付款(¥{detail.total_amount})
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
.statement-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding: 20rpx 24rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
// ===== 头部 =====
|
||||
.statement-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8rpx 8rpx 24rpx;
|
||||
|
||||
&__title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__tip {
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
}
|
||||
|
||||
.statement-empty {
|
||||
padding-top: 120rpx;
|
||||
|
||||
&__btn {
|
||||
margin-top: 24rpx;
|
||||
padding: 14rpx 60rpx;
|
||||
background: #ee0a24;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.statement-loading {
|
||||
padding: 30rpx 0;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #c8c9cc;
|
||||
}
|
||||
|
||||
// ===== 账单单项 =====
|
||||
.statement-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__no {
|
||||
font-size: 28rpx;
|
||||
color: #323233;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 24rpx;
|
||||
|
||||
&--unpaid {
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
&--paid {
|
||||
color: #07c160;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
margin-top: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__period {
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__amount {
|
||||
font-size: 28rpx;
|
||||
color: #323233;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__settle {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #969799;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import Taro, { useDidShow, useReachBottom } from '@tarojs/taro'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { Empty } from '@antmjs/vantui'
|
||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||
import { getStatementListApi } from '@/services/statement'
|
||||
import { STATEMENT_STATUS_MAP } from '@/types/statement'
|
||||
import type { Statement } from '@/types/statement'
|
||||
import './index.less'
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
/**
|
||||
* 账单页
|
||||
* 账单按门店回款周期自动生成:列表 + 查看详情 + 付款
|
||||
*/
|
||||
export default function StatementPage() {
|
||||
const token = useAuthStore(s => s.token)
|
||||
|
||||
const [statements, setStatements] = useState<Statement[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [finished, setFinished] = useState(false)
|
||||
const loadingRef = useRef(false)
|
||||
|
||||
const loggedIn = !!token
|
||||
|
||||
/** 拉取账单列表 */
|
||||
const loadList = useCallback(
|
||||
async (pageNum: number, reset: boolean) => {
|
||||
if (!loggedIn || loadingRef.current) return
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await getStatementListApi({ page: pageNum, pageSize: PAGE_SIZE })
|
||||
const { data, total } = res.data
|
||||
setStatements(prev => (reset ? data : [...prev, ...data]))
|
||||
setPage(pageNum)
|
||||
setFinished(pageNum * PAGE_SIZE >= total)
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
} finally {
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
}
|
||||
},
|
||||
[loggedIn],
|
||||
)
|
||||
|
||||
useDidShow(() => {
|
||||
loadList(1, true)
|
||||
})
|
||||
|
||||
useReachBottom(() => {
|
||||
if (!finished && !loadingRef.current && loggedIn) {
|
||||
loadList(page + 1, false)
|
||||
}
|
||||
})
|
||||
|
||||
const goLogin = useCallback(() => {
|
||||
Taro.navigateTo({ url: '/pages/login/index' })
|
||||
}, [])
|
||||
|
||||
const goDetail = useCallback((id: number) => {
|
||||
Taro.navigateTo({ url: `/pages/statement-detail/index?id=${id}` })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className='statement-page'>
|
||||
{/* ========== 头部 ========== */}
|
||||
<View className='statement-header'>
|
||||
<Text className='statement-header__title'>账单</Text>
|
||||
<Text className='statement-header__tip'>按回款周期自动生成</Text>
|
||||
</View>
|
||||
|
||||
{/* ========== 列表 ========== */}
|
||||
{!loggedIn ? (
|
||||
<Empty description='登录后查看账单' className='statement-empty'>
|
||||
<View className='statement-empty__btn' onClick={goLogin}>去登录</View>
|
||||
</Empty>
|
||||
) : statements.length === 0 ? (
|
||||
loading ? (
|
||||
<View className='statement-loading'><Text>加载中...</Text></View>
|
||||
) : (
|
||||
<Empty description='暂无账单' className='statement-empty' />
|
||||
)
|
||||
) : (
|
||||
statements.map(st => (
|
||||
<View key={st.id} className='statement-item' onClick={() => goDetail(st.id)}>
|
||||
<View className='statement-item__header'>
|
||||
<Text className='statement-item__no'>{st.statement_no}</Text>
|
||||
<Text
|
||||
className={st.status === 0 ? 'statement-item__status statement-item__status--unpaid' : 'statement-item__status statement-item__status--paid'}
|
||||
>
|
||||
{STATEMENT_STATUS_MAP[st.status]}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='statement-item__body'>
|
||||
<Text className='statement-item__period'>
|
||||
{st.period_start} ~ {st.period_end}
|
||||
</Text>
|
||||
<Text className='statement-item__amount'>¥{st.total_amount}</Text>
|
||||
</View>
|
||||
{st.settlement_date && (
|
||||
<Text className='statement-item__settle'>应结算:{st.settlement_date}</Text>
|
||||
)}
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
|
||||
{loggedIn && finished && statements.length > 0 && (
|
||||
<View className='statement-loading'><Text>没有更多了</Text></View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { get } from '@/utils/request'
|
||||
import type { PaginatedData } from '@/types/api'
|
||||
|
||||
/** 账单支付进度:0 待支付 / 1 审核中 / 2 已支付(由后端推导,展示以此为准) */
|
||||
export type BillPayState = 0 | 1 | 2
|
||||
|
||||
/** 账单(列表行与详情的 bill 字段一致) */
|
||||
export interface Bill {
|
||||
id: number
|
||||
/** 账单编号(ZD 前缀) */
|
||||
bill_no: string
|
||||
/** 账单日期(出账日,Y-m-d) */
|
||||
bill_date: string
|
||||
/** 关联采购单 ID */
|
||||
purchase_id: number
|
||||
/** 关联采购单 */
|
||||
purchase: { id: number; purchase_no: string; purchase_date: string } | null
|
||||
/** 商品金额(订单汇总快照) */
|
||||
product_amount: string
|
||||
/** 配送费 */
|
||||
delivery_fee: string
|
||||
/** 周转筐 / 周转托盘数量 */
|
||||
box_num: number
|
||||
tray_num: number
|
||||
/** 筐 / 托盘单价(出账时快照) */
|
||||
box_price: string
|
||||
tray_price: string
|
||||
/** 附加金额 = box_num×box_price + tray_num×tray_price */
|
||||
added_amount: string
|
||||
/** 账单总金额 = 商品金额 + 配送费 + 附加金额 */
|
||||
total_amount: string
|
||||
/** 原始支付状态:0 未支付 / 1 已支付(展示用 pay_state 系列字段) */
|
||||
status: 0 | 1
|
||||
status_name: string
|
||||
/** 支付进度:0 待支付 / 1 审核中 / 2 已支付 */
|
||||
pay_state: BillPayState
|
||||
/** 支付进度中文名(列表状态标签直接用它) */
|
||||
pay_state_name: string
|
||||
/** 是否可发起合并付款(=待支付) */
|
||||
can_pay: boolean
|
||||
/** 关联支付记录 ID,0=未发起付款 */
|
||||
payment_id: number
|
||||
/** 应结算日期 = 账单日期 + 门店回款周期天数 */
|
||||
settlement_date: string
|
||||
/** 付款时间(已支付时非空) */
|
||||
paid_at: string | null
|
||||
/** 付款备注 */
|
||||
pay_remark: string
|
||||
/** 账单备注 */
|
||||
remark: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
/** 待支付汇总(门店口径,含审核中,不受筛选参数影响) */
|
||||
export interface BillSummary {
|
||||
unpaid_count: number
|
||||
unpaid_amount: string
|
||||
}
|
||||
|
||||
/** 账单合并商品明细(跨本账单全部订单按商品聚合) */
|
||||
export interface BillItem {
|
||||
product_id: number
|
||||
product_name: string
|
||||
product_spec: string
|
||||
unit: string
|
||||
/** 加权平均单价(Σ金额÷Σ数量) */
|
||||
price: string
|
||||
/** 合计数量 */
|
||||
quantity: number
|
||||
weight: string
|
||||
/** 合计金额 */
|
||||
amount: string
|
||||
}
|
||||
|
||||
/** 账单关联订单 */
|
||||
export interface BillOrder {
|
||||
id: number
|
||||
order_no: string
|
||||
order_date: string
|
||||
total_quantity: number
|
||||
total_weight: string
|
||||
total_amount: string
|
||||
/** 订单状态枚举(0/1/2/3/4/9) */
|
||||
status: number
|
||||
}
|
||||
|
||||
/** 账单详情 */
|
||||
export interface BillDetail {
|
||||
bill: Bill
|
||||
items: BillItem[]
|
||||
orders: BillOrder[]
|
||||
}
|
||||
|
||||
/** 账单列表查询参数 */
|
||||
export interface BillListParams {
|
||||
/** 原始支付状态:0 未支付(含审核中)/ 1 已支付,不传=全部 */
|
||||
status?: 0 | 1
|
||||
/** 传 1 = 仅可发起付款的账单(合并付款选择页专用) */
|
||||
payable?: 1
|
||||
/** 账单日期起(Y-m-d) */
|
||||
start_date?: string
|
||||
/** 账单日期止(Y-m-d),不得早于 start_date */
|
||||
end_date?: string
|
||||
page?: number
|
||||
/** 每页数量,默认 10,最大 50 */
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
/** 账单列表(附加 summary 待支付汇总):GET /mini/bill */
|
||||
export function getBillListApi(params: BillListParams = {}) {
|
||||
return get<PaginatedData<Bill> & { summary: BillSummary }>('/mini/bill', { data: params })
|
||||
}
|
||||
|
||||
/** 账单详情(校验本店归属):GET /mini/bill/{id} */
|
||||
export function getBillDetailApi(id: number) {
|
||||
return get<BillDetail>(`/mini/bill/${id}`)
|
||||
}
|
||||
+20
-14
@@ -1,6 +1,6 @@
|
||||
import { get, post, put } from '@/utils/request'
|
||||
import type { PaginatedData } from '@/types/api'
|
||||
import type { Order, OrderCreateResult, OrderSummary } from '@/types/order'
|
||||
import type { OrderCreateResult, OrderDetail, OrderListItem, OrderStatus } from '@/types/order'
|
||||
|
||||
/** 下单明细行(金额一律服务端重算,前端不传金额) */
|
||||
export interface OrderItemParam {
|
||||
@@ -15,29 +15,35 @@ export interface CreateOrderParams {
|
||||
remark?: string
|
||||
}
|
||||
|
||||
/** 订单列表查询参数 */
|
||||
export interface OrderListParams {
|
||||
/** 订单状态,不传=全部 */
|
||||
status?: OrderStatus
|
||||
/** 订货日期起(Y-m-d) */
|
||||
start_date?: string
|
||||
/** 订货日期止(Y-m-d),不得早于 start_date */
|
||||
end_date?: string
|
||||
page?: number
|
||||
/** 每页数量,默认 10,最大 50 */
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
/** 下单:POST /mini/order */
|
||||
export function createOrderApi(params: CreateOrderParams) {
|
||||
return post<OrderCreateResult>('/mini/order', params)
|
||||
}
|
||||
|
||||
/** 历史订单列表(强制本店隔离):GET /mini/order */
|
||||
export function getOrderListApi(
|
||||
params: { status?: number; page?: number; pageSize?: number } = {},
|
||||
) {
|
||||
return get<PaginatedData<Order>>('/mini/order', { data: params })
|
||||
/** 历史订单列表(强制本店隔离,按订货日期倒序):GET /mini/order */
|
||||
export function getOrderListApi(params: OrderListParams = {}) {
|
||||
return get<PaginatedData<OrderListItem>>('/mini/order', { data: params })
|
||||
}
|
||||
|
||||
/** 周期汇总:GET /mini/order/summary */
|
||||
export function getOrderSummaryApi(period: 'day' | 'week' | 'month' = 'month') {
|
||||
return get<OrderSummary>('/mini/order/summary', { data: { period } })
|
||||
}
|
||||
|
||||
/** 订单详情(校验本店归属):GET /mini/order/{id} */
|
||||
/** 订单详情(校验本店归属,含完整明细):GET /mini/order/{id} */
|
||||
export function getOrderDetailApi(id: number) {
|
||||
return get<Order>(`/mini/order/${id}`)
|
||||
return get<OrderDetail>(`/mini/order/${id}`)
|
||||
}
|
||||
|
||||
/** 取消订单(仅待汇总可取消):PUT /mini/order/{id}/cancel */
|
||||
/** 取消订单(仅待接单可取消):PUT /mini/order/{id}/cancel */
|
||||
export function cancelOrderApi(id: number) {
|
||||
return put(`/mini/order/${id}/cancel`)
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { get, post } from '@/utils/request'
|
||||
import type { PaginatedData } from '@/types/api'
|
||||
import type { PendingContainers, Statement, StatementDetail } from '@/types/statement'
|
||||
|
||||
/** 账单列表(仅本店,按回款周期自动生成):GET /mini/statement */
|
||||
export function getStatementListApi(params: { page?: number; pageSize?: number } = {}) {
|
||||
return get<PaginatedData<Statement>>('/mini/statement', { data: params })
|
||||
}
|
||||
|
||||
/** 账单详情(校验归属,含商品汇总/回筐计费/关联订单):GET /mini/statement/{id} */
|
||||
export function getStatementDetailApi(id: number) {
|
||||
return get<StatementDetail>(`/mini/statement/${id}`)
|
||||
}
|
||||
|
||||
/** 付款确认:POST /mini/statement/{id}/pay */
|
||||
export function payStatementApi(id: number) {
|
||||
return post<{ id: number; total_amount: string }>(`/mini/statement/${id}/pay`)
|
||||
}
|
||||
|
||||
/** 当前门店待回筐/托盘台账:GET /mini/statement/pending-containers */
|
||||
export function getPendingContainersApi() {
|
||||
return get<PendingContainers>('/mini/statement/pending-containers')
|
||||
}
|
||||
+79
-49
@@ -1,85 +1,115 @@
|
||||
/** 门店订单状态:0 待汇总 / 1 已汇总 / 2 配送中 / 3 已完成 / 9 已取消 */
|
||||
export type OrderStatus = 0 | 1 | 2 | 3 | 9
|
||||
/**
|
||||
* 门店订单状态(新枚举):
|
||||
* 0 待接单 / 1 已接单 / 2 采购中 / 3 配送中 / 4 已完成 / 9 已取消
|
||||
*/
|
||||
export type OrderStatus = 0 | 1 | 2 | 3 | 4 | 9
|
||||
|
||||
export const ORDER_STATUS_MAP: Record<OrderStatus, string> = {
|
||||
0: '待汇总',
|
||||
1: '已汇总',
|
||||
2: '配送中',
|
||||
3: '已完成',
|
||||
/**
|
||||
* 状态文案兜底映射(完整 6 态)
|
||||
* 仅用于接口未返回 status_name 的场景(订单详情、账单关联订单);
|
||||
* 列表展示一律使用接口返回的 status_name,不要再硬编码
|
||||
*/
|
||||
export const ORDER_STATUS_TEXT: Record<number, string> = {
|
||||
0: '待接单',
|
||||
1: '已接单',
|
||||
2: '采购中',
|
||||
3: '配送中',
|
||||
4: '已完成',
|
||||
9: '已取消',
|
||||
}
|
||||
|
||||
/** 状态筛选(value 为 null 表示全部) */
|
||||
export const ORDER_STATUS_FILTERS: Array<{ value: OrderStatus | null; label: string }> = [
|
||||
{ value: null, label: '全部' },
|
||||
{ value: 0, label: '待汇总' },
|
||||
{ value: 1, label: '已汇总' },
|
||||
{ value: 2, label: '配送中' },
|
||||
{ value: 3, label: '已完成' },
|
||||
/** 状态筛选(订单列表页 chips,value 为 undefined 表示全部) */
|
||||
export const ORDER_STATUS_FILTERS: Array<{ value: OrderStatus | undefined; label: string }> = [
|
||||
{ value: undefined, label: '全部' },
|
||||
{ value: 0, label: '待接单' },
|
||||
{ value: 1, label: '已接单' },
|
||||
{ value: 2, label: '采购中' },
|
||||
{ value: 3, label: '配送中' },
|
||||
{ value: 4, label: '已完成' },
|
||||
{ value: 9, label: '已取消' },
|
||||
]
|
||||
|
||||
/**
|
||||
* 我的页「订单总汇」导航(业务语言 → 后端枚举)
|
||||
* status 映射 StoreOrder.status:0 待汇总 / 1 已汇总 / 2 配送中 / 3 已完成 / 9 已取消
|
||||
* 注意:后端当前无「待付款」状态(status 缺省 = 不传参,显示全部),待后端补充后在此对齐
|
||||
*/
|
||||
/** 我的页「订单总汇」导航(status 映射后端订单状态枚举) */
|
||||
export const ORDER_NAV_ITEMS: Array<{ key: string; label: string; status?: number; icon: string }> = [
|
||||
{ key: 'pending_review', label: '待审核', status: 0, icon: 'clock-o' },
|
||||
{ key: 'pending_stock', label: '待配货', status: 1, icon: 'logistics' },
|
||||
{ key: 'stocking', label: '配货中', status: 2, icon: 'van-o' },
|
||||
{ key: 'pending_pay', label: '待付款', icon: 'pending-payment' },
|
||||
{ key: 'completed', label: '已完成', status: 3, icon: 'completed' },
|
||||
{ key: 'all', label: '我的订单', icon: 'orders-o' },
|
||||
{ key: 'pending', label: '待接单', status: 0, icon: 'clock-o' },
|
||||
{ key: 'accepted', label: '已接单', status: 1, icon: 'passed' },
|
||||
{ key: 'purchasing', label: '采购中', status: 2, icon: 'shopping-cart-o' },
|
||||
{ key: 'delivering', label: '配送中', status: 3, icon: 'logistics' },
|
||||
{ key: 'completed', label: '已完成', status: 4, icon: 'completed' },
|
||||
{ key: 'all', label: '全部订单', icon: 'orders-o' },
|
||||
]
|
||||
|
||||
/** 门店订单 */
|
||||
export interface Order {
|
||||
/** 订单列表行商品预览(仅前 3 条) */
|
||||
export interface OrderItemPreview {
|
||||
product_name: string
|
||||
quantity: number
|
||||
unit: string
|
||||
}
|
||||
|
||||
/** 订单列表行(状态名/可取消/商品预览均由后端给出,直接展示) */
|
||||
export interface OrderListItem {
|
||||
id: number
|
||||
order_no: string
|
||||
/** 订货日期(Y-m-d) */
|
||||
order_date: string
|
||||
total_quantity: string
|
||||
total_amount: string
|
||||
status: OrderStatus
|
||||
/** 状态中文名(直接展示,前端不要再硬编码映射) */
|
||||
status_name: string
|
||||
/** 是否可取消(=待接单),取消按钮据此渲染 */
|
||||
can_cancel: boolean
|
||||
total_quantity: number
|
||||
/** 总称重(斤,常为 0) */
|
||||
total_weight: string
|
||||
total_amount: string
|
||||
remark: string
|
||||
/** 详情接口返回 */
|
||||
items?: OrderItem[]
|
||||
/** 关联采购单 ID,0=未归集 */
|
||||
purchase_id: number
|
||||
/** 关联账单 ID,0=未出账(>0 可跳账单详情) */
|
||||
bill_id: number
|
||||
created_at: string
|
||||
/** 明细种数(如「共 4 种」) */
|
||||
item_count: number
|
||||
/** 商品预览,仅前 3 条;完整明细走详情接口 */
|
||||
items: OrderItemPreview[]
|
||||
}
|
||||
|
||||
/** 订单明细 */
|
||||
/** 订单明细(详情接口,下单时商品快照) */
|
||||
export interface OrderItem {
|
||||
id: number
|
||||
order_id: number
|
||||
product_id: number
|
||||
product_name: string
|
||||
product_spec: string
|
||||
/** 下单时等级实际价快照 */
|
||||
unit: string
|
||||
/** 下单时门店等级实际价快照 */
|
||||
price: string
|
||||
quantity: string
|
||||
/** 称重(默认 0) */
|
||||
quantity: number
|
||||
/** 称重(斤,参考值) */
|
||||
weight: string
|
||||
amount: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
/** 订单详情(无 status_name/can_cancel,状态展示沿用列表行或 ORDER_STATUS_TEXT) */
|
||||
export interface OrderDetail {
|
||||
id: number
|
||||
order_no: string
|
||||
store_id: number
|
||||
order_date: string
|
||||
total_quantity: number
|
||||
total_weight: string
|
||||
total_amount: string
|
||||
status: OrderStatus
|
||||
remark: string
|
||||
purchase_id: number
|
||||
bill_id: number
|
||||
created_at: string
|
||||
items: OrderItem[]
|
||||
}
|
||||
|
||||
/** 下单返回 */
|
||||
export interface OrderCreateResult {
|
||||
id: number
|
||||
order_no: string
|
||||
total_amount: string
|
||||
}
|
||||
|
||||
/** 周期汇总分组 */
|
||||
export interface SummaryGroup {
|
||||
period_label: string
|
||||
total_amount: string
|
||||
total_quantity: string
|
||||
order_count: number
|
||||
}
|
||||
|
||||
/** 周期汇总 */
|
||||
export interface OrderSummary {
|
||||
period: 'day' | 'week' | 'month'
|
||||
groups: SummaryGroup[]
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/** 账单状态:0 待付款 / 1 已付款 */
|
||||
export type StatementStatus = 0 | 1
|
||||
|
||||
export const STATEMENT_STATUS_MAP: Record<StatementStatus, string> = {
|
||||
0: '待付款',
|
||||
1: '已付款',
|
||||
}
|
||||
|
||||
/** 账单 */
|
||||
export interface Statement {
|
||||
id: number
|
||||
statement_no: string
|
||||
period_start: string
|
||||
period_end: string
|
||||
/** 商品金额合计 */
|
||||
goods_amount: string
|
||||
/** 账单计费周转框数(可为负=抵扣) */
|
||||
box_num: number
|
||||
/** 账单计费托盘数(可为负=抵扣) */
|
||||
tray_num: number
|
||||
/** 回筐金额合计(可为负=抵扣) */
|
||||
crate_amount: string
|
||||
/** 账单总金额 = 商品金额 + 回筐金额 */
|
||||
total_amount: string
|
||||
/** 生成时快照的回款周期 */
|
||||
payment_cycle_days: number
|
||||
/** 应结算日期 = 周期结束 + 回款周期天 */
|
||||
settlement_date: string | null
|
||||
status: StatementStatus
|
||||
settled_at: string | null
|
||||
remark: string
|
||||
}
|
||||
|
||||
/** 账单商品汇总记录(按商品聚合,不记单价) */
|
||||
export interface StatementGoods {
|
||||
product_name: string
|
||||
product_spec: string
|
||||
unit: string
|
||||
quantity: string
|
||||
weight: string
|
||||
amount: string
|
||||
}
|
||||
|
||||
/** 账单关联订单(下单单价明细在订单记录中查看) */
|
||||
export interface StatementOrder {
|
||||
id: number
|
||||
order_no: string
|
||||
order_date: string
|
||||
total_amount: string
|
||||
}
|
||||
|
||||
/** 账单详情 */
|
||||
export interface StatementDetail extends Statement {
|
||||
goods: StatementGoods[]
|
||||
orders: StatementOrder[]
|
||||
}
|
||||
|
||||
/** 门店待回筐台账 */
|
||||
export interface PendingContainers {
|
||||
pending_box_num: number
|
||||
pending_tray_num: number
|
||||
box_price: string
|
||||
tray_price: string
|
||||
}
|
||||
@@ -90,6 +90,25 @@ function handleBusinessError(data: ApiResponse): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理请求参数
|
||||
* data 中的 undefined 在 GET 查询串里会被序列化为字符串 'undefined'(null 同理),
|
||||
* 导致后端收到字符串而非缺省;统一在此剔除。GET 额外剔除 null,
|
||||
* POST/PUT 等请求体保留 null(JSON 序列化语义可能依赖它)
|
||||
*/
|
||||
function sanitizeData(data: any, method?: RequestConfig['method']): any {
|
||||
if (!data || typeof data !== 'object' || Array.isArray(data)) return data
|
||||
const isGet = !method || method === 'GET'
|
||||
const result: Record<string, any> = {}
|
||||
Object.keys(data).forEach(key => {
|
||||
const value = data[key]
|
||||
if (value === undefined) return
|
||||
if (isGet && value === null) return
|
||||
result[key] = value
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起网络请求
|
||||
*
|
||||
@@ -126,6 +145,7 @@ export function request<T = any>(config: RequestConfig): Promise<ApiResponse<T>>
|
||||
Taro.request({
|
||||
...restConfig,
|
||||
url: BASE_URL + restConfig.url,
|
||||
data: sanitizeData(restConfig.data, restConfig.method),
|
||||
header,
|
||||
timeout: restConfig.timeout || DEFAULT_TIMEOUT,
|
||||
success(res) {
|
||||
|
||||
Reference in New Issue
Block a user