@@ -0,0 +1,382 @@
import React , { useState , useEffect } from 'react'
import { View , Text , Image , ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { pageShopOrder , updateShopOrder } from '@/api/shop/shopOrder'
import type { ShopOrder } from '@/api/shop/shopOrder/model'
import { TenantId } from '../../../../config/app'
type TabKey = 'unpaid' | 'unshipped' | 'shipped' | 'completed'
const TABS = [
{ key : 'unpaid' , label : '待付款' , params : { payStatus : 0 } } ,
{ key : 'unshipped' , label : '待发货' , params : { payStatus : 1 , deliveryStatus : 10 } } ,
{ key : 'shipped' , label : '待收货' , params : { payStatus : 1 , deliveryStatus : 20 } } ,
{ key : 'completed' , label : '已完成' , params : { orderStatus : 1 , payStatus : 1 } } ,
] as const
type OpType = 'pay' | 'ship' | 'complete'
const OP_LABEL : Record < OpType , string > = {
pay : '已付款' ,
ship : '已发货' ,
complete : '已完成' ,
}
/** 上传图片到后端,返回图片 URL */
const uploadImage = ( filePath : string ) : Promise < string > = > {
return new Promise ( ( resolve , reject ) = > {
Taro . uploadFile ( {
url : 'https://shop-api.websoft.top/api/oss/upload' ,
filePath ,
name : 'file' ,
header : { 'content-type' : 'application/json' , TenantId } ,
success : ( res ) = > {
try {
const data = JSON . parse ( res . data )
if ( data . code === 0 && data . data ? . url ) {
resolve ( data . data . url )
} else {
reject ( new Error ( data . message || '上传失败' ) )
}
} catch {
reject ( new Error ( '解析上传响应失败' ) )
}
} ,
fail : ( ) = > reject ( new Error ( '上传请求失败' ) ) ,
} )
} )
}
export default function StoreCenterPage() {
const [ activeTab , setActiveTab ] = useState < TabKey > ( 'unshipped' )
const [ orderList , setOrderList ] = useState < ShopOrder [ ] > ( [ ] )
const [ total , setTotal ] = useState ( 0 )
const [ pageNo , setPageNo ] = useState ( 1 )
const [ loading , setLoading ] = useState ( false )
const pageSize = 10
// 操作弹窗状态
const [ showModal , setShowModal ] = useState ( false )
const [ currentOrder , setCurrentOrder ] = useState < ShopOrder | null > ( null )
const [ opType , setOpType ] = useState < OpType > ( 'pay' )
const [ proofImages , setProofImages ] = useState < string [ ] > ( [ ] )
const [ submitting , setSubmitting ] = useState ( false )
/** 加载订单列表 */
const loadOrders = async ( tab : TabKey = activeTab , page : number = 1 , append = false ) = > {
const tabConfig = TABS . find ( t = > t . key === tab )
if ( ! tabConfig ) return
setLoading ( true )
try {
const res : any = await pageShopOrder ( {
. . . tabConfig . params ,
pageNo ,
pageSize ,
} as any )
const records : ShopOrder [ ] = res ? . records || res ? . data ? . records || [ ]
const totalCount : number = res ? . total || res ? . data ? . total || 0
setOrderList ( append ? prev = > [ . . . prev , . . . records ] : records )
setTotal ( totalCount )
setPageNo ( page )
} catch ( e : any ) {
Taro . showToast ( { title : e.message || '加载失败' , icon : 'none' } )
} finally {
setLoading ( false )
}
}
useEffect ( ( ) = > { loadOrders ( activeTab , 1 ) } , [ activeTab ] )
/** 打开操作弹窗 */
const openModal = ( order : ShopOrder , type : OpType ) = > {
setCurrentOrder ( order )
setOpType ( type )
setProofImages ( [ ] )
setShowModal ( true )
}
/** 关闭弹窗 */
const closeModal = ( ) = > {
setShowModal ( false )
setCurrentOrder ( null )
setProofImages ( [ ] )
}
/** 选择凭证图片 */
const chooseProofImage = ( ) = > {
Taro . chooseImage ( {
count : 1 ,
sizeType : [ 'compressed' ] ,
sourceType : [ 'camera' , 'album' ] ,
success : async ( res ) = > {
try {
Taro . showLoading ( { title : '上传中...' } )
const url = await uploadImage ( res . tempFilePaths [ 0 ] )
Taro . hideLoading ( )
setProofImages ( prev = > [ . . . prev , url ] )
} catch ( e : any ) {
Taro . hideLoading ( )
Taro . showToast ( { title : e.message || '上传失败' , icon : 'none' } )
}
} ,
} )
}
/** 移除凭证图片 */
const removeProofImage = ( idx : number ) = > {
setProofImages ( prev = > prev . filter ( ( _ , i ) = > i !== idx ) )
}
/** 提交操作 */
const submitOperation = async ( ) = > {
if ( ! currentOrder ) return
if ( proofImages . length === 0 ) {
Taro . showToast ( { title : '请上传操作凭证' , icon : 'none' } )
return
}
setSubmitting ( true )
try {
const proofText = ` 【门店操作凭证】 ${ OP_LABEL [ opType ] } : ${ proofImages . join ( ',' ) } `
const updateData : any = { orderId : currentOrder.orderId }
if ( opType === 'pay' ) {
updateData . payStatus = true
updateData . payTime = new Date ( ) . toISOString ( )
} else if ( opType === 'ship' ) {
updateData . deliveryStatus = 20
updateData . deliveryTime = new Date ( ) . toISOString ( )
} else if ( opType === 'complete' ) {
updateData . orderStatus = 1
}
// 将凭证追加到备注
updateData . comments = ( currentOrder . comments || '' ) + ` \ n ${ proofText } `
await updateShopOrder ( updateData )
Taro . showToast ( { title : '操作成功' , icon : 'success' } )
closeModal ( )
loadOrders ( activeTab , 1 )
} catch ( e : any ) {
Taro . showToast ( { title : e.message || '操作失败' , icon : 'none' } )
} finally {
setSubmitting ( false )
}
}
/** 获取订单可进行的操作 */
const getOrderActions = ( order : ShopOrder ) = > {
const actions : { label : string ; type : OpType } [ ] = [ ]
if ( ! order . payStatus ) {
actions . push ( { label : '标记已付款' , type : 'pay' } )
}
if ( order . payStatus && order . deliveryStatus === 10 ) {
actions . push ( { label : '标记已发货' , type : 'ship' } )
}
if ( order . payStatus && order . deliveryStatus === 20 && order . orderStatus === 0 ) {
actions . push ( { label : '标记已完成' , type : 'complete' } )
}
return actions
}
const renderOrderCard = ( order : ShopOrder ) = > {
const actions = getOrderActions ( order )
return (
< View key = { order . orderId } className = 'bg-white rounded-xl mx-3 mt-3 p-4' >
{ /* 订单头部 */ }
< View className = 'flex justify-between items-center mb-3' >
< Text className = 'text-xs text-gray-400' > 订 单 号 : { order . orderNo } < / Text >
< Text className = 'text-xs' style = { { color : getStatusColor ( order ) } } >
{ getStatusText ( order ) }
< / Text >
< / View >
{ /* 商品列表 */ }
{ ( order as any ) . orderGoods ? . map ( ( goods : any , idx : number ) = > (
< View key = { idx } className = 'flex items-center gap-3 mb-3' >
{ goods . coverImage && (
< Image className = 'w-16 h-16 rounded-lg bg-gray-50' src = { goods . coverImage } mode = 'aspectFill' / >
) }
< View className = 'flex-1' >
< Text className = 'text-sm text-gray-800 block' > { goods . goodsName } < / Text >
{ goods . specInfo && (
< Text className = 'text-xs text-gray-400 mt-1 block' > { goods . specInfo } < / Text >
) }
< View className = 'flex justify-between items-center mt-1' >
< Text className = 'text-sm text-red-500 font-medium' > ¥ { goods . price } < / Text >
< Text className = 'text-xs text-gray-400' > x { goods . quantity } < / Text >
< / View >
< / View >
< / View >
) ) }
{ /* 收货信息 */ }
{ ( order . realName || order . phone ) && (
< View className = 'bg-gray-50 rounded-lg p-3 mb-3' >
< Text className = 'text-sm text-gray-700 block' > { order . realName } { order . phone } < / Text >
{ order . address && (
< Text className = 'text-xs text-gray-400 mt-1 block' > { order . address } < / Text >
) }
< / View >
) }
{ /* 金额 */ }
< View className = 'flex justify-between items-center mb-3' >
< Text className = 'text-xs text-gray-400' >
共 { ( order as any ) . orderGoods ? . length || 0 } 件 商 品
< / Text >
< Text className = 'text-sm text-gray-700' >
实 付 : < Text className = 'text-red-500 font-medium' > ¥ { order . payPrice || order . totalPrice } < / Text >
< / Text >
< / View >
{ /* 操作按钮 */ }
{ actions . length > 0 && (
< View className = 'flex justify-end gap-2 border-t border-gray-50 pt-3' >
{ actions . map ( act = > (
< View
key = { act . type }
className = 'px-4 py-2 rounded-lg border border-blue-500'
onClick = { ( ) = > openModal ( order , act . type ) }
>
< Text className = 'text-sm text-blue-500' > { act . label } < / Text >
< / View >
) ) }
< / View >
) }
< / View >
)
}
return (
< View className = 'min-h-full bg-gray-50' >
{ /* Tab 栏 */ }
< View className = 'bg-white flex' >
{ TABS . map ( tab = > (
< View
key = { tab . key }
className = { ` flex-1 text-center py-3 border-b-2 ${
activeTab === tab . key ? 'border-blue-500' : 'border-transparent'
} ` }
onClick = { ( ) = > setActiveTab ( tab . key as TabKey ) }
>
< Text className = { ` text-sm ${ activeTab === tab . key ? 'text-blue-500 font-medium' : 'text-gray-500' } ` } >
{ tab . label }
< / Text >
< / View >
) ) }
< / View >
{ /* 订单列表 */ }
< ScrollView
scrollY
style = { { height : 'calc(100vh - 100px)' } }
onScrollToLower = { ( ) = > {
if ( orderList . length < total && ! loading ) loadOrders ( activeTab , pageNo + 1 , true )
} }
>
{ loading && orderList . length === 0 ? (
< View className = 'flex justify-center items-center py-20' >
< Text className = 'text-gray-400' > 加 载 中 . . . < / Text >
< / View >
) : orderList . length === 0 ? (
< View className = 'flex justify-center items-center py-20' >
< Text className = 'text-gray-400' > 暂 无 订 单 < / Text >
< / View >
) : (
orderList . map ( renderOrderCard )
) }
{ loading && orderList . length > 0 && (
< View className = 'flex justify-center items-center py-4' >
< Text className = 'text-gray-400 text-sm' > 加 载 中 . . . < / Text >
< / View >
) }
< View className = 'h-6' / >
< / ScrollView >
{ /* 操作弹窗 */ }
{ showModal && (
< View className = 'fixed inset-0 z-50 flex items-end justify-center' >
{ /* 遮罩 */ }
< View className = 'absolute inset-0 bg-black/50' onClick = { closeModal } / >
{ /* 弹窗内容 */ }
< View className = 'relative bg-white rounded-t-2xl w-full px-5 pt-6 pb-10' >
< Text className = 'text-lg font-medium text-gray-800 text-center mb-5 block' >
确 认 { OP_LABEL [ opType ] }
< / Text >
{ /* 订单信息 */ }
{ currentOrder && (
< View className = 'bg-gray-50 rounded-xl p-4 mb-5' >
< Text className = 'text-sm text-gray-700 block' > 订 单 号 : { currentOrder . orderNo } < / Text >
< Text className = 'text-sm text-gray-700 mt-1 block' >
实 付 金 额 : < Text className = 'text-red-500 font-medium' > ¥ { currentOrder . payPrice || currentOrder . totalPrice } < / Text >
< / Text >
< / View >
) }
{ /* 凭证上传 */ }
< Text className = 'text-sm text-gray-600 mb-3 block' > 上 传 操 作 凭 证 ( 必 填 , 最 多 3 张 ) < / Text >
< View className = 'flex flex-wrap gap-3 mb-6' >
{ proofImages . map ( ( url , idx ) = > (
< View key = { idx } className = 'relative' >
< Image className = 'w-20 h-20 rounded-lg bg-gray-50' src = { url } mode = 'aspectFill' / >
< View
className = 'absolute -top-2 -right-2 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center'
onClick = { ( ) = > removeProofImage ( idx ) }
>
< Text className = 'text-white text-xs' > × < / Text >
< / View >
< / View >
) ) }
{ proofImages . length < 3 && (
< View
className = 'w-20 h-20 rounded-lg bg-gray-50 border-2 border-dashed border-gray-200 flex items-center justify-center'
onClick = { chooseProofImage }
>
< Text className = 'text-2xl text-gray-300' > + < / Text >
< / View >
) }
< / View >
{ /* 操作按钮 */ }
< View className = 'flex gap-3' >
< View className = 'flex-1 py-3 rounded-xl bg-gray-100 text-center' onClick = { closeModal } >
< Text className = 'text-sm text-gray-600' > 取 消 < / Text >
< / View >
< View
className = 'flex-1 py-3 rounded-xl bg-blue-500 text-center flex items-center justify-center'
onClick = { submitOperation }
>
{ submitting ? (
< Text className = 'text-sm text-white' > 提 交 中 . . . < / Text >
) : (
< Text className = 'text-sm text-white' > 确 认 { OP_LABEL [ opType ] } < / Text >
) }
< / View >
< / View >
< / View >
< / View >
) }
< / View >
)
}
/** 获取订单状态文字 */
function getStatusText ( order : ShopOrder ) : string {
if ( order . payStatus === false ) return '待付款'
if ( order . orderStatus === 2 ) return '已取消'
if ( order . deliveryStatus === 10 ) return '待发货'
if ( order . deliveryStatus === 20 ) return '待收货'
if ( order . orderStatus === 1 ) return '已完成'
return '处理中'
}
/** 获取订单状态颜色 */
function getStatusColor ( order : ShopOrder ) : string {
if ( order . payStatus === false ) return '#ee0a24'
if ( order . orderStatus === 2 ) return '#999'
if ( order . deliveryStatus === 10 ) return '#ff7d00'
if ( order . deliveryStatus === 20 ) return '#4b9cf5'
if ( order . orderStatus === 1 ) return '#0e932e'
return '#999'
}