Browse Source

feat(order): 优化订单列表展示逻辑

- 为 PaymentCountdown 组件添加背景渐变样式- 添加订单支付过期判断和过滤逻辑
- 优化订单列表项的样式和结构
-修复 invite.ts 中的若干问题
dev
科技小王子 5 days ago
parent
commit
40e282cf8f
  1. 6
      src/components/PaymentCountdown.scss
  2. 77
      src/user/order/components/OrderList.tsx
  3. 31
      src/utils/invite.ts

6
src/components/PaymentCountdown.scss

@ -18,19 +18,19 @@
/* 紧急状态(少于1小时) */
&.urgent {
color: #ff6b6b;
background: linear-gradient(135deg, #ff6b6b, #ee5a52);
animation: pulse 2s infinite;
}
/* 非常紧急状态(少于10分钟) */
&.critical {
color: #c44569;
background: linear-gradient(135deg, #ff4757, #c44569);
animation: flash 1s infinite;
}
/* 过期状态 */
&.expired {
color: #95a5a6;
background: linear-gradient(135deg, #95a5a6, #7f8c8d);
animation: none;
}
}

77
src/user/order/components/OrderList.tsx

@ -11,6 +11,15 @@ import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
import {copyText} from "@/utils/common";
import PaymentCountdown from "@/components/PaymentCountdown";
// 判断订单是否支付已过期
const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => {
if (!createTime) return false;
const createTimeObj = dayjs(createTime);
const expireTime = createTimeObj.add(timeoutHours, 'hour');
const now = dayjs();
return now.isAfter(expireTime);
};
const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({
marginTop: showSearch ? '0' : '0', // 如果显示搜索框,增加更多的上边距
height: showSearch ? '75vh' : '84vh', // 相应调整高度
@ -363,10 +372,10 @@ function OrderList(props: OrderListProps) {
})
}
</Tabs>
<div style={getInfiniteUlStyle(props.showSearch)} id="scroll">
<View style={getInfiniteUlStyle(props.showSearch)} id="scroll">
{error ? (
<div className="flex flex-col items-center justify-center h-64">
<div className="text-gray-500 mb-4">{error}</div>
<View className="flex flex-col items-center justify-center h-64">
<View className="text-gray-500 mb-4">{error}</View>
<Button
size="small"
type="primary"
@ -374,7 +383,7 @@ function OrderList(props: OrderListProps) {
>
</Button>
</div>
</View>
) : (
<InfiniteLoading
target="scroll"
@ -403,21 +412,30 @@ function OrderList(props: OrderListProps) {
>
{/* 订单列表 */}
{list.length > 0 && list?.map((item, index) => {
{list.length > 0 && list
?.filter((item) => {
// 如果是待付款标签页(tapIndex === 0),过滤掉支付已过期的订单
if (tapIndex === 0 && !item.payStatus && item.orderStatus !== 2 && item.createTime) {
return !isPaymentExpired(item.createTime);
}
return true;
})
?.map((item, index) => {
return (
<Cell key={index} style={{padding: '16px'}}
onClick={() => Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
<Space direction={'vertical'} className={'w-full flex flex-col'}>
<View className={'order-no flex justify-between'}>
<View className={'flex items-center'}>
<View className={'text-gray-600 font-bold text-sm'}
<Text className={'text-gray-600 font-bold text-sm'}
onClick={(e) => {
e.stopPropagation();
copyText(`${item.orderNo}`)
}}>{item.orderNo}</View>
}}>{item.orderNo}</Text>
</View>
{/* 右侧显示合并的状态和倒计时 */}
<View className={`${getOrderStatusColor(item)} font-medium`}>
{item.orderStatus === 0 && (
{!item.payStatus && item.orderStatus !== 2 ? (
<PaymentCountdown
createTime={item.createTime}
payStatus={item.payStatus}
@ -425,18 +443,19 @@ function OrderList(props: OrderListProps) {
showSeconds={false}
mode="text"
/>
) : (
getOrderStatusText(item)
)}
<Text>{getOrderStatusText(item)}</Text>
</View>
</View>
<div
className={'create-time text-gray-400 text-xs'}>{dayjs(item.createTime).format('YYYY年MM月DD日 HH:mm:ss')}</div>
<View
className={'create-time text-gray-400 text-xs'}>{dayjs(item.createTime).format('YYYY年MM月DD日 HH:mm:ss')}</View>
{/* 商品信息 */}
<div className={'goods-info'}>
<View className={'goods-info'}>
{item.orderGoods && item.orderGoods.length > 0 ? (
item.orderGoods.map((goods, goodsIndex) => (
<div key={goodsIndex} className={'flex items-center mb-2'}>
<View key={goodsIndex} className={'flex items-center mb-2'}>
<Image
src={goods.image || '/default-goods.png'}
width="50"
@ -444,30 +463,30 @@ function OrderList(props: OrderListProps) {
lazyLoad={false}
className={'rounded'}
/>
<div className={'ml-2 flex-1'}>
<div className={'text-sm font-bold'}>{goods.goodsName}</div>
{goods.spec && <div className={'text-gray-500 text-xs'}>{goods.spec}</div>}
<div className={'text-gray-500 text-xs'}>{goods.totalNum}</div>
</div>
<div className={'text-sm'}>{goods.price}</div>
</div>
<View className={'ml-2 flex flex-col flex-1'}>
<Text className={'text-sm font-bold'}>{goods.goodsName}</Text>
{goods.spec && <Text className={'text-gray-500 text-xs'}>{goods.spec}</Text>}
<Text className={'text-gray-500 text-xs'}>{goods.totalNum}</Text>
</View>
<Text className={'text-sm'}>{goods.price}</Text>
</View>
))
) : (
<div className={'flex items-center'}>
<View className={'flex items-center'}>
<Avatar
src='/default-goods.png'
size={'50'}
shape={'square'}
/>
<div className={'ml-2'}>
<div className={'text-sm'}>{item.title || '订单商品'}</div>
<div className={'text-gray-400 text-xs'}>{item.totalNum}</div>
</div>
</div>
<View className={'ml-2'}>
<Text className={'text-sm'}>{item.title || '订单商品'}</Text>
<Text className={'text-gray-400 text-xs'}>{item.totalNum}</Text>
</View>
</View>
)}
</div>
</View>
<div className={'w-full text-right'}>{item.payPrice}</div>
<Text className={'w-full text-right'}>{item.payPrice}</Text>
{/* 操作按钮 */}
<Space className={'btn flex justify-end'}>
@ -506,7 +525,7 @@ function OrderList(props: OrderListProps) {
})}
</InfiniteLoading>
)}
</div>
</View>
</>
)
}

31
src/utils/invite.ts

@ -17,9 +17,12 @@ export function parseInviteParams(options: any): InviteParams | null {
try {
// 从 scene 参数中解析邀请信息
if (options.scene) {
// 确保 scene 是字符串类型
const sceneStr = typeof options.scene === 'string' ? options.scene : String(options.scene)
const params: InviteParams = {}
const pairs = options.scene.split('&')
const pairs = sceneStr.split('&')
pairs.forEach((pair: string) => {
const [key, value] = pair.split('=')
if (key && value) {
@ -36,10 +39,10 @@ export function parseInviteParams(options: any): InviteParams | null {
}
}
})
return params.inviter ? params : null
}
// 从 query 参数中解析邀请信息(兼容旧版本)
if (options.referrer) {
return {
@ -47,7 +50,7 @@ export function parseInviteParams(options: any): InviteParams | null {
source: 'link'
}
}
return null
} catch (error) {
console.error('解析邀请参数失败:', error)
@ -80,7 +83,7 @@ export function getStoredInviteParams(): InviteParams | null {
// 检查是否过期(24小时)
const now = Date.now()
const expireTime = 24 * 60 * 60 * 1000 // 24小时
if (now - stored.timestamp < expireTime) {
return {
inviter: stored.inviter,
@ -139,7 +142,7 @@ export async function handleInviteRelation(userId: number): Promise<boolean> {
// 清除本地存储的邀请参数
clearInviteParams()
console.log(`邀请关系建立成功: ${inviterId} -> ${userId}`)
return true
} catch (error) {
@ -167,7 +170,7 @@ export function getSourceDisplayName(source: string): string {
'poster': '海报分享',
'unknown': '未知来源'
}
return sourceMap[source] || source
}
@ -177,11 +180,11 @@ export function getSourceDisplayName(source: string): string {
export function validateInviteCode(scene: string): boolean {
try {
if (!scene) return false
// 检查是否包含必要的参数
const hasInviter = scene.includes('inviter=')
const hasSource = scene.includes('source=')
return hasInviter && hasSource
} catch (error) {
return false
@ -208,19 +211,19 @@ export function trackInviteSource(source: string, inviterId?: number) {
timestamp: Date.now(),
userAgent: Taro.getSystemInfoSync()
}
// 可以发送到统计服务
console.log('邀请来源统计:', trackData)
// 暂存到本地,后续可批量上报
const existingTracks = Taro.getStorageSync('invite_tracks') || []
existingTracks.push(trackData)
// 只保留最近100条记录
if (existingTracks.length > 100) {
existingTracks.splice(0, existingTracks.length - 100)
}
Taro.setStorageSync('invite_tracks', existingTracks)
} catch (error) {
console.error('统计邀请来源失败:', error)

Loading…
Cancel
Save