feat(order): complete 7.1 order full lifecycle state transitions, 15m/30m/48h auto-timeouts, expert reject/apply-complete, and partial refund arbitration
这个提交包含在:
@@ -242,9 +242,22 @@ export function formatOrderData(raw) {
|
||||
|
||||
let statusKey = raw.status || 'IN_SERVICE';
|
||||
if (statusKey === '待支付' || statusKey === '待付款') statusKey = 'PENDING_PAY';
|
||||
else if (statusKey === '已到场核销' || statusKey === '履约中' || statusKey === '服务中') statusKey = 'IN_SERVICE';
|
||||
else if (
|
||||
statusKey === '待接单' ||
|
||||
statusKey === '已接单' ||
|
||||
statusKey === '已到场核销' ||
|
||||
statusKey === '履约中' ||
|
||||
statusKey === '服务中' ||
|
||||
statusKey === '待确认'
|
||||
) statusKey = 'IN_SERVICE';
|
||||
else if (statusKey === '已完成') statusKey = 'COMPLETED';
|
||||
else if (statusKey === '争议仲裁中' || statusKey === '退款中' || statusKey === '已退款' || statusKey === '已全额退款') statusKey = 'REFUNDING';
|
||||
else if (
|
||||
statusKey === '争议仲裁中' ||
|
||||
statusKey === '退款中' ||
|
||||
statusKey === '已退款' ||
|
||||
statusKey === '已全额退款' ||
|
||||
statusKey === '已部分退款'
|
||||
) statusKey = 'REFUNDING';
|
||||
else if (statusKey === '已取消') statusKey = 'CANCELLED';
|
||||
|
||||
const companionAvatar = raw.expertAvatar || raw.expert_avatar || raw.avatar ||
|
||||
@@ -265,6 +278,7 @@ export function formatOrderData(raw) {
|
||||
serviceTitle: raw.serviceTitle || raw.service_title || `${game} · 专业陪伴服务`,
|
||||
quantity: Number(raw.durationHours || raw.duration_hours || raw.quantity || 2),
|
||||
totalAmount: Number(raw.amount || raw.totalAmount || 0),
|
||||
refundAmount: Number(raw.refundAmount || raw.refund_amount || 0),
|
||||
status: statusKey,
|
||||
rawStatus: raw.status || '履约中',
|
||||
meetVenue: raw.location || raw.meetVenue || '北京市公开商圈',
|
||||
@@ -332,7 +346,33 @@ export async function payOrder(orderId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认验收订单
|
||||
* 大神拒单自动退款 (待接单 -> 已退款)
|
||||
*/
|
||||
export async function rejectOrder(orderId, payload = {}) {
|
||||
try {
|
||||
const res = await http.post(`/orders/${orderId}/reject`, payload);
|
||||
return formatOrderData(res);
|
||||
} catch (err) {
|
||||
console.warn('[API] 拒单接口网络异常:', err.message);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 大神点击申请完成服务 (服务中 -> 待确认)
|
||||
*/
|
||||
export async function applyCompleteOrder(orderId, payload = {}) {
|
||||
try {
|
||||
const res = await http.post(`/orders/${orderId}/apply-complete`, payload);
|
||||
return formatOrderData(res);
|
||||
} catch (err) {
|
||||
console.warn('[API] 申请完成服务网络异常:', err.message);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 买家确认验收订单 (待确认 -> 已完成)
|
||||
*/
|
||||
export async function completeOrder(orderId) {
|
||||
try {
|
||||
|
||||
@@ -300,7 +300,10 @@
|
||||
<text class="pool-desc">{{ order.desc }}</text>
|
||||
<view class="pool-foot">
|
||||
<text class="pool-time">倒计时: {{ order.countdown }}s</text>
|
||||
<button class="btn-grab btn-active" @click="grabOrder(order)">立即抢单</button>
|
||||
<view class="pool-btn-group">
|
||||
<button class="btn-reject btn-active" @click="handleRejectOrder(order)">拒单</button>
|
||||
<button class="btn-grab btn-active" @click="grabOrder(order)">立即抢单</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -404,6 +407,8 @@ import {
|
||||
getOrdersPool,
|
||||
getExpertStats,
|
||||
claimOrder,
|
||||
rejectOrder,
|
||||
applyCompleteOrder,
|
||||
completeOrder as completeOrderApi,
|
||||
updateCompanionProfile,
|
||||
getExpertRevenueTrends,
|
||||
@@ -763,13 +768,37 @@ const grabOrder = async (order) => {
|
||||
});
|
||||
} catch (_) {}
|
||||
uni.showToast({
|
||||
title: `抢单成功!已接单 ¥${order.price}`,
|
||||
title: '抢单成功已接单',
|
||||
icon: 'success'
|
||||
});
|
||||
poolOrders.value = poolOrders.value.filter(o => o.id !== order.id);
|
||||
loadWorkspaceOrders();
|
||||
};
|
||||
|
||||
const handleRejectOrder = (order) => {
|
||||
uni.showModal({
|
||||
title: '确认拒单并退款',
|
||||
content: '拒单后系统将自动将该订单款项原路全额退还给买家,确定拒单吗?',
|
||||
confirmColor: '#EF4444',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await rejectOrder(order.id, {
|
||||
expertId: expert.value.id || 101,
|
||||
reason: '大神当前档期冲突拒单'
|
||||
});
|
||||
} catch (_) {}
|
||||
poolOrders.value = poolOrders.value.filter(o => o.id !== order.id);
|
||||
uni.showToast({
|
||||
title: '已拒单并自动退款',
|
||||
icon: 'none'
|
||||
});
|
||||
loadWorkspaceOrders();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const chatWithBuyer = () => {
|
||||
uni.showActionSheet({
|
||||
@@ -798,15 +827,19 @@ const chatWithBuyer = () => {
|
||||
const completeOrder = (id) => {
|
||||
uni.showModal({
|
||||
title: '确认提交服务验收',
|
||||
content: '服务已完成?提交后将完成服务结算并自动解冻订单资金打入您的佣金钱包。',
|
||||
content: '服务已完成?提交后订单将进入【待确认】状态,买家确认或48小时后自动解冻资金结算至您的佣金钱包。',
|
||||
confirmColor: '#10B981',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await completeOrderApi(id);
|
||||
} catch (_) {}
|
||||
await applyCompleteOrder(id, { expertId: expert.value.id || 101 });
|
||||
} catch (_) {
|
||||
try {
|
||||
await completeOrderApi(id);
|
||||
} catch (_) {}
|
||||
}
|
||||
activeOrders.value = activeOrders.value.filter(o => o.id !== id);
|
||||
uni.showToast({ title: '已验收结算并入账!', icon: 'success' });
|
||||
uni.showToast({ title: '已提交待买家确认', icon: 'success' });
|
||||
loadWorkspaceOrders();
|
||||
}
|
||||
}
|
||||
@@ -1698,6 +1731,26 @@ const switchBackToPlayer = () => {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.pool-btn-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.btn-reject {
|
||||
background: rgba(239, 68, 68, 0.14);
|
||||
border: 1px solid rgba(239, 68, 68, 0.35);
|
||||
color: #F87171;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 28rpx;
|
||||
height: 56rpx;
|
||||
line-height: 56rpx;
|
||||
padding: 0 22rpx;
|
||||
margin: 0;
|
||||
&::after { display: none; }
|
||||
}
|
||||
|
||||
.btn-grab {
|
||||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||||
color: #FFF;
|
||||
@@ -1709,6 +1762,7 @@ const switchBackToPlayer = () => {
|
||||
padding: 0 28rpx;
|
||||
margin: 0;
|
||||
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
|
||||
&::after { display: none; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,8 +466,11 @@ const goToExplore = () => {
|
||||
};
|
||||
|
||||
const getStatusText = (status, order) => {
|
||||
if (order && order.rawStatus === '已退款') return '已全额退款';
|
||||
if (order && (order.rawStatus === '已退款' || order.rawStatus === '已全额退款')) return '已全额退款';
|
||||
if (order && order.rawStatus === '已部分退款') return '已部分退款';
|
||||
if (order && order.rawStatus === '争议仲裁中') return '争议仲裁中';
|
||||
if (order && order.rawStatus === '待接单') return '待接单';
|
||||
if (order && order.rawStatus === '待确认') return '待确认验收';
|
||||
const map = {
|
||||
PENDING_PAY: '待付款',
|
||||
IN_SERVICE: '服务中',
|
||||
|
||||
在新工单中引用
屏蔽一个用户