mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
feat(送礼订单): 完善送礼/收礼订单全链路与详情性能
- 订单详情按查看者裁剪:购买者隐藏收货信息、物流轨迹及确认收货/申请退款/查看物流;领取人展示收货信息、物流轨迹与确认收货/查看物流 - 领取人订单列表纳入礼物订单并只读展示,统计计数含礼物、金额仅计本人支付 - 收礼人可确认收货;修复领取成功页订单号传递与领取页礼物详情查询 - 新增 eb_order_center.gift_uid 实体列与索引并同步安装 SQL - 详情接口物流轨迹改为缓存读取 + 前端异步补齐,空轨迹短缓存 120 秒,修复送礼订单物流接口收货地址取值
This commit is contained in:
parent
897d4339d7
commit
78f56bb074
@ -130,7 +130,8 @@ class CenterOrderController
|
||||
if ($ownedId === null) {
|
||||
return app('json')->fail('订单不存在');
|
||||
}
|
||||
$result = $services->getDetailWithConfig($ownedId);
|
||||
// 详情首屏不同步请求第三方物流:只读轨迹缓存,未命中由前端走 order/express 异步补齐
|
||||
$result = $services->getDetailWithConfig($ownedId, false);
|
||||
// 移动端订单详情不返回 timeline section,避免接口数据冗余
|
||||
if (!empty($result['page_config']['sections'])) {
|
||||
$result['page_config']['sections'] = array_values(
|
||||
@ -139,6 +140,8 @@ class CenterOrderController
|
||||
})
|
||||
);
|
||||
}
|
||||
// 送礼订单按查看者裁剪:购买者隐藏收货信息/物流/相关按钮,领取人保留只读物流能力
|
||||
$result = $services->applyGiftViewerPolicy($result, $uid);
|
||||
return app('json')->success($result);
|
||||
} catch (\Throwable $e) {
|
||||
return app('json')->fail($e->getMessage());
|
||||
@ -435,14 +438,20 @@ class CenterOrderController
|
||||
$validCartInfo = $priceGroup['cartInfo'] ?? $validCartInfo;
|
||||
|
||||
// 组装与单品直购模式一致的 priceData,供 computed/create 读取
|
||||
// 送礼订单:应付价 = 商品总价 + 礼品附加费(无运费)
|
||||
$giftPrice = $priceGroup['giftPrice'] ?? 0;
|
||||
$priceData = [
|
||||
'totalPrice' => $priceGroup['totalPrice'] ?? 0,
|
||||
'payPrice' => bcadd((string)($priceGroup['totalPrice'] ?? 0), (string)($priceGroup['storePostage'] ?? 0), 2),
|
||||
'payPrice' => bcadd(
|
||||
(string)bcadd((string)($priceGroup['totalPrice'] ?? 0), (string)($priceGroup['storePostage'] ?? 0), 2),
|
||||
(string)$giftPrice,
|
||||
2
|
||||
),
|
||||
'payPostage' => $priceGroup['storePostage'] ?? 0,
|
||||
'isStoreFreePostage' => $priceGroup['isStoreFreePostage'] ?? false,
|
||||
'storeFreePostage' => $priceGroup['storeFreePostage'] ?? 0,
|
||||
'costPrice' => $priceGroup['costPrice'] ?? 0,
|
||||
'giftPrice' => $priceGroup['giftPrice'] ?? 0,
|
||||
'giftPrice' => $giftPrice,
|
||||
'levelPrice' => $priceGroup['levelPrice'] ?? 0,
|
||||
'memberPrice' => $priceGroup['memberPrice'] ?? 0,
|
||||
];
|
||||
@ -569,10 +578,11 @@ class CenterOrderController
|
||||
}
|
||||
|
||||
// 功能开关配置
|
||||
// 送礼订单:收货信息由领取人填写,关闭配送/自提/上门入口
|
||||
$features = [
|
||||
'express_shipping' => $expressShipping, // 是否支持快递配送(所有商品均支持快递)
|
||||
'store_pickup' => (bool)$storeSelfMention && $storePickupAll,// 是否支持到店自提(系统开启且所有商品均支持自提)
|
||||
'door_service' => $doorServiceAll, // 是否支持上门服务(所有商品均支持上门)
|
||||
'express_shipping' => $isGift != 1 && $expressShipping, // 是否支持快递配送(所有商品均支持快递)
|
||||
'store_pickup' => $isGift != 1 && (bool)$storeSelfMention && $storePickupAll,// 是否支持到店自提(系统开启且所有商品均支持自提)
|
||||
'door_service' => $isGift != 1 && $doorServiceAll, // 是否支持上门服务(所有商品均支持上门)
|
||||
'door_service_postage' => $doorServicePostage, // 上门服务费用(元)
|
||||
'remark' => true, // 买家留言
|
||||
'invoice' => (bool)sys_config('invoice_func_status', 0),// 发票
|
||||
@ -581,6 +591,8 @@ class CenterOrderController
|
||||
'custom_form' => !empty($cartCustomForm), // 自定义表单(取主商品定义)
|
||||
'params_list' => false, // 商品参数(下单页不展示)
|
||||
'visitor_form' => !empty($cartVisitorFormConfig), // 游客信息表单(门票专属,取主商品定义)
|
||||
'gift' => $isGift == 1, // 送礼订单标记
|
||||
'gift_mark' => true, // 送礼留言输入
|
||||
];
|
||||
|
||||
$deduction = $cartGroup['deduction'] ?? [];
|
||||
@ -648,6 +660,8 @@ class CenterOrderController
|
||||
$addressId = (int) request()->param('address_id', 0);
|
||||
$deliveryType = $this->normalizeOrderDeliveryType((string) request()->param('delivery_type', ''));
|
||||
$storeId = (int) request()->param('store_id', 0);
|
||||
// 送礼订单:不选地址、不计运费,累加礼品附加费
|
||||
$isGift = (int) request()->param('is_gift', 0);
|
||||
|
||||
if (!$key) {
|
||||
return app('json')->fail('参数错误');
|
||||
@ -726,7 +740,8 @@ class CenterOrderController
|
||||
$uid,
|
||||
$cartInfo,
|
||||
$rebuildAddressInfo,
|
||||
$shippingType
|
||||
$shippingType,
|
||||
$isGift
|
||||
);
|
||||
$cartInfo = $freshCartGroup['valid'] ?? [];
|
||||
if (empty($cartInfo)) {
|
||||
@ -735,16 +750,20 @@ class CenterOrderController
|
||||
if (!empty($freshCartGroup['invalid'])) {
|
||||
return app('json')->fail('商品库存或配送条件已变化,请返回确认页刷新');
|
||||
}
|
||||
// 商品信息可能已变化,必须以重建后的商品能力再次校验配送方式。
|
||||
$deliveryContext = $this->validateNormalOrderDelivery(
|
||||
$cartInfo,
|
||||
$deliveryType,
|
||||
$uid,
|
||||
$addressId,
|
||||
$storeId,
|
||||
[],
|
||||
false
|
||||
);
|
||||
// 送礼订单由领取人填收货信息,下单阶段不校验配送地址
|
||||
$deliveryContext = ['address_info' => [], 'store_info' => []];
|
||||
if ($isGift != 1) {
|
||||
// 商品信息可能已变化,必须以重建后的商品能力再次校验配送方式。
|
||||
$deliveryContext = $this->validateNormalOrderDelivery(
|
||||
$cartInfo,
|
||||
$deliveryType,
|
||||
$uid,
|
||||
$addressId,
|
||||
$storeId,
|
||||
[],
|
||||
false
|
||||
);
|
||||
}
|
||||
/** @var OrderComputedServices $computedServices */
|
||||
$computedServices = app()->make(OrderComputedServices::class);
|
||||
$priceGroup = $computedServices->getOrderPriceGroup(
|
||||
@ -753,7 +772,7 @@ class CenterOrderController
|
||||
$deliveryContext['address_info'],
|
||||
$userInfo,
|
||||
$shippingType,
|
||||
0
|
||||
$isGift
|
||||
);
|
||||
$cartGroup['cartInfo'] = $cartInfo;
|
||||
$cartGroup['priceData'] = [
|
||||
@ -872,7 +891,7 @@ class CenterOrderController
|
||||
$computedServices = app()->make(OrderComputedServices::class);
|
||||
$result = $computedServices->computedOrder(
|
||||
$uid, $userInfo, $modifiedGroup, $addressId, '',
|
||||
$useIntegral, 0, false, $shippingType, 0
|
||||
$useIntegral, 0, false, $shippingType, $isGift
|
||||
);
|
||||
|
||||
// 修正返回字段:total_price 用原始总价,coupon_price 用计算值
|
||||
@ -959,6 +978,9 @@ class CenterOrderController
|
||||
$formShippingType = $formDeliveryType === 'pickup' ? 2 : ($formDeliveryType === 'door' ? 3 : 1);
|
||||
$addressId = (int)($formData['address_id'] ?? 0);
|
||||
$storeId = (int)($formData['store_id'] ?? 0);
|
||||
// 送礼订单:地址由领取人填写,下单阶段不选地址、不计运费
|
||||
$isGift = (int)($formData['is_gift'] ?? request()->param('is_gift', 0));
|
||||
$giftMark = trim((string)($formData['gift_mark'] ?? request()->param('gift_mark', '')));
|
||||
$storeInfo = [];
|
||||
$validatedAddressInfo = [];
|
||||
$normalDeliveryValidated = false;
|
||||
@ -1094,14 +1116,16 @@ class CenterOrderController
|
||||
$cartIdsForRemove,
|
||||
false,
|
||||
[],
|
||||
$formShippingType
|
||||
$formShippingType,
|
||||
$isGift
|
||||
);
|
||||
} else {
|
||||
$freshCartGroup = $cartServices->rebuildNormalCheckoutCart(
|
||||
$uid,
|
||||
$cartInfo,
|
||||
[],
|
||||
$formShippingType
|
||||
$formShippingType,
|
||||
$isGift
|
||||
);
|
||||
}
|
||||
$cartInfo = $freshCartGroup['valid'] ?? [];
|
||||
@ -1124,18 +1148,24 @@ class CenterOrderController
|
||||
$num = (int)($cartInfo[0]['cart_num'] ?? $num);
|
||||
$productInfo = $cartInfo[0]['productInfo'] ?? $productInfo;
|
||||
|
||||
// 重建后再次校验商品当前配送能力,并用当前地址计算运费。
|
||||
$deliveryContext = $this->validateNormalOrderDelivery(
|
||||
$cartInfo,
|
||||
$formDeliveryType,
|
||||
$uid,
|
||||
$addressId,
|
||||
$storeId,
|
||||
is_array($formData['service'] ?? null) ? $formData['service'] : []
|
||||
);
|
||||
$validatedAddressInfo = $deliveryContext['address_info'];
|
||||
$storeInfo = $deliveryContext['store_info'];
|
||||
$normalDeliveryValidated = true;
|
||||
// 送礼订单由领取人填收货信息,下单阶段不校验配送地址
|
||||
if ($isGift == 1) {
|
||||
$validatedAddressInfo = [];
|
||||
$normalDeliveryValidated = true;
|
||||
} else {
|
||||
// 重建后再次校验商品当前配送能力,并用当前地址计算运费。
|
||||
$deliveryContext = $this->validateNormalOrderDelivery(
|
||||
$cartInfo,
|
||||
$formDeliveryType,
|
||||
$uid,
|
||||
$addressId,
|
||||
$storeId,
|
||||
is_array($formData['service'] ?? null) ? $formData['service'] : []
|
||||
);
|
||||
$validatedAddressInfo = $deliveryContext['address_info'];
|
||||
$storeInfo = $deliveryContext['store_info'];
|
||||
$normalDeliveryValidated = true;
|
||||
}
|
||||
|
||||
/** @var OrderComputedServices $computedServices */
|
||||
$computedServices = app()->make(OrderComputedServices::class);
|
||||
@ -1145,7 +1175,7 @@ class CenterOrderController
|
||||
$validatedAddressInfo,
|
||||
$userInfo,
|
||||
$formShippingType,
|
||||
0
|
||||
$isGift
|
||||
);
|
||||
$cartInfo = $priceGroup['cartInfo'] ?? $cartInfo;
|
||||
$priceData = [
|
||||
@ -1160,18 +1190,23 @@ class CenterOrderController
|
||||
'memberPrice' => $priceGroup['memberPrice'] ?? 0,
|
||||
];
|
||||
} elseif ($productType === 'normal' && !$isActivityOrder) {
|
||||
// 非普通重建路径(兼容旧活动/特殊缓存)仍必须校验配送信息。
|
||||
$deliveryContext = $this->validateNormalOrderDelivery(
|
||||
$cartInfo,
|
||||
$formDeliveryType,
|
||||
$uid,
|
||||
$addressId,
|
||||
$storeId,
|
||||
is_array($formData['service'] ?? null) ? $formData['service'] : []
|
||||
);
|
||||
$validatedAddressInfo = $deliveryContext['address_info'];
|
||||
$storeInfo = $deliveryContext['store_info'];
|
||||
$normalDeliveryValidated = true;
|
||||
// 非普通重建路径(兼容旧活动/特殊缓存)仍必须校验配送信息;送礼订单除外
|
||||
if ($isGift == 1) {
|
||||
$validatedAddressInfo = [];
|
||||
$normalDeliveryValidated = true;
|
||||
} else {
|
||||
$deliveryContext = $this->validateNormalOrderDelivery(
|
||||
$cartInfo,
|
||||
$formDeliveryType,
|
||||
$uid,
|
||||
$addressId,
|
||||
$storeId,
|
||||
is_array($formData['service'] ?? null) ? $formData['service'] : []
|
||||
);
|
||||
$validatedAddressInfo = $deliveryContext['address_info'];
|
||||
$storeInfo = $deliveryContext['store_info'];
|
||||
$normalDeliveryValidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 记录实时原始总价(优惠券抵扣不影响 total_price 记录)
|
||||
@ -1337,7 +1372,7 @@ class CenterOrderController
|
||||
$computedServices = app()->make(OrderComputedServices::class);
|
||||
$computedResult = $computedServices->computedOrder(
|
||||
$uid, $userInfo, $tmpCartGroup, $addressId, $payType,
|
||||
$useIntegral, 0, false, $formShippingType, 0
|
||||
$useIntegral, 0, false, $formShippingType, $isGift
|
||||
);
|
||||
$finalPayPrice = $computedResult['pay_price'] ?? 0;
|
||||
$finalDeductionPrice = $computedResult['deduction_price'] ?? 0;
|
||||
@ -1606,7 +1641,14 @@ class CenterOrderController
|
||||
// 保存用户优惠券ID,取消或退款时用于恢复优惠券。
|
||||
'coupon_id' => (!$isActivityOrder && $couponId > 0) ? $couponId : 0,
|
||||
'activity_info' => $activityExtraInfo,
|
||||
],
|
||||
] + ($isGift == 1 ? [
|
||||
// 送礼订单:留言与领取记录挂在 extra_info,领取时写入 gift.gift_uid
|
||||
'gift_mark' => $giftMark,
|
||||
'gift' => [
|
||||
'gift_uid' => 0,
|
||||
'gift_key' => '',
|
||||
],
|
||||
] : []),
|
||||
'detail_route' => $handler->getOrderDetailRoute($orderNo),
|
||||
])) {
|
||||
throw new \RuntimeException('订单创建失败');
|
||||
@ -2194,7 +2236,7 @@ class CenterOrderController
|
||||
// 从中台表查询订单
|
||||
/** @var OrderCenterUserServices $centerServices */
|
||||
$centerServices = app()->make(OrderCenterUserServices::class);
|
||||
$centerOrder = $centerServices->getDetailByOrderNo($uni);
|
||||
$centerOrder = $centerServices->getDetailByOrderNo($uni, false);
|
||||
if (!$centerOrder) {
|
||||
CacheService::delete('PAY_LOCK_' . $uni);
|
||||
return app('json')->fail('订单不存在');
|
||||
@ -2356,7 +2398,7 @@ class CenterOrderController
|
||||
case 'order':
|
||||
/** @var OrderCenterUserServices $centerServices */
|
||||
$centerServices = app()->make(OrderCenterUserServices::class);
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo);
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo, false);
|
||||
if (!$order) {
|
||||
return app('json')->fail('您支付的订单不存在');
|
||||
}
|
||||
@ -2372,7 +2414,12 @@ class CenterOrderController
|
||||
$data['offline_postage'] = (int)sys_config('offline_postage', 0);
|
||||
$data['invalid_time'] = $time;
|
||||
$data['oid'] = $order['id'] ?? 0;
|
||||
$data['is_gift'] = 0;
|
||||
// 送礼订单:支付成功后跳支付结果页并展示送礼分享
|
||||
$payExtra = $order['extra_info'] ?? [];
|
||||
$payExtra = is_string($payExtra) ? (json_decode($payExtra, true) ?: []) : ($payExtra ?: []);
|
||||
$isGiftOrder = !empty($payExtra['gift']) || !empty($payExtra['gift_mark']) || (float)($order['gift_price'] ?? 0) > 0;
|
||||
$data['is_gift'] = $isGiftOrder ? 1 : 0;
|
||||
$data['gift_mark'] = (string)($payExtra['gift_mark'] ?? '');
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -2531,7 +2578,7 @@ class CenterOrderController
|
||||
{
|
||||
if (!strlen(trim($uni))) return app('json')->fail('参数错误');
|
||||
$orderCenterServices = app()->make(OrderCenterUserServices::class);
|
||||
$order = $orderCenterServices->getDetailByOrderNo($uni);
|
||||
$order = $orderCenterServices->getDetailByOrderNo($uni, false);
|
||||
if (!$order) return app('json')->fail('订单不存在');
|
||||
// 订单详情格式化(OrderOutServices 已中台化,tidyOrder 基于中台数据)
|
||||
$orderData = app()->make(OrderOutServices::class)->tidyOrder($order, true);
|
||||
@ -2627,7 +2674,7 @@ class CenterOrderController
|
||||
// 快照为空时回退到订单中台读取商品列表(兼容早期未填充快照的售后单)
|
||||
if (empty($info) && !empty($afterSale['order_no'])) {
|
||||
try {
|
||||
$sourceOrder = $orderCenterServices->getDetailByOrderNo($afterSale['order_no']);
|
||||
$sourceOrder = $orderCenterServices->getDetailByOrderNo($afterSale['order_no'], false);
|
||||
foreach ($sourceOrder['order_items'] ?? [] as $item) {
|
||||
$info[] = [
|
||||
'cart_num' => $item['cart_num'] ?? 1,
|
||||
@ -2664,7 +2711,8 @@ class CenterOrderController
|
||||
if ($ownedId === null) {
|
||||
return app('json')->fail('订单不存在');
|
||||
}
|
||||
$order = $orderCenterServices->getDetail($ownedId);
|
||||
// 物流接口自身会查询轨迹,这里只取订单/配送信息,避免重复请求第三方
|
||||
$order = $orderCenterServices->getDetail($ownedId, false);
|
||||
} catch (\Throwable $e) {
|
||||
return app('json')->fail('订单不存在');
|
||||
}
|
||||
@ -2978,7 +3026,7 @@ class CenterOrderController
|
||||
}
|
||||
$uid = (int)request()->uid();
|
||||
$orderCenterServices = app()->make(OrderCenterUserServices::class);
|
||||
$res = $orderCenterServices->receiveGift($uid, $oid, $gift_key, $shipping_type, $name, $phone, $address_id, $store_id);
|
||||
$res = $orderCenterServices->receiveGift($uid, (int)$oid, $gift_key, (int)$shipping_type, $name, $phone, (int)$address_id, (int)$store_id);
|
||||
if ($res) {
|
||||
return app('json')->success('领取成功', ['status' => 1]);
|
||||
} else {
|
||||
@ -2997,7 +3045,8 @@ class CenterOrderController
|
||||
return app('json')->fail('缺少参数');
|
||||
}
|
||||
$orderCenterServices = app()->make(OrderCenterUserServices::class);
|
||||
return app('json')->success($orderCenterServices->giftDetail($oid));
|
||||
// 路由参数为字符串,服务层签名要求 int
|
||||
return app('json')->success($orderCenterServices->giftDetail((int)$oid));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3016,7 +3065,7 @@ class CenterOrderController
|
||||
try {
|
||||
/** @var OrderCenterUserServices $centerServices */
|
||||
$centerServices = app()->make(OrderCenterUserServices::class);
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo);
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo, false);
|
||||
|
||||
// 从 order_items 获取商品明细
|
||||
$orderItems = $order['order_items'] ?? [];
|
||||
@ -3124,7 +3173,7 @@ class CenterOrderController
|
||||
/** @var OrderCenterUserServices $centerServices */
|
||||
$centerServices = app()->make(OrderCenterUserServices::class);
|
||||
try {
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo);
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo, false);
|
||||
} catch (\Throwable $e) {
|
||||
return app('json')->fail($e->getMessage());
|
||||
}
|
||||
@ -3165,7 +3214,7 @@ class CenterOrderController
|
||||
/** @var OrderCenterUserServices $centerServices */
|
||||
$centerServices = app()->make(OrderCenterUserServices::class);
|
||||
try {
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo);
|
||||
$order = $centerServices->getDetailByOrderNo($orderNo, false);
|
||||
} catch (\Throwable $e) {
|
||||
return app('json')->fail('订单不存在');
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ class StoreOrderInvoiceController
|
||||
$uid = (int)$request->uid();
|
||||
try {
|
||||
// 订单中台统一详情(含状态、支付、配送、发票等信息)
|
||||
$order = $services->getDetailByOrderNo($uni);
|
||||
$order = $services->getDetailByOrderNo($uni, false);
|
||||
} catch (\Throwable) {
|
||||
return app('json')->fail('订单不存在');
|
||||
}
|
||||
|
||||
@ -81,6 +81,20 @@ class OrderCenterDao extends BaseDao
|
||||
return $this->getModel()->where($where)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户订单范围查询:本人下单的订单 + 本人领取的礼品订单
|
||||
* 两个条件均为索引字段(idx_uid / idx_gift_uid),支持索引合并
|
||||
* @param int $uid 用户ID
|
||||
* @return mixed 返回查询对象
|
||||
*/
|
||||
public function userOrderQuery(int $uid)
|
||||
{
|
||||
return $this->getModel()->whereRaw(
|
||||
'(uid = :owner_uid OR gift_uid = :gift_uid)',
|
||||
['owner_uid' => $uid, 'gift_uid' => $uid]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存或更新记录
|
||||
* 根据条件判断是新增还是更新,支持幂等性操作
|
||||
|
||||
@ -15,6 +15,7 @@ use crmeb\traits\ModelTrait;
|
||||
* 本模型保留常量别名以兼容已有代码,新代码请直接引用接口常量或从 Adapter 继承
|
||||
*
|
||||
* @method static $this where($field, $operator = null, $value = null)
|
||||
* @property int $gift_uid 礼品领取人UID(0=未领取;领取后用于领取人订单列表/详情权限)
|
||||
* @property int $pay_counted 购买次数已记账标记,与用户计数同事务提交
|
||||
* @property int $take_rewards_given 收货奖励已记账标记,防止重复发奖
|
||||
* @property array|string|null $refund_reward_accounted 退款奖励累计已处理目标,含已核对短缺
|
||||
|
||||
@ -420,26 +420,28 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
/**
|
||||
* 根据订单号获取订单详情
|
||||
* @param string $orderNo 订单号
|
||||
* @param bool $withRemoteLogistics 是否同步请求第三方物流轨迹
|
||||
* @return array 返回订单详情数据
|
||||
* @throws \RuntimeException 订单不存在时抛出异常
|
||||
*/
|
||||
public function getDetailByOrderNo(string $orderNo): array
|
||||
public function getDetailByOrderNo(string $orderNo, bool $withRemoteLogistics = true): array
|
||||
{
|
||||
$detail = $this->dao->get(['order_no' => $orderNo], ['id']);
|
||||
if (!$detail) {
|
||||
throw new \RuntimeException('订单不存在');
|
||||
}
|
||||
return $this->getDetail((int)$detail['id']);
|
||||
return $this->getDetail((int)$detail['id'], $withRemoteLogistics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
* 通过事件机制允许扩展模块注入详情数据
|
||||
* @param int $id 订单中台记录ID
|
||||
* @param bool $withRemoteLogistics 是否同步请求第三方物流轨迹(详情首屏可关闭,改由前端异步查询)
|
||||
* @return array 返回订单详情数据
|
||||
* @throws \RuntimeException 订单不存在时抛出异常
|
||||
*/
|
||||
public function getDetail(int $id): array
|
||||
public function getDetail(int $id, bool $withRemoteLogistics = true): array
|
||||
{
|
||||
$detail = $this->dao->get(['id' => $id]);
|
||||
if (!$detail) {
|
||||
@ -584,7 +586,7 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
|
||||
// 加载物流轨迹扩展数据(已发货订单调用物流查询驱动获取 tracks)
|
||||
// 前端 LogisticsTimeline 组件据此渲染物流轨迹卡片,无需点"查看物流"单独查询
|
||||
$this->loadLogisticsExtension($detail);
|
||||
$this->loadLogisticsExtension($detail, $withRemoteLogistics);
|
||||
|
||||
// 加载订单完成态权益扩展数据(积分奖励/领券入口,系统内置能力,中台自行组装)
|
||||
$this->loadOrderRewardExtension($detail);
|
||||
@ -601,6 +603,24 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
return $detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判定是否送礼订单
|
||||
* extra_info.gift / gift_mark 为下单写入的送礼标记,gift_price 为兼容旧单兜底
|
||||
* @param array $detail 订单详情(含 extra_info)
|
||||
* @return int 1=送礼订单 0=普通订单
|
||||
*/
|
||||
protected function resolveIsGiftFlag(array $detail): int
|
||||
{
|
||||
$extra = $detail['extra_info'] ?? [];
|
||||
if (!is_array($extra)) {
|
||||
$extra = [];
|
||||
}
|
||||
if (!empty($extra['gift']) || isset($extra['gift_mark'])) {
|
||||
return 1;
|
||||
}
|
||||
return (float)($detail['gift_price'] ?? 0) > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装价格明细数组
|
||||
* 将金额实体列转为 {label, value, type} 数组格式
|
||||
@ -659,6 +679,14 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
'type' => 'vip',
|
||||
];
|
||||
}
|
||||
// 礼品附加费(送礼订单按件累计)
|
||||
if (!empty($detail['gift_price']) && (float)$detail['gift_price'] > 0) {
|
||||
$items[] = [
|
||||
'label' => '礼品附加费',
|
||||
'value' => '+' . number_format((float)$detail['gift_price'], 2, '.', ''),
|
||||
'type' => 'gift',
|
||||
];
|
||||
}
|
||||
|
||||
// 价格明细扩展缝口:交易模式/产品类型插件经事件追加金额项(如服务费/上门费)
|
||||
$pricingEvent = ['items' => &$items, 'detail' => $detail];
|
||||
@ -775,6 +803,7 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
|
||||
// 40. 配送/自提卡片(仅实物商品)
|
||||
// 上门服务订单不渲染地址卡片,预约信息由 service_appointment section(118)展示
|
||||
// 送礼订单的可见性由查看者身份决定(购买者隐藏、领取人展示),在 applyGiftViewerPolicy 中裁剪
|
||||
if ($productType === 'normal' && !($detail['delivery']['is_door_service'] ?? false)) {
|
||||
$deliveryComponent = $isPickup ? 'store_card' : 'address_card';
|
||||
$deliveryProps = $isPickup
|
||||
@ -1090,6 +1119,11 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
'detail_route' => $detail['detail_route'] ?? '',
|
||||
'admin_detail_url'=> $detail['admin_detail_url'] ?? '',
|
||||
'activity_detail_url' => $detail['activity_detail_url'] ?? '',
|
||||
// 送礼订单:1=购买礼物 0=普通单;gift_uid 非 0 表示已被领取
|
||||
'is_gift' => $this->resolveIsGiftFlag($detail),
|
||||
'gift_uid' => (int)($detail['gift_uid'] ?? $detail['extra_info']['gift']['gift_uid'] ?? ($detail['extra_info']['gift_uid'] ?? 0)),
|
||||
'gift_mark' => (string)($detail['extra_info']['gift_mark'] ?? ''),
|
||||
'gift_price' => $detail['gift_price'] ?? '0.00',
|
||||
];
|
||||
|
||||
// 时间线
|
||||
@ -1357,12 +1391,13 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
* 由中台基础 section + 插件 extend_sections 自声明 section 组成
|
||||
* - page_config.extensions:扩展数据(含 extra_info 深度定制数据)
|
||||
* @param int $id 订单中台记录ID
|
||||
* @param bool $withRemoteLogistics 是否同步请求第三方物流轨迹(首屏可关闭,由前端异步补齐)
|
||||
* @return array 返回 {page_config: {sections: [...], extensions: {...}}}
|
||||
*/
|
||||
public function getDetailWithConfig(int $id): array
|
||||
public function getDetailWithConfig(int $id, bool $withRemoteLogistics = true): array
|
||||
{
|
||||
// 1. 获取扁平详情数据(复用现有 getDetail 逻辑)
|
||||
$flatDetail = $this->getDetail($id);
|
||||
$flatDetail = $this->getDetail($id, $withRemoteLogistics);
|
||||
|
||||
// 2. 组装分组数据(内部使用:buildDefaultPageConfig 依赖全量分组生成 sections)
|
||||
$data = $this->buildGroupedData($flatDetail);
|
||||
@ -1396,16 +1431,17 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
/**
|
||||
* 根据订单号获取带声明式渲染配置的订单详情
|
||||
* @param string $orderNo 订单号
|
||||
* @param bool $withRemoteLogistics 是否同步请求第三方物流轨迹
|
||||
* @return array 返回 {page_config, data}
|
||||
* @throws \RuntimeException 订单不存在时抛出异常
|
||||
*/
|
||||
public function getDetailWithConfigByOrderNo(string $orderNo): array
|
||||
public function getDetailWithConfigByOrderNo(string $orderNo, bool $withRemoteLogistics = true): array
|
||||
{
|
||||
$detail = $this->dao->get(['order_no' => $orderNo], ['id']);
|
||||
if (!$detail) {
|
||||
throw new \RuntimeException('订单不存在');
|
||||
}
|
||||
return $this->getDetailWithConfig((int)$detail['id']);
|
||||
return $this->getDetailWithConfig((int)$detail['id'], $withRemoteLogistics);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1647,16 +1683,17 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
|
||||
/**
|
||||
* 加载物流轨迹扩展数据
|
||||
* 已发货且填了快递单号的订单,调用物流查询驱动获取 tracks,注入 extend.logistics
|
||||
* 已发货且填了快递单号的订单,读取物流轨迹(默认允许同步查询第三方;详情首屏可只读缓存)并注入 extend.logistics
|
||||
* 前端 LogisticsTimeline 组件据此渲染物流轨迹卡片,用户无需点"查看物流"单独查询
|
||||
* 数据流:本方法写入 $detail['extend']['logistics']
|
||||
* → buildGroupedData() 合并到 extensions['logistics']
|
||||
* → page_config 中 component='timeline' + key='logistics' → 复合 key 'timeline:logistics'
|
||||
* → 前端 LogisticsTimeline 渲染 delivery_name/delivery_id + OrderTimeline 遍历 tracks
|
||||
* @param array $detail 订单详情数据(引用传递)
|
||||
* @param bool $allowRemote 是否允许同步请求第三方物流接口;false 时仅读取已有缓存,避免详情首屏被慢请求阻塞
|
||||
* @return void
|
||||
*/
|
||||
protected function loadLogisticsExtension(array &$detail): void
|
||||
protected function loadLogisticsExtension(array &$detail, bool $allowRemote = true): void
|
||||
{
|
||||
// 仅处理有快递单号的订单(已发货且填写了物流单号)
|
||||
$deliveryId = (string)($detail['delivery_id'] ?? '');
|
||||
@ -1690,7 +1727,10 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
|
||||
/** @var ExpressServices $expressServices */
|
||||
$expressServices = $this->app->make(ExpressServices::class);
|
||||
$rawTracks = $expressServices->query($cacheName, $deliveryId, $deliveryCode, $phone);
|
||||
// 详情首屏只读缓存;轨迹按需由前端调用 order/express 异步补齐,避免第三方慢请求阻塞详情
|
||||
$rawTracks = $allowRemote
|
||||
? $expressServices->query($cacheName, $deliveryId, $deliveryCode, $phone)
|
||||
: $expressServices->getCachedTracks($cacheName);
|
||||
|
||||
// 归一化轨迹字段为前端统一格式 {title, time, desc}
|
||||
// 驱动返回格式差异:一号通 {time,status} / 阿里云 {time,context} / 其他兼容
|
||||
|
||||
@ -25,15 +25,106 @@ use think\facade\Log;
|
||||
class OrderCenterUserServices extends OrderCenterServices
|
||||
{
|
||||
/**
|
||||
* 查询当前用户归属的订单中台记录 ID
|
||||
* 查询当前用户可访问的订单中台记录 ID
|
||||
*
|
||||
* 可访问范围:
|
||||
* 1. 订单归属人(uid 匹配);
|
||||
* 2. 送礼订单的领取人(gift_uid 匹配)——领取礼物后需查看物流、核销与领取进度。
|
||||
*
|
||||
* @param string $orderNo 订单号
|
||||
* @param int $uid 当前用户 ID
|
||||
* @return int|null 不属于当前用户或订单不存在时返回 null
|
||||
* @return int|null 无权访问或订单不存在时返回 null
|
||||
*/
|
||||
public function getOwnedOrderIdByNo(string $orderNo, int $uid): ?int
|
||||
{
|
||||
$id = $this->dao->value(['order_no' => $orderNo, 'uid' => $uid], 'id');
|
||||
return $id ? (int)$id : null;
|
||||
$order = $this->dao->get(['order_no' => $orderNo], ['id', 'uid', 'gift_uid', 'extra_info']);
|
||||
if (!$order) {
|
||||
return null;
|
||||
}
|
||||
$order = is_array($order) ? $order : $order->toArray();
|
||||
if ((int)($order['uid'] ?? 0) === $uid) {
|
||||
return (int)$order['id'];
|
||||
}
|
||||
// 送礼订单:领取人可查看该订单(优先实体列,历史数据回退 extra_info)
|
||||
$extra = $this->parseExtraInfo($order['extra_info'] ?? null);
|
||||
$giftUid = (int)($order['gift_uid'] ?? $extra['gift']['gift_uid'] ?? ($extra['gift_uid'] ?? 0));
|
||||
return $giftUid > 0 && $giftUid === $uid ? (int)$order['id'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 送礼订单详情按查看者身份裁剪渲染配置
|
||||
*
|
||||
* - 购买者:隐藏收货信息、物流轨迹,以及确认收货/申请退款/查看物流按钮;
|
||||
* - 领取人:只读视角,保留收货信息、物流轨迹与确认收货/查看物流按钮,
|
||||
* 隐藏金额/支付/发票等下单人信息。
|
||||
* 非送礼订单不做任何处理。
|
||||
*
|
||||
* @param array $result getDetailWithConfig 返回的 page_config 结构
|
||||
* @param int $viewerUid 查看者用户 ID
|
||||
* @return array 裁剪后的结构
|
||||
*/
|
||||
public function applyGiftViewerPolicy(array $result, int $viewerUid): array
|
||||
{
|
||||
$sections = $result['page_config']['sections'] ?? [];
|
||||
if (empty($sections)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 从订单信息 section 读取送礼标识、下单人与领取人
|
||||
$fields = [];
|
||||
foreach ($sections as $section) {
|
||||
if (($section['key'] ?? '') === 'order_info') {
|
||||
$fields = $section['props']['fields'] ?? [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((int)($fields['is_gift'] ?? 0) !== 1) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$ownerUid = (int)($fields['uid'] ?? 0);
|
||||
$giftUid = (int)($fields['gift_uid'] ?? 0);
|
||||
$isReceiver = $ownerUid !== $viewerUid && $giftUid === $viewerUid;
|
||||
|
||||
if ($isReceiver) {
|
||||
// 领取人:隐藏金额/支付/发票与下单人权益入口,保留收货信息、物流轨迹
|
||||
$sections = array_values(array_filter(
|
||||
$sections,
|
||||
fn($section) => !in_array($section['key'] ?? '', ['pricing', 'payment', 'invoice', 'coupon_gift', 'points_reward'], true)
|
||||
));
|
||||
$result['page_config']['extensions']['viewer_role'] = 'gift_receiver';
|
||||
$visibleActions = ['confirmReceive', 'viewLogistics'];
|
||||
$hiddenActions = [];
|
||||
} else {
|
||||
// 购买者:不展示收货信息与物流轨迹,隐藏确认收货/申请退款/查看物流
|
||||
$sections = array_values(array_filter(
|
||||
$sections,
|
||||
fn($section) => !in_array($section['key'] ?? '', ['delivery', 'logistics'], true)
|
||||
));
|
||||
$visibleActions = [];
|
||||
$hiddenActions = ['confirmReceive', 'applyRefund', 'viewLogistics'];
|
||||
}
|
||||
|
||||
// 按查看者身份过滤底部操作按钮
|
||||
foreach ($sections as &$section) {
|
||||
if (($section['key'] ?? '') !== 'buttons') {
|
||||
continue;
|
||||
}
|
||||
$section['props']['buttons'] = array_values(array_filter(
|
||||
$section['props']['buttons'] ?? [],
|
||||
function ($button) use ($isReceiver, $visibleActions, $hiddenActions) {
|
||||
$action = (string)($button['action'] ?? '');
|
||||
return $isReceiver
|
||||
? in_array($action, $visibleActions, true)
|
||||
: !in_array($action, $hiddenActions, true);
|
||||
}
|
||||
));
|
||||
$section['visible'] = !empty($section['props']['buttons']);
|
||||
}
|
||||
unset($section);
|
||||
|
||||
$result['page_config']['sections'] = $sections;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,7 +139,6 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
public function getList(int $uid, array $where, int $page, int $limit): array
|
||||
{
|
||||
$searchWhere = [
|
||||
['uid', '=', $uid],
|
||||
['is_del', '=', 0],
|
||||
];
|
||||
// 订单大类筛选
|
||||
@ -84,15 +174,18 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
$searchWhere[] = ['take_time', '=', (int)$where['take_time']];
|
||||
}
|
||||
|
||||
$list = $this->dao->selectList(
|
||||
$searchWhere,
|
||||
'order_no, source_type, source_id, order_category, product_type, activity_type, status_code,
|
||||
// 列表范围:本人下单 + 本人领取的礼品订单(userOrderQuery 内按 uid/gift_uid 索引查询)
|
||||
$list = $this->dao->userOrderQuery($uid)
|
||||
->where($searchWhere)
|
||||
->field('uid, gift_uid, order_no, source_type, source_id, order_category, product_type, activity_type, status_code,
|
||||
total_price, pay_price, total_num, title, image,
|
||||
subtitle, pay_type, shipping_type, extra_info, detail_route,
|
||||
add_time, pay_time, delivery_time, take_time',
|
||||
$page, $limit, 'id DESC'
|
||||
);
|
||||
$total = $this->dao->count($searchWhere);
|
||||
add_time, pay_time, delivery_time, take_time, gift_price')
|
||||
->page($page, $limit)
|
||||
->order('id DESC')
|
||||
->select()
|
||||
->toArray();
|
||||
$total = (int)$this->dao->userOrderQuery($uid)->where($searchWhere)->count();
|
||||
|
||||
// 批量加载扩展表子状态,供列表页按钮和扩展文案精确判断
|
||||
$sourceStatusMap = $this->loadSourceStatusesForList($list);
|
||||
@ -114,6 +207,18 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
// type_label: 订单类型短标签(秒杀/积分/充值/网盘/卡密等),用于列表项角标展示
|
||||
// 优先级:活动类型 > 订单大类 > 产品类型,各维度均通过注册中心/事件动态获取,新增类型无需改前端
|
||||
$item['type_label'] = $this->resolveTypeLabel($item);
|
||||
// 送礼订单标识与礼品附加费,供列表角标与金额展示
|
||||
$itemExtra = $item['extra_info'] ?? [];
|
||||
$item['is_gift'] = (!empty($itemExtra['gift']) || isset($itemExtra['gift_mark'])) ? 1 : 0;
|
||||
$item['gift_price'] = $item['gift_price'] ?? '0.00';
|
||||
// 礼物领取人视角(非下单人):只读列表,仅保留查看物流,避免出现退款/取消/删除等下单人操作
|
||||
$item['is_gift_receiver'] = ((int)($item['uid'] ?? 0) !== $uid && (int)($item['gift_uid'] ?? 0) === $uid) ? 1 : 0;
|
||||
if ($item['is_gift_receiver']) {
|
||||
$item['order_buttons'] = array_values(array_filter(
|
||||
$item['order_buttons'] ?? [],
|
||||
fn($button) => ($button['action'] ?? '') === 'viewLogistics'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 为 viewAfterSale 按钮批量注入 after_sale_no(与详情接口 getOrderButtonsFromHandler 同口径)
|
||||
@ -319,27 +424,27 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
*/
|
||||
public function getUserStatistics(int $uid): array
|
||||
{
|
||||
// 缓存 key 带结构版本 v3 + 失效版本号:订单状态流转经 syncStatus/updateRecord 递增
|
||||
// order_center:stat:ver 强制换 key 刷新(结构变更升级 v3),旧 key 靠自身 TTL 自然过期
|
||||
// 缓存 key 带结构版本 v4 + 失效版本号:订单状态流转经 syncStatus/updateRecord 递增
|
||||
// order_center:stat:ver 强制换 key 刷新(结构变更升级 v4:统计范围含领取的礼品订单),旧 key 靠自身 TTL 自然过期
|
||||
$ver = (int) app()->cache->get('order_center:stat:ver');
|
||||
$cacheKey = 'order_center:user_stat:v3:' . $uid . ':' . $ver;
|
||||
$cacheKey = 'order_center:user_stat:v4:' . $uid . ':' . $ver;
|
||||
|
||||
return app()->cache->remember($cacheKey, function () use ($uid) {
|
||||
$baseWhere = [
|
||||
['uid', '=', $uid],
|
||||
['is_del', '=', 0],
|
||||
];
|
||||
// 统计范围:本人下单 + 本人领取的礼品订单(与订单列表口径一致)
|
||||
// 金额只累计本人实际支付金额,收到的礼物不计入个人消费
|
||||
$baseQuery = function () use ($uid) {
|
||||
return $this->dao->userOrderQuery($uid)->where('is_del', 0);
|
||||
};
|
||||
$amountField = 'SUM(CASE WHEN uid = ' . $uid . ' THEN pay_price ELSE 0 END) as amount';
|
||||
|
||||
$statusStats = $this->dao->getDb()
|
||||
->where($baseWhere)
|
||||
->field('status_code, COUNT(*) as count, SUM(pay_price) as amount')
|
||||
$statusStats = $baseQuery()
|
||||
->field('status_code, COUNT(*) as count, ' . $amountField)
|
||||
->group('status_code')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$all = $this->dao->getDb()
|
||||
->where($baseWhere)
|
||||
->field('COUNT(*) as count, SUM(pay_price) as amount')
|
||||
$all = $baseQuery()
|
||||
->field('COUNT(*) as count, ' . $amountField)
|
||||
->find();
|
||||
|
||||
$statusCounts = [];
|
||||
@ -353,10 +458,9 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
|
||||
// STATUS_PROCESSING 按产品类型拆分:非实物业态=待使用(虚拟/卡密/酒店/门票等)
|
||||
// 实物(normal)进行中再按发货状态细分为待发货/待收货,与订单列表 tab 及角标口径一致
|
||||
$processingByType = $this->dao->getDb()
|
||||
->where($baseWhere)
|
||||
$processingByType = $baseQuery()
|
||||
->where('status_code', OrderCenter::STATUS_PROCESSING)
|
||||
->field('product_type, COUNT(*) as count, SUM(pay_price) as amount')
|
||||
->field('product_type, COUNT(*) as count, ' . $amountField)
|
||||
->group('product_type')
|
||||
->select()
|
||||
->toArray();
|
||||
@ -378,20 +482,18 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
}
|
||||
|
||||
// 实物进行中按发货状态拆分:待发货=未发货(delivery_time=0),待收货=已发货未收货
|
||||
$pendingShip = $this->dao->getDb()
|
||||
->where($baseWhere)
|
||||
$pendingShip = $baseQuery()
|
||||
->where('status_code', OrderCenter::STATUS_PROCESSING)
|
||||
->where('product_type', 'normal')
|
||||
->where('delivery_time', 0)
|
||||
->field('COUNT(*) as count, SUM(pay_price) as amount')
|
||||
->field('COUNT(*) as count, ' . $amountField)
|
||||
->find();
|
||||
$pendingReceipt = $this->dao->getDb()
|
||||
->where($baseWhere)
|
||||
$pendingReceipt = $baseQuery()
|
||||
->where('status_code', OrderCenter::STATUS_PROCESSING)
|
||||
->where('product_type', 'normal')
|
||||
->where('delivery_time', '<>', 0)
|
||||
->where('take_time', 0)
|
||||
->field('COUNT(*) as count, SUM(pay_price) as amount')
|
||||
->field('COUNT(*) as count, ' . $amountField)
|
||||
->find();
|
||||
|
||||
$grouped = [
|
||||
@ -540,7 +642,8 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
*/
|
||||
public function getFriendDetail(int $orderId, int $uid)
|
||||
{
|
||||
$detail = $this->getDetail($orderId);
|
||||
// 代付详情不需要物流轨迹,跳过第三方物流查询
|
||||
$detail = $this->getDetail($orderId, false);
|
||||
if (!$detail) {
|
||||
throw new ApiException('订单不存在');
|
||||
}
|
||||
@ -586,7 +689,8 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
*/
|
||||
public function refundCartInfoList(array $cartIds = [], int $id = 0)
|
||||
{
|
||||
$detail = $this->getDetail($id);
|
||||
// 退款商品列表不需要物流轨迹,跳过第三方物流查询
|
||||
$detail = $this->getDetail($id, false);
|
||||
if (!$detail) {
|
||||
throw new ApiException('订单不存在');
|
||||
}
|
||||
@ -676,7 +780,7 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
throw new ApiException('参数错误');
|
||||
}
|
||||
|
||||
$detail = $this->getDetailByOrderNo($uni);
|
||||
$detail = $this->getDetailByOrderNo($uni, false);
|
||||
if (!$detail || (int)($detail['uid'] ?? 0) !== $uid) {
|
||||
throw new ApiException('订单不存在');
|
||||
}
|
||||
@ -798,7 +902,8 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
*/
|
||||
public function giftDetail(int $oid)
|
||||
{
|
||||
$detail = $this->getDetail($oid);
|
||||
// 礼品详情不需要物流轨迹,跳过第三方物流查询
|
||||
$detail = $this->getDetail($oid, false);
|
||||
if (!$detail) {
|
||||
throw new ApiException('订单不存在');
|
||||
}
|
||||
@ -807,8 +912,44 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
$userServices = app()->make(UserServices::class);
|
||||
$userInfo = $userServices->get($detail['uid']);
|
||||
|
||||
// 商品快照:中台 order_items 为扁平结构,旧版页面(orderGoods 组件)按 productInfo 嵌套结构渲染,
|
||||
// 这里在保留扁平字段的同时补齐 productInfo 等旧版字段,供礼物领取页等旧页面直接复用
|
||||
$orderItems = is_array($detail['order_items'] ?? null) ? $detail['order_items'] : [];
|
||||
$cartInfo = [];
|
||||
foreach ($orderItems as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
// 已是旧版结构时直接透传
|
||||
if (!empty($item['productInfo']) && is_array($item['productInfo'])) {
|
||||
$cartInfo[] = $item;
|
||||
continue;
|
||||
}
|
||||
$attr = is_array($item['attr_info'] ?? null) ? $item['attr_info'] : [];
|
||||
$cartInfo[] = array_merge($item, [
|
||||
'unique' => (string)($item['unique'] ?? ($attr['unique'] ?? '')),
|
||||
'is_valid' => $item['is_valid'] ?? true,
|
||||
'is_reply' => (int)($item['is_reply'] ?? 0),
|
||||
'refund_num' => (int)($item['refund_num'] ?? 0),
|
||||
'productInfo' => [
|
||||
'product_id' => (int)($item['product_id'] ?? ($attr['product_id'] ?? 0)),
|
||||
'store_name' => (string)($item['store_name'] ?? ''),
|
||||
'image' => (string)($item['image'] ?? ($attr['image'] ?? '')),
|
||||
'price' => (string)($item['price'] ?? ($attr['price'] ?? '0.00')),
|
||||
'ot_price' => (string)($attr['ot_price'] ?? ($item['ot_price'] ?? '0.00')),
|
||||
'store_mention' => (int)($item['store_mention'] ?? 1),
|
||||
'virtual_type' => (int)($item['virtual_type'] ?? 0),
|
||||
'time_id' => (int)($item['time_id'] ?? 0),
|
||||
'attrInfo' => $attr ? array_merge($attr, [
|
||||
'image' => (string)($attr['image'] ?? ($item['image'] ?? '')),
|
||||
'price' => (string)($attr['price'] ?? ($item['price'] ?? '0.00')),
|
||||
'suk' => (string)($attr['suk'] ?? ($item['sku'] ?? '')),
|
||||
]) : null,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
// 从 extra_info 提取物流方式信息
|
||||
$cartInfo = $detail['order_items'] ?? [];
|
||||
$arr = [];
|
||||
foreach ($cartInfo as $cartInfoItem) {
|
||||
$arr[] = $cartInfoItem['productInfo']['logistics'] ?? '';
|
||||
@ -840,7 +981,8 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
'pay_price' => $detail['pay_price'] ?? '0.00',
|
||||
'gift_key' => $giftKey,
|
||||
'gift_mark' => $extraInfo['gift_mark'] ?? '',
|
||||
'gift_uid' => $extraInfo['gift_uid'] ?? 0,
|
||||
'gift_uid' => (int)($detail['gift_uid'] ?? $extraInfo['gift']['gift_uid'] ?? ($extraInfo['gift_uid'] ?? 0)),
|
||||
'is_gift' => (!empty($extraInfo['gift']) || isset($extraInfo['gift_mark'])) ? 1 : 0,
|
||||
'refund_status' => $detail['refund_status'] ?? 0,
|
||||
'type' => $type,
|
||||
'store_self_mention' => (int)sys_config('store_self_mention') ?? 0,
|
||||
@ -908,9 +1050,12 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
}
|
||||
|
||||
// 更新 extra_info 中的礼物和联系人信息
|
||||
// gift_time 供订单详情「礼品已领」进度节点展示;本人重复领取时保留首次领取时间
|
||||
$giftTime = (int)($extraInfo['gift']['gift_time'] ?? 0) ?: time();
|
||||
$extraInfo['gift'] = [
|
||||
'gift_uid' => $uid,
|
||||
'gift_key' => $giftKey,
|
||||
'gift_uid' => $uid,
|
||||
'gift_key' => $giftKey,
|
||||
'gift_time' => $giftTime,
|
||||
];
|
||||
// 联系人信息
|
||||
$extraInfo['contact'] = [
|
||||
@ -927,8 +1072,12 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
'shipping_type' => $shippingType,
|
||||
'store_id' => $storeId,
|
||||
'verify_code' => $verifyCode,
|
||||
// 领取人写入实体列,供用户订单列表按 gift_uid 索引查询
|
||||
'gift_uid' => $uid,
|
||||
'extra_info' => $this->encodeExtraInfo($extraInfo),
|
||||
]);
|
||||
// 领取后礼物订单进入领取人订单列表,清除统计缓存
|
||||
$this->clearStatCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -83,13 +83,24 @@ class OrderTakeServices extends BaseService
|
||||
*/
|
||||
public function takeOrder(string $uni, int $uid)
|
||||
{
|
||||
$order = $this->dao->getOne(['order_no' => $uni, 'uid' => $uid]);
|
||||
$order = $this->dao->getOne(['order_no' => $uni]);
|
||||
if (!$order) {
|
||||
throw new ApiException('订单不存在');
|
||||
}
|
||||
if (is_object($order)) {
|
||||
$order = $order->toArray();
|
||||
}
|
||||
// 订单归属人可确认收货;送礼订单的领取人也可确认收货
|
||||
if ((int)($order['uid'] ?? 0) !== $uid) {
|
||||
$extra = $order['extra_info'] ?? [];
|
||||
if (is_string($extra)) {
|
||||
$extra = json_decode($extra, true) ?: [];
|
||||
}
|
||||
$giftUid = (int)($order['gift_uid'] ?? $extra['gift']['gift_uid'] ?? ($extra['gift_uid'] ?? 0));
|
||||
if ($giftUid !== $uid) {
|
||||
throw new ApiException('订单不存在');
|
||||
}
|
||||
}
|
||||
// 售后拦截:存在处理中售后单不可确认收货(售后引擎统一判断)
|
||||
$orderIsRefund = app()->make(AfterSaleServices::class)->hasActiveByOrderId((int)$order['id']);
|
||||
if($orderIsRefund){
|
||||
|
||||
@ -195,6 +195,48 @@ class ExpressServices extends BaseService
|
||||
return $this->dao->getExpressList($where, 'id,name,code,partner_id,partner_key,net,account,key,net_name', 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前启用的物流查询驱动
|
||||
* @return array 驱动配置;未启用或所属插件停用时返回空数组
|
||||
*/
|
||||
protected function resolveEnabledQueryDriver(): array
|
||||
{
|
||||
/** @var LogisticsDriverServices $logisticsDriverServices */
|
||||
$logisticsDriverServices = app()->make(LogisticsDriverServices::class);
|
||||
$enabledDriver = $logisticsDriverServices->getEnabledDriver() ?? [];
|
||||
$driverKey = (string)($enabledDriver['driver_key'] ?? '');
|
||||
$ownerPluginId = (string)($enabledDriver['owner_plugin_id'] ?? '');
|
||||
// 插件或查询驱动关闭时,即使轨迹缓存仍存在也不返回旧数据。
|
||||
if ($driverKey === '' || $ownerPluginId === '' || !ModuleStateChecker::isDriverActive($ownerPluginId)) {
|
||||
return [];
|
||||
}
|
||||
if (!in_array($driverKey, ['aliyun_express', 'yihaotong'], true)) {
|
||||
throw new \RuntimeException('物流查询驱动不支持');
|
||||
}
|
||||
return $enabledDriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅读取物流轨迹缓存,不请求第三方服务
|
||||
* 用于订单详情首屏:避免第三方物流接口的慢请求阻塞详情返回
|
||||
* @param string $cacheName 订单号 + 快递单号
|
||||
* @return array 已缓存的轨迹数据,未命中返回空数组
|
||||
*/
|
||||
public function getCachedTracks(string $cacheName): array
|
||||
{
|
||||
try {
|
||||
$enabledDriver = $this->resolveEnabledQueryDriver();
|
||||
$driverKey = (string)($enabledDriver['driver_key'] ?? '');
|
||||
if ($driverKey === '') {
|
||||
return [];
|
||||
}
|
||||
$data = CacheService::get('logistics:' . $driverKey . ':' . $cacheName, null);
|
||||
return is_array($data) ? $data : [];
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 物流公司查询
|
||||
* @param string $cacheName
|
||||
@ -209,23 +251,21 @@ class ExpressServices extends BaseService
|
||||
$com = (string)($com ?? '');
|
||||
$phone = (string)($phone ?? '');
|
||||
|
||||
/** @var LogisticsDriverServices $logisticsDriverServices */
|
||||
$logisticsDriverServices = app()->make(LogisticsDriverServices::class);
|
||||
$enabledDriver = $logisticsDriverServices->getEnabledDriver() ?? [];
|
||||
$enabledDriver = $this->resolveEnabledQueryDriver();
|
||||
$driverKey = (string)($enabledDriver['driver_key'] ?? '');
|
||||
$ownerPluginId = (string)($enabledDriver['owner_plugin_id'] ?? '');
|
||||
// 插件或查询驱动关闭时,即使轨迹缓存仍存在也不返回旧数据。
|
||||
if ($driverKey === '' || $ownerPluginId === '' || !ModuleStateChecker::isDriverActive($ownerPluginId)) {
|
||||
if ($driverKey === '') {
|
||||
return [];
|
||||
}
|
||||
if (!in_array($driverKey, ['aliyun_express', 'yihaotong'], true)) {
|
||||
throw new \RuntimeException('物流查询驱动不支持');
|
||||
}
|
||||
|
||||
// 按查询驱动隔离轨迹缓存,避免切换后复用其他服务商的数据。
|
||||
$driverCacheName = 'logistics:' . $driverKey . ':' . $cacheName;
|
||||
$resultData = CacheService::get($driverCacheName, null);
|
||||
if (!is_array($resultData) || empty($resultData)) {
|
||||
// 第三方无轨迹/请求失败时短缓存,避免详情页与物流页反复触发慢请求
|
||||
$emptyCacheName = $driverCacheName . ':empty:' . md5($phone . '|' . $com);
|
||||
if ((int)CacheService::get($emptyCacheName, 0) === 1) {
|
||||
return [];
|
||||
}
|
||||
$data = [];
|
||||
$cacheTime = 0;
|
||||
switch ($driverKey) {
|
||||
@ -265,10 +305,12 @@ class ExpressServices extends BaseService
|
||||
default:
|
||||
throw new \RuntimeException('物流查询驱动不支持');
|
||||
}
|
||||
// 空轨迹通常表示手机号/快递编码尚未满足验单条件,不缓存失败结果,
|
||||
// 以便后续携带完整参数的查询可以重新请求第三方服务。
|
||||
// 空轨迹通常表示手机号/快递编码尚未满足验单条件:按查询参数短缓存 120 秒,
|
||||
// 既避免详情页/物流页反复触发第三方慢请求,又允许后续携带更完整参数时重试。
|
||||
if ($data !== []) {
|
||||
CacheService::set($driverCacheName, $data, $cacheTime);
|
||||
} else {
|
||||
CacheService::set($emptyCacheName, 1, 120);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
@ -27,12 +27,15 @@ class OrderController
|
||||
try {
|
||||
$ownedId = $service->getOwnedOrderIdByNo($orderNo, $uid);
|
||||
if ($ownedId === null) return app('json')->fail('订单不存在');
|
||||
$result = $service->getDetailWithConfig($ownedId);
|
||||
// 详情首屏不同步请求第三方物流:只读轨迹缓存,未命中由前端走 order/express 异步补齐
|
||||
$result = $service->getDetailWithConfig($ownedId, false);
|
||||
if (!empty($result['page_config']['sections'])) {
|
||||
$result['page_config']['sections'] = array_values(array_filter(
|
||||
$result['page_config']['sections'], fn ($s) => ($s['key'] ?? '') !== 'timeline'
|
||||
));
|
||||
}
|
||||
// 送礼订单按查看者裁剪(与中台详情接口同口径)
|
||||
$result = $service->applyGiftViewerPolicy($result, $uid);
|
||||
return app('json')->success($result);
|
||||
} catch (\Throwable $e) {
|
||||
return app('json')->fail($e->getMessage());
|
||||
|
||||
@ -359,6 +359,114 @@ class NormalOrderAdapter extends BaseOrderTypeAdapter
|
||||
return $this->extStatusMap[$statusCode]['user_buttons'] ?? [];
|
||||
}
|
||||
|
||||
public function getOrderButtons(array $detail): array
|
||||
{
|
||||
$buttons = parent::getOrderButtons($detail);
|
||||
// 送礼订单:已支付且未领取时,提供「送给好友」(与申请退款同在底部按钮组)
|
||||
if ($this->isGiftOrder($detail) && $this->isGiftShareable($detail)) {
|
||||
array_unshift($buttons, [
|
||||
'text' => '送给好友',
|
||||
'action' => 'shareGift',
|
||||
'primary' => true,
|
||||
'params' => ['order_no' => (string)($detail['order_no'] ?? '')],
|
||||
]);
|
||||
}
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否送礼订单
|
||||
*/
|
||||
protected function isGiftOrder(array $detail): bool
|
||||
{
|
||||
$extra = $detail['extra_info'] ?? [];
|
||||
if (!is_array($extra)) {
|
||||
$extra = [];
|
||||
}
|
||||
return !empty($extra['gift']) || isset($extra['gift_mark']) || (float)($detail['gift_price'] ?? 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 送礼订单是否可分享(已支付且尚未被领取)
|
||||
*/
|
||||
protected function isGiftShareable(array $detail): bool
|
||||
{
|
||||
if ((int)($detail['pay_status'] ?? 0) !== 1) {
|
||||
return false;
|
||||
}
|
||||
$extra = $detail['extra_info'] ?? [];
|
||||
if (!is_array($extra)) {
|
||||
$extra = [];
|
||||
}
|
||||
// 优先读取实体列 gift_uid(领取索引),历史数据回退 extra_info
|
||||
$giftUid = (int)($detail['gift_uid'] ?? $extra['gift']['gift_uid'] ?? ($extra['gift_uid'] ?? 0));
|
||||
return $giftUid === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 送礼订单生命周期进度条
|
||||
*
|
||||
* 节点固定为:提交订单 → 支付成功 → 礼品待领 → 礼品已领 → 订单完成。
|
||||
* 领取状态由实体列 gift_uid 判定(非 0 表示已被领取;历史数据回退 extra_info.gift.gift_uid),
|
||||
* 领取时间取 extra_info.gift.gift_time(历史订单无该字段时留空)。
|
||||
*
|
||||
* @param array $detail 订单详情(含 status_code、pay_status、extra_info、add_time、pay_time、take_time 等)
|
||||
* @return array 返回 {current, status, nodes}
|
||||
*/
|
||||
protected function buildGiftProgress(array $detail): array
|
||||
{
|
||||
$statusCode = (string)($detail['status_code'] ?? '');
|
||||
$isCancel = $statusCode === self::STATUS_CANCELED;
|
||||
$isAfterSale = $statusCode === self::STATUS_AFTER_SALE;
|
||||
$isRefunded = $statusCode === self::STATUS_REFUNDED;
|
||||
$isCompleted = $statusCode === self::STATUS_COMPLETED;
|
||||
|
||||
$extra = is_array($detail['extra_info'] ?? null) ? $detail['extra_info'] : [];
|
||||
$gift = is_array($extra['gift'] ?? null) ? $extra['gift'] : [];
|
||||
// 优先读取实体列 gift_uid(领取索引),历史数据回退 extra_info
|
||||
$giftUid = (int)($detail['gift_uid'] ?? $gift['gift_uid'] ?? ($extra['gift_uid'] ?? 0));
|
||||
$giftTime = (int)($gift['gift_time'] ?? 0);
|
||||
$addTime = (int)($detail['add_time'] ?? 0);
|
||||
$payTime = (int)($detail['pay_time'] ?? 0);
|
||||
$takeTime = (int)($detail['take_time'] ?? 0);
|
||||
// 已领取:礼品待领/礼品已领两个节点同时完成
|
||||
$isClaimed = $giftUid > 0;
|
||||
|
||||
$nodes = [
|
||||
['title' => '提交订单', 'key' => 'submit', 'time' => $addTime ? date('Y-m-d H:i:s', $addTime) : '', 'completed' => $addTime > 0],
|
||||
['title' => '支付成功', 'key' => 'pay', 'time' => $payTime ? date('Y-m-d H:i:s', $payTime) : '', 'completed' => $payTime > 0 || (int)($detail['pay_status'] ?? 0) === 1],
|
||||
['title' => '礼品待领', 'key' => 'gift_pending', 'time' => $isClaimed && $giftTime ? date('Y-m-d H:i:s', $giftTime) : '', 'completed' => $isClaimed],
|
||||
['title' => '礼品已领', 'key' => 'gift_received', 'time' => $isClaimed && $giftTime ? date('Y-m-d H:i:s', $giftTime) : '', 'completed' => $isClaimed],
|
||||
['title' => '订单完成', 'key' => 'complete', 'time' => $isCompleted && $takeTime ? date('Y-m-d H:i:s', $takeTime) : '', 'completed' => $isCompleted],
|
||||
];
|
||||
|
||||
// 当前激活节点:取消停在提交订单、售后/退款中断在支付成功、完成后停在订单完成
|
||||
if ($isCancel) {
|
||||
$current = 0;
|
||||
} elseif ($isRefunded || $isAfterSale) {
|
||||
$current = 1;
|
||||
} elseif ($isCompleted) {
|
||||
$current = 4;
|
||||
} elseif ($isClaimed) {
|
||||
$current = 3; // 已领取,等待订单完成(收货/自动收货)
|
||||
} elseif ($payTime > 0) {
|
||||
$current = 2; // 已支付,等待好友领取
|
||||
} else {
|
||||
$current = 0; // 刚提交,未支付
|
||||
}
|
||||
|
||||
foreach ($nodes as $i => &$node) {
|
||||
$node['active'] = ($i === $current);
|
||||
}
|
||||
unset($node);
|
||||
|
||||
return [
|
||||
'current' => $current,
|
||||
'status' => $statusCode,
|
||||
'nodes' => $nodes,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* PROCESSING 状态动态按钮
|
||||
*/
|
||||
@ -413,6 +521,11 @@ class NormalOrderAdapter extends BaseOrderTypeAdapter
|
||||
$isRefunded = $statusCode === self::STATUS_REFUNDED;
|
||||
$isCompleted = $statusCode === self::STATUS_COMPLETED;
|
||||
|
||||
// 送礼订单:提交订单 → 支付成功 → 礼品待领 → 礼品已领 → 订单完成
|
||||
if ($this->isGiftOrder($detail)) {
|
||||
return $this->buildGiftProgress($detail);
|
||||
}
|
||||
|
||||
// 判断是否为到店自提(shipping_type=2),默认走快递配送流程
|
||||
$isPickup = ($detail['shipping_type'] ?? 0) == 2;
|
||||
|
||||
|
||||
@ -52,15 +52,20 @@ class OrderCenterDetailExtendListener implements ListenerInterface
|
||||
$detail['contact'] = $extraInfo['contact'];
|
||||
unset($extraInfo['contact']);
|
||||
|
||||
// 将 contact.address 对象格式化为字符串,赋值给 user_address
|
||||
if (isset($detail['contact']['address']) && is_array($detail['contact']['address'])) {
|
||||
$addr = $detail['contact']['address'];
|
||||
$detail['user_address'] = implode('', array_filter([
|
||||
$addr['province'] ?? '',
|
||||
$addr['city'] ?? '',
|
||||
$addr['district'] ?? '',
|
||||
$addr['detail'] ?? '',
|
||||
]));
|
||||
// 将 contact.address 统一转为字符串赋值给 user_address
|
||||
// 送礼订单领取时写入的是字符串地址,普通订单是省市区对象,两种都要兼容
|
||||
if (isset($detail['contact']['address'])) {
|
||||
if (is_array($detail['contact']['address'])) {
|
||||
$addr = $detail['contact']['address'];
|
||||
$detail['user_address'] = implode('', array_filter([
|
||||
$addr['province'] ?? '',
|
||||
$addr['city'] ?? '',
|
||||
$addr['district'] ?? '',
|
||||
$addr['detail'] ?? '',
|
||||
]));
|
||||
} else {
|
||||
$detail['user_address'] = (string)$detail['contact']['address'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -770,6 +770,7 @@ CREATE TABLE IF NOT EXISTS `eb_order_center` (
|
||||
`member_discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '等级会员优惠金额',
|
||||
`vip_discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '付费会员优惠金额',
|
||||
`gift_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '礼品费用',
|
||||
`gift_uid` int NOT NULL DEFAULT '0' COMMENT '礼品领取人UID(0=未领取)',
|
||||
`total_num` int NOT NULL DEFAULT '0' COMMENT '商品数量总数',
|
||||
`title` varchar(255) NOT NULL DEFAULT '' COMMENT '订单标题摘要',
|
||||
`image` varchar(500) NOT NULL DEFAULT '' COMMENT '缩略图',
|
||||
@ -792,6 +793,7 @@ CREATE TABLE IF NOT EXISTS `eb_order_center` (
|
||||
UNIQUE KEY `uk_order_no` (`order_no`),
|
||||
UNIQUE KEY `uk_source_order` (`source_type`,`source_id`),
|
||||
KEY `idx_uid` (`uid`),
|
||||
KEY `idx_gift_uid` (`gift_uid`),
|
||||
KEY `idx_order_category` (`order_category`),
|
||||
KEY `idx_source_type` (`source_type`),
|
||||
KEY `idx_status_code` (`status_code`),
|
||||
|
||||
@ -76,6 +76,22 @@ export default {
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 送给好友(送礼订单分享礼物领取链接)
|
||||
* 详情页引擎通过 context.onShareGift 打开送礼弹窗;无弹窗上下文的页面跳详情页操作
|
||||
*/
|
||||
shareGift(order, params, context) {
|
||||
if (context && typeof context.onShareGift === 'function') {
|
||||
context.onShareGift(params || {});
|
||||
return;
|
||||
}
|
||||
const orderNo = params.order_no || order.order_no || '';
|
||||
if (!orderNo) {
|
||||
uni.showToast({ title: i18n.t(`订单信息异常`), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({ url: routes.orderDetail + '?order_no=' + orderNo });
|
||||
},
|
||||
/** 申请退款(统一走售后流程,售后页按 available_types 渲染仅退款等类型) */
|
||||
applyRefund(order, params) {
|
||||
const orderNo = params.order_no || order.order_no || '';
|
||||
|
||||
@ -14,6 +14,11 @@
|
||||
<view>{{ $t(`可用积分`) }}</view>
|
||||
<view class="money">{{ usableIntegral }}</view>
|
||||
</view>
|
||||
<!-- 礼品附加费(送礼订单) -->
|
||||
<view class="item acea-row row-between-wrapper" v-if="parseFloat(giftPrice) > 0">
|
||||
<view>{{ $t(`礼品附加费`) }}</view>
|
||||
<view class="money">¥{{ parseFloat(giftPrice).toFixed(2) }}</view>
|
||||
</view>
|
||||
<!-- 运费(仅支持快递配送时显示) -->
|
||||
<view class="item acea-row row-between-wrapper" v-if="showPostage">
|
||||
<view>{{ $t(`快递费用`) }}</view>
|
||||
@ -78,6 +83,8 @@ const props = defineProps({
|
||||
integralPrice: { type: [Number, String], default: 0 },
|
||||
/** 积分兑换总额(非0时替换商品总价显示) */
|
||||
integralTotal: { type: [Number, String], default: 0 },
|
||||
/** 礼品附加费(送礼订单按件累计) */
|
||||
giftPrice: { type: [Number, String], default: 0 },
|
||||
/** 可用积分(积分兑换模式展示) */
|
||||
usableIntegral: { type: [Number, String], default: 0 },
|
||||
/** 是否显示快递费用行(快递配送关闭时隐藏) */
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<text class="product-name">{{ item.store_name || '' }}</text>
|
||||
<text v-if="getSkuText(item)" class="sku-text">{{ getSkuText(item) }}</text>
|
||||
<view class="product-meta">
|
||||
<text class="product-price">¥{{ item.true_price || item.price || '0.00' }}</text>
|
||||
<text v-if="!isGiftReceiver" class="product-price">¥{{ item.true_price || item.price || '0.00' }}</text>
|
||||
<text class="product-qty">x{{ item.cart_num || 1 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -53,6 +53,8 @@ const emit = defineEmits(['click']);
|
||||
const p = computed(() => (props.section && props.section.props) || {});
|
||||
/** 引擎注入的全局状态 */
|
||||
const ctx = computed(() => (props.section && props.section._ctx) || {});
|
||||
/** 礼品领取人查看:不展示商品价格(与旧版 is_gift=2 一致) */
|
||||
const isGiftReceiver = computed(() => ctx.value.viewerRole === 'gift_receiver');
|
||||
/** 商品信息对象 */
|
||||
const product = computed(() => p.value.product || {});
|
||||
/**
|
||||
|
||||
@ -17,8 +17,9 @@
|
||||
</view>
|
||||
|
||||
<template v-if="!loading && !errorMsg">
|
||||
<!-- 配送方式 + 收货地址,由 express_shipping / store_pickup / door_service 驱动 -->
|
||||
<!-- 配送方式 + 收货地址,由 express_shipping / store_pickup / door_service 驱动;送礼订单由领取人填地址 -->
|
||||
<DeliverySection
|
||||
v-if="!isGift"
|
||||
v-model="deliveryType"
|
||||
:express-shipping="features.express_shipping"
|
||||
:store-pickup="features.store_pickup"
|
||||
@ -81,11 +82,22 @@
|
||||
:fields="visitorFormConfig"
|
||||
/>
|
||||
|
||||
<!-- 订单备注 -->
|
||||
<!-- 订单备注 / 送礼留言 -->
|
||||
<OrderRemarkInput
|
||||
v-if="!isGift"
|
||||
v-model="remark"
|
||||
:visible="features.remark"
|
||||
/>
|
||||
<view v-if="isGift" class="gift-mark-card">
|
||||
<view class="gift-mark-label">{{ $t('礼物留言') }}</view>
|
||||
<input
|
||||
v-model="giftMark"
|
||||
class="gift-mark-input"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
:placeholder="$t('填写好友留言,最多40字')"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 发票信息(抬头选择模式,编辑/保存走原 user_invoice_form 页面) -->
|
||||
<OrderInvoicePanel
|
||||
@ -112,9 +124,10 @@
|
||||
:integral-price="integralPrice"
|
||||
:integral-total="integralTotal"
|
||||
:usable-integral="usableIntegral"
|
||||
:show-postage="features.express_shipping && deliveryType === 'delivery'"
|
||||
:gift-price="giftPrice"
|
||||
:show-postage="!isGift && features.express_shipping && deliveryType === 'delivery'"
|
||||
:door-postage="features.door_service_postage"
|
||||
:show-door-postage="features.door_service && deliveryType === 'door'"
|
||||
:show-door-postage="!isGift && features.door_service && deliveryType === 'door'"
|
||||
/>
|
||||
|
||||
<!-- 底部提交栏 -->
|
||||
@ -197,6 +210,10 @@ const num = ref(1);
|
||||
const cartId = ref('');
|
||||
const news = ref(0);
|
||||
const isCartMode = ref(false);
|
||||
// 送礼订单:1=购买礼物(不填地址/不计运费),2=领取礼物
|
||||
const isGift = ref(0);
|
||||
const giftMark = ref('');
|
||||
const giftPrice = ref(0);
|
||||
// 详情页 display_sections block 选中数据(票种/场次/日期),通过 URL block_data 参数透传
|
||||
const selectedBlockData = ref([]);
|
||||
|
||||
@ -318,11 +335,14 @@ const usableIntegralDisplay = computed(() => {
|
||||
const integralCheckDisabled = computed(() => usableIntegral.value <= 0 && !useIntegral.value);
|
||||
const totalPriceText = computed(() => totalPrice.value || '0.00');
|
||||
const payPriceText = computed(() => payPrice.value ?? '0.00');
|
||||
/** 提交禁用:快递配送模式下未选地址,自提模式下未选门店,上门模式下未填完整预约信息 */
|
||||
/** 提交禁用:快递配送模式下未选地址,自提模式下未选门店,上门模式下未填完整预约信息;送礼订单不校验收货信息 */
|
||||
const submitDisabled = computed(() => {
|
||||
if (!priceReady.value || priceComputing.value) {
|
||||
return true;
|
||||
}
|
||||
if (isGift.value) {
|
||||
return false;
|
||||
}
|
||||
if (features.value.express_shipping && deliveryType.value === 'delivery') {
|
||||
return !addressInfo.value.real_name;
|
||||
}
|
||||
@ -419,6 +439,7 @@ async function loadConfirmData() {
|
||||
if (activityType.value) params.activity_type = activityType.value;
|
||||
if (activityId.value) params.activity_id = activityId.value;
|
||||
}
|
||||
if (isGift.value) params.is_gift = isGift.value;
|
||||
var res = await getCenterOrderConfirm(params);
|
||||
|
||||
var data = res.data || {};
|
||||
@ -432,7 +453,17 @@ async function loadConfirmData() {
|
||||
levelPrice.value = confirmPriceGroup.levelPrice ?? data.level_price ?? 0;
|
||||
memberPrice.value = confirmPriceGroup.memberPrice ?? data.member_price ?? 0;
|
||||
postageDiscount.value = confirmPriceGroup.postageDiscount ?? data.postage_discount ?? 0;
|
||||
giftPrice.value = confirmPriceGroup.giftPrice ?? data.gift_price ?? 0;
|
||||
features.value = { ...features.value, ...(data.features || {}) };
|
||||
// 送礼订单:后端 features 仍按商品配送能力下发,前端按 is_gift 隐藏地址与运费
|
||||
if (isGift.value) {
|
||||
features.value = {
|
||||
...features.value,
|
||||
express_shipping: false,
|
||||
store_pickup: false,
|
||||
door_service: false,
|
||||
};
|
||||
}
|
||||
// 保存活动订单信息(含免费支付方式标识与跳转地址,由交易模式 Handler 下发)
|
||||
activityInfo.value = data.activity_info || {};
|
||||
// 同步发票相关配置:后端未返回时默认开启专用发票
|
||||
@ -473,8 +504,8 @@ async function loadConfirmData() {
|
||||
}
|
||||
|
||||
// 加载地址:有回跳 addressId 时加载地址详情,否则加载默认地址
|
||||
// 购物车模式从地址列表选择后回跳会携带 address_id
|
||||
if (features.value.express_shipping || features.value.store_pickup || features.value.door_service) {
|
||||
// 购物车模式从地址列表选择后回跳会携带 address_id;送礼订单由领取人填地址,跳过
|
||||
if (!isGift.value && (features.value.express_shipping || features.value.store_pickup || features.value.door_service)) {
|
||||
if (addressId.value) {
|
||||
await refreshAddressInfo();
|
||||
} else {
|
||||
@ -567,8 +598,8 @@ function ChangCoupons(e) {
|
||||
*/
|
||||
async function computedPrice() {
|
||||
var requestSerial = ++priceRequestSerial;
|
||||
// 快递运费依赖收货地址,未选择时等待地址回调再试算。
|
||||
if (!orderKey.value || (features.value.express_shipping && deliveryType.value === 'delivery' && !addressId.value)) {
|
||||
// 快递运费依赖收货地址,未选择时等待地址回调再试算;送礼订单不计运费,无需地址
|
||||
if (!orderKey.value || (!isGift.value && features.value.express_shipping && deliveryType.value === 'delivery' && !addressId.value)) {
|
||||
priceComputing.value = false;
|
||||
priceReady.value = false;
|
||||
priceError.value = '';
|
||||
@ -593,11 +624,13 @@ async function computedPrice() {
|
||||
address_id: addressId.value,
|
||||
delivery_type: deliveryType.value,
|
||||
store_id: deliveryType.value === 'pickup' ? (storeInfo.value.id || 0) : 0,
|
||||
is_gift: isGift.value || 0,
|
||||
});
|
||||
if (requestSerial !== priceRequestSerial) return;
|
||||
var data = res.data || {};
|
||||
payPrice.value = data.pay_price ?? 0;
|
||||
payPostage.value = data.pay_postage ?? 0;
|
||||
giftPrice.value = data.gift_price ?? giftPrice.value;
|
||||
// computed 接口返回 snake_case 价格字段,和确认接口的 priceGroup 保持同一展示口径。
|
||||
levelPrice.value = data.level_price ?? data.priceGroup?.levelPrice ?? levelPrice.value;
|
||||
memberPrice.value = data.member_price ?? data.priceGroup?.memberPrice ?? memberPrice.value;
|
||||
@ -644,26 +677,29 @@ async function handleConfirm() {
|
||||
uni.showToast({ title: i18n.global.t('金额正在计算,请稍后再试'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (features.value.express_shipping && deliveryType.value === 'delivery' && !addressId.value) {
|
||||
uni.showToast({ title: i18n.global.t('请选择收货地址'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (features.value.store_pickup && deliveryType.value === 'pickup' && !storeInfo.value.id) {
|
||||
uni.showToast({ title: i18n.global.t('请选择自提门店'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (features.value.door_service && deliveryType.value === 'door') {
|
||||
if (!serviceInfo.value.contact_name) {
|
||||
uni.showToast({ title: i18n.global.t('请填写联系人姓名'), icon: 'none' });
|
||||
// 送礼订单由领取人填收货信息,不校验地址/门店/预约
|
||||
if (!isGift.value) {
|
||||
if (features.value.express_shipping && deliveryType.value === 'delivery' && !addressId.value) {
|
||||
uni.showToast({ title: i18n.global.t('请选择收货地址'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!serviceInfo.value.contact_phone) {
|
||||
uni.showToast({ title: i18n.global.t('请填写联系电话'), icon: 'none' });
|
||||
if (features.value.store_pickup && deliveryType.value === 'pickup' && !storeInfo.value.id) {
|
||||
uni.showToast({ title: i18n.global.t('请选择自提门店'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!serviceInfo.value.appointment_time) {
|
||||
uni.showToast({ title: i18n.global.t('请选择预约时间'), icon: 'none' });
|
||||
return;
|
||||
if (features.value.door_service && deliveryType.value === 'door') {
|
||||
if (!serviceInfo.value.contact_name) {
|
||||
uni.showToast({ title: i18n.global.t('请填写联系人姓名'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!serviceInfo.value.contact_phone) {
|
||||
uni.showToast({ title: i18n.global.t('请填写联系电话'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!serviceInfo.value.appointment_time) {
|
||||
uni.showToast({ title: i18n.global.t('请选择预约时间'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (features.value.custom_form && customForm.value.length > 0) {
|
||||
@ -687,8 +723,8 @@ async function handleConfirm() {
|
||||
coupon_id: couponId.value,
|
||||
use_integral: useIntegral.value ? 1 : 0,
|
||||
order_data: {
|
||||
address_id: !features.value.express_shipping || deliveryType.value === 'pickup' || deliveryType.value === 'door' ? 0 : addressId.value,
|
||||
delivery_type: deliveryType.value,
|
||||
address_id: isGift.value ? 0 : (!features.value.express_shipping || deliveryType.value === 'pickup' || deliveryType.value === 'door' ? 0 : addressId.value),
|
||||
delivery_type: isGift.value ? 'delivery' : deliveryType.value,
|
||||
store_id: deliveryType.value === 'pickup' ? (storeInfo.value.id || 0) : 0,
|
||||
service: deliveryType.value === 'door' ? serviceInfo.value : {},
|
||||
mark: remark.value,
|
||||
@ -696,6 +732,8 @@ async function handleConfirm() {
|
||||
visitors: (visitorFormRef.value && features.value.visitor_form) ? visitorFormRef.value.getData() : [],
|
||||
block_data: selectedBlockData.value,
|
||||
invoice_id: features.value.invoice ? (invoiceId.value || 0) : 0,
|
||||
is_gift: isGift.value || 0,
|
||||
gift_mark: isGift.value ? giftMark.value : '',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
@ -710,8 +748,8 @@ async function handleConfirm() {
|
||||
coupon_id: couponId.value,
|
||||
use_integral: useIntegral.value ? 1 : 0,
|
||||
order_data: {
|
||||
address_id: !features.value.express_shipping || deliveryType.value === 'pickup' || deliveryType.value === 'door' ? 0 : addressId.value,
|
||||
delivery_type: deliveryType.value,
|
||||
address_id: isGift.value ? 0 : (!features.value.express_shipping || deliveryType.value === 'pickup' || deliveryType.value === 'door' ? 0 : addressId.value),
|
||||
delivery_type: isGift.value ? 'delivery' : deliveryType.value,
|
||||
store_id: deliveryType.value === 'pickup' ? (storeInfo.value.id || 0) : 0,
|
||||
service: deliveryType.value === 'door' ? serviceInfo.value : {},
|
||||
mark: remark.value,
|
||||
@ -719,6 +757,8 @@ async function handleConfirm() {
|
||||
visitors: (visitorFormRef.value && features.value.visitor_form) ? visitorFormRef.value.getData() : [],
|
||||
block_data: selectedBlockData.value,
|
||||
invoice_id: features.value.invoice ? (invoiceId.value || 0) : 0,
|
||||
is_gift: isGift.value || 0,
|
||||
gift_mark: isGift.value ? giftMark.value : '',
|
||||
},
|
||||
};
|
||||
if (activityType.value) params.activity_type = activityType.value;
|
||||
@ -746,7 +786,7 @@ async function handleConfirm() {
|
||||
return;
|
||||
}
|
||||
uni.redirectTo({
|
||||
url: routes.cashier + '?order_no=' + orderNo + '&from_type=order&price=' + payPrice,
|
||||
url: routes.cashier + '?order_no=' + orderNo + '&from_type=order&price=' + payPrice + (isGift.value ? '&is_gift=1' : ''),
|
||||
});
|
||||
} catch (e) {
|
||||
uni.showToast({ title: e.msg || e.message || i18n.global.t('下单失败'), icon: 'none' });
|
||||
@ -953,6 +993,11 @@ function initFromOptions(options) {
|
||||
cartId.value = routeQuery.cartId || '';
|
||||
news.value = routeQuery.new === '1' || routeQuery.new === 1 ? 1 : 0;
|
||||
isCartMode.value = !!cartId.value;
|
||||
// 送礼下单:详情页「送礼物」跳转携带 is_gift=1
|
||||
isGift.value = Number(routeQuery.is_gift || 0) || 0;
|
||||
if (isGift.value === 1) {
|
||||
giftMark.value = i18n.global.t('这是送您的一份礼物~');
|
||||
}
|
||||
productId.value = routeQuery.product_id || '';
|
||||
skuUnique.value = routeQuery.sku_unique || '';
|
||||
try {
|
||||
@ -1043,4 +1088,22 @@ onBeforeUnmount(() => {
|
||||
.product-card-hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 送礼留言 */
|
||||
.gift-mark-card {
|
||||
margin: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
}
|
||||
.gift-mark-label {
|
||||
font-size: 28rpx;
|
||||
color: #282828;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
.gift-mark-input {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
color: #282828;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -27,6 +27,14 @@
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
<!-- 送礼订单:分享入口由底部按钮组(与「申请退款」同组)的 shareGift 动作唤起 -->
|
||||
<giftModal
|
||||
:aleartStatus="giftModalShow"
|
||||
:giftData="giftShareData"
|
||||
@shareH5="shareH5"
|
||||
@close="giftModalShow = false"
|
||||
/>
|
||||
<view class="mask" v-if="giftModalShow" @click="giftModalShow = false"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@ -54,9 +62,11 @@ import SectionDispatcher from '../components/order/detail/SectionDispatcher.vue'
|
||||
// #endif
|
||||
import actionMap from '../api/action-map.js';
|
||||
import { getCenterOrderDetail, takeOrder } from '@/api/order-center.js';
|
||||
import { express } from '@/api/order.js';
|
||||
import routes from '../api/routes.js';
|
||||
import { useColorTheme } from '@/composables/useColorTheme';
|
||||
import i18n from '@/utils/lang.js';
|
||||
import giftModal from '@/pages/goods/order_pay_status/components/giftModal.vue';
|
||||
|
||||
defineOptions({ name: 'OrderDetailEngine' });
|
||||
|
||||
@ -72,6 +82,9 @@ const { colorStyle } = useColorTheme();
|
||||
const pageConfig = ref({ sections: [], extensions: {} });
|
||||
const loading = ref(true);
|
||||
const confirmingReceive = ref(false);
|
||||
const logisticsLoading = ref(false);
|
||||
// 送礼分享
|
||||
const giftModalShow = ref(false);
|
||||
|
||||
// ---------- computed ----------
|
||||
/**
|
||||
@ -107,6 +120,30 @@ const payPrice = computed(() => {
|
||||
const payStatus = computed(() => {
|
||||
return (sectionPropsMap.value['status'] && sectionPropsMap.value['status'].pay_status) || 0;
|
||||
});
|
||||
/** 查看者角色:gift_receiver 表示当前用户是礼品领取人(只读视图) */
|
||||
const viewerRole = computed(() => {
|
||||
return (pageConfig.value.extensions && pageConfig.value.extensions.viewer_role) || '';
|
||||
});
|
||||
/** 订单信息字段(送礼标识/领取人等) */
|
||||
const orderInfoFields = computed(() => {
|
||||
return (sectionPropsMap.value['order_info'] && sectionPropsMap.value['order_info'].fields) || {};
|
||||
});
|
||||
/** giftModal 分享数据 */
|
||||
const giftShareData = computed(() => {
|
||||
// section.props.product = { product: {title,image,items}, product_id }
|
||||
var productObj = (sectionPropsMap.value['product'] && sectionPropsMap.value['product'].product) || {};
|
||||
var productItems = productObj.items || [];
|
||||
var first = productItems[0] || {};
|
||||
return {
|
||||
id: orderInfoFields.value.id || 0,
|
||||
image: first.image || productObj.image || '',
|
||||
title: first.store_name || first.title || productObj.title || '',
|
||||
message: orderInfoFields.value.gift_mark || '',
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
code: '',
|
||||
};
|
||||
});
|
||||
|
||||
// ---------- watch ----------
|
||||
// The parent page receives route options in onLoad, after this child may
|
||||
@ -133,6 +170,8 @@ async function loadDetail() {
|
||||
return;
|
||||
}
|
||||
pageConfig.value = (res.data && res.data.page_config) || { sections: [], extensions: {} };
|
||||
// 详情接口首屏只返回已缓存的物流轨迹,未命中时异步补齐,避免被第三方物流慢请求阻塞
|
||||
loadLogisticsTracks();
|
||||
} catch (e) {
|
||||
uni.showToast({ title: i18n.t('加载失败'), icon: 'none' });
|
||||
} finally {
|
||||
@ -140,6 +179,43 @@ async function loadDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步加载物流轨迹
|
||||
* 详情接口不再同步请求第三方物流,仅返回已缓存轨迹;未命中时由前端按需查询并回填 section
|
||||
* 失败时静默降级,仍展示快递公司 + 单号
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async function loadLogisticsTracks() {
|
||||
if (logisticsLoading.value) return;
|
||||
const sections = pageConfig.value.sections || [];
|
||||
const section = sections.find(function (s) { return s.key === 'logistics'; });
|
||||
if (!section) return;
|
||||
// 已命中后端轨迹缓存,无需再次查询
|
||||
const cached = (section.props && section.props.tracks) || [];
|
||||
if (cached.length > 0) return;
|
||||
const orderNo = (sectionPropsMap.value['order_info'] && sectionPropsMap.value['order_info'].fields
|
||||
&& sectionPropsMap.value['order_info'].fields.order_no) || props.orderNo || '';
|
||||
if (!orderNo) return;
|
||||
logisticsLoading.value = true;
|
||||
try {
|
||||
const res = await express(orderNo);
|
||||
const list = res && res.data && res.data.express && res.data.express.result
|
||||
? res.data.express.result.list : null;
|
||||
if (!Array.isArray(list) || list.length === 0) return;
|
||||
section.props.tracks = list.map(function (track) {
|
||||
return {
|
||||
title: track.title || track.status || track.context || track.name || '',
|
||||
time: track.time || track.ftime || '',
|
||||
desc: track.desc || '',
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
// 物流查询失败不影响详情展示
|
||||
} finally {
|
||||
logisticsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 包装页 onShow 返回时调用(评价完成/售后进度变化等场景,返回详情需看到最新状态)
|
||||
defineExpose({ refresh: loadDetail });
|
||||
|
||||
@ -158,6 +234,7 @@ function withCtx(section) {
|
||||
payStatus: payStatus.value,
|
||||
isRefund: isRefund.value,
|
||||
sectionPropsMap: sectionPropsMap.value,
|
||||
viewerRole: viewerRole.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -207,11 +284,24 @@ function onButtonAction(payload) {
|
||||
var orderNo = ((sectionPropsMap.value['order_info'] && sectionPropsMap.value['order_info'].fields)
|
||||
&& sectionPropsMap.value['order_info'].fields.order_no)
|
||||
|| (payload.params && payload.params.order_no) || '';
|
||||
var context = payload.action === 'confirmReceive' ? { onConfirm: confirmReceive } : undefined;
|
||||
// 需要页面内弹窗/刷新等交互的动作,通过 context 回调注入,action-map 不直接持有页面状态
|
||||
var context;
|
||||
if (payload.action === 'confirmReceive') {
|
||||
context = { onConfirm: confirmReceive };
|
||||
} else if (payload.action === 'shareGift') {
|
||||
context = { onShareGift: openGiftShare };
|
||||
}
|
||||
handler(Object.assign({}, sectionPropsMap.value, { order_no: orderNo }), payload.params || {}, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开送礼分享弹窗(送给好友按钮,与申请退款同组)
|
||||
*/
|
||||
function openGiftShare() {
|
||||
giftModalShow.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页确认收货
|
||||
* @param {object} payload {order_no}
|
||||
@ -329,6 +419,10 @@ function goGoodCall() {
|
||||
&& sectionPropsMap.value['order_info'].fields.order_no) || '';
|
||||
uni.navigateTo({ url: routes.customerChat + '?orderId=' + orderNo });
|
||||
}
|
||||
/** H5 分享面板回调(giftModal) */
|
||||
function shareH5() {
|
||||
// 链接复制由 giftModal 内部完成
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -337,4 +431,15 @@ function goGoodCall() {
|
||||
padding-bottom: calc(150rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(150rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
/* 低于 giftModal 的 999,避免遮罩盖住弹窗按钮 */
|
||||
z-index: 998;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -44,6 +44,7 @@
|
||||
<view class="title acea-row row-between-wrapper">
|
||||
<view class="acea-row row-middle">
|
||||
<text v-if="order.type_label" class="type-tag">{{ order.type_label }}</text>
|
||||
<text v-if="order.is_gift" class="type-tag gift-tag">{{ order.is_gift_receiver ? $t('收到的礼物') : $t('礼物') }}</text>
|
||||
<view>{{ formatTime(order.add_time) }}</view>
|
||||
</view>
|
||||
<StatusBadge :status-code="order.status_code" :status-text="order.status_text" />
|
||||
@ -58,7 +59,7 @@
|
||||
<text class="goods-name line2">{{ item.store_name || '' }}</text>
|
||||
<text v-if="getItemSkuText(item)" class="goods-sku line1">{{ getItemSkuText(item) }}</text>
|
||||
<view class="goods-meta">
|
||||
<text class="goods-price">¥{{ item.true_price || item.price || '0.00' }}</text>
|
||||
<text v-if="!order.is_gift_receiver" class="goods-price">¥{{ item.true_price || item.price || '0.00' }}</text>
|
||||
<text class="goods-num">x{{ item.cart_num || 1 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -73,7 +74,7 @@
|
||||
<view class="text">
|
||||
<view class="title-row">
|
||||
<text class="name line2">{{ order.title || '' }}</text>
|
||||
<view class="money">
|
||||
<view v-if="!order.is_gift_receiver" class="money">
|
||||
<view>{{ $t(`¥`) }}{{ order.pay_price || '0.00' }}</view>
|
||||
<view>x{{ order.total_num || 0 }}</view>
|
||||
</view>
|
||||
@ -426,6 +427,11 @@ defineExpose({ refresh: refreshList });
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.my-order .list .item .title .gift-tag {
|
||||
color: #ff6b00;
|
||||
background: rgba(255, 107, 0, 0.1);
|
||||
}
|
||||
|
||||
.my-order .list .item .item-info {
|
||||
padding: 0 30rpx;
|
||||
margin-top: 22rpx;
|
||||
|
||||
@ -39,6 +39,18 @@
|
||||
@click="onAction(action)"
|
||||
>{{ $t(action.label) }}</button>
|
||||
</view>
|
||||
<!-- 送礼订单:支付成功后引导分享礼物(按钮样式与其它操作按钮一致) -->
|
||||
<view v-if="isGiftShareVisible" @click="giftModalShow = true">
|
||||
<button class="returnBnt bg-color" hover-class="none">{{ $t('送给好友') }}</button>
|
||||
</view>
|
||||
<!-- 蒙层置于弹窗下层,避免遮挡 giftModal -->
|
||||
<view class="gift-mask" v-if="giftModalShow" @click="giftModalShow = false"></view>
|
||||
<giftModal
|
||||
:aleartStatus="giftModalShow"
|
||||
:giftData="giftData"
|
||||
@shareH5="shareH5"
|
||||
@close="giftModalShow = false"
|
||||
/>
|
||||
<!-- 赠送优惠券 -->
|
||||
<view class="coupons" v-if="couponList.length">
|
||||
<view class="title acea-row row-center-wrapper">
|
||||
@ -92,6 +104,7 @@ import { useAppStore } from '@/store/modules/app';
|
||||
import { useColorTheme } from '@/composables/useColorTheme';
|
||||
import routes from '../api/routes.js';
|
||||
import i18n from '@/utils/lang.js';
|
||||
import giftModal from '@/pages/goods/order_pay_status/components/giftModal.vue';
|
||||
|
||||
defineOptions({ name: 'PayStatusEngine' });
|
||||
|
||||
@ -111,6 +124,9 @@ const msg = ref('');
|
||||
const payType = ref('');
|
||||
/** 是否礼品订单(0 否) */
|
||||
const isGift = ref(0);
|
||||
// 送礼分享弹窗与数据
|
||||
const giftModalShow = ref(false);
|
||||
const giftData = ref({});
|
||||
// 是否已完成首次查询
|
||||
const loading = ref(false);
|
||||
// 订单支付信息
|
||||
@ -181,6 +197,13 @@ const currentActions = computed(() => {
|
||||
list.push(actionMap.value.goIndex);
|
||||
return list;
|
||||
});
|
||||
/**
|
||||
* 送礼分享入口可见性:送礼订单且已支付(含线下支付成功)
|
||||
*/
|
||||
const isGiftShareVisible = computed(() => {
|
||||
const info = order_pay_info.value || {};
|
||||
return !!isGift.value && (info.pay_status === 1 || info.pay_type === 'offline');
|
||||
});
|
||||
/**
|
||||
* 状态图标 class
|
||||
* @returns {String} 图标 class 字符串
|
||||
@ -291,6 +314,22 @@ function getOrderPayInfo() {
|
||||
pay_price: pricingProps.pay_price || '0.00',
|
||||
pay_status: statusProps.pay_status || 0,
|
||||
};
|
||||
// 送礼订单:URL 参数缺失时按订单详情字段兜底
|
||||
const orderIsGift = Number(orderInfoFields.is_gift || 0) || (isGift.value ? 1 : 0);
|
||||
isGift.value = orderIsGift;
|
||||
// section.props.product = { product: {title,image,items}, product_id }
|
||||
const productObj = (propsMap['product'] && propsMap['product'].product) || {};
|
||||
const productItems = productObj.items || [];
|
||||
const firstProduct = productItems[0] || {};
|
||||
giftData.value = {
|
||||
id: orderInfoFields.id || 0,
|
||||
image: firstProduct.image || productObj.image || '',
|
||||
title: firstProduct.store_name || firstProduct.title || productObj.title || '',
|
||||
message: orderInfoFields.gift_mark || '',
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
code: '',
|
||||
};
|
||||
// 根据支付状态设置导航栏标题
|
||||
// pay_status: 0未支付 1已支付 2部分退款 3全额退款
|
||||
const isPaid = order_pay_info.value.pay_status === 1;
|
||||
@ -298,8 +337,10 @@ function getOrderPayInfo() {
|
||||
title: isPaid ? i18n.t('支付成功') : i18n.t('未支付'),
|
||||
});
|
||||
loading.value = true;
|
||||
// 非礼品订单禁用分享(本引擎不支持礼品分享)
|
||||
uni.hideShareMenu();
|
||||
// 送礼订单允许分享领取链接;普通订单禁用分享
|
||||
if (!isGift.value) {
|
||||
uni.hideShareMenu();
|
||||
}
|
||||
getOrderCoupon();
|
||||
})
|
||||
.catch(() => {
|
||||
@ -360,6 +401,12 @@ function goIndex() {
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* H5 分享面板(giftModal 回调)
|
||||
*/
|
||||
function shareH5() {
|
||||
// 保留与旧支付结果页一致的回调占位;链接复制由 giftModal 内部完成
|
||||
}
|
||||
|
||||
// 供壳子(pages/center/pay/status)onMounted 初始化、onShow 刷新,通过 engineRef 调用
|
||||
defineExpose({
|
||||
@ -437,6 +484,17 @@ defineExpose({
|
||||
margin: 0 auto 20rpx auto;
|
||||
}
|
||||
|
||||
/* 送礼分享蒙层:压在内容上、置于 giftModal(z-index 999)之下 */
|
||||
.gift-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.coupons {
|
||||
.title {
|
||||
margin: 30rpx 0 25rpx 0;
|
||||
|
||||
@ -1463,8 +1463,10 @@
|
||||
store_id: system_store.value ? system_store.value.id : 0,
|
||||
}
|
||||
orderReceiveGift(orderId.value, data).then(res => {
|
||||
// 订单详情页按订单号(order_no)跳转,不能传中台主键 id
|
||||
const receivedOrderNo = giftData.value.order_no || '';
|
||||
uni.reLaunch({
|
||||
url: `/pages/goods/receive_gifts_status/index?status=${res.data.status}&order_id=${giftData.value.order_id}`
|
||||
url: `/pages/goods/receive_gifts_status/index?status=${res.data.status}&order_no=${receivedOrderNo}`
|
||||
})
|
||||
}).catch(err => {
|
||||
uni.showToast({
|
||||
|
||||
@ -48,7 +48,8 @@ const image = ref('');
|
||||
const store_name = ref('');
|
||||
const nickname = ref('');
|
||||
const giftStatusText = ref('');
|
||||
const o_id = ref(0);
|
||||
// 订单号:已领取后跳转订单详情使用
|
||||
const orderNo = ref('');
|
||||
// 原代码未在 data 声明、直接挂到实例上的字段
|
||||
let gift_uid;
|
||||
// 原代码 onLoad 中 this.uid = this.$store.state.app.uid 的快照
|
||||
@ -74,8 +75,13 @@ onLoad((options) => {
|
||||
|
||||
function goGetGift() {
|
||||
if (giftStatus.value == 2) {
|
||||
// 已领取:查看礼物订单详情,按订单号跳转(中台主键 id 不能作为 order_no)
|
||||
if (!orderNo.value) {
|
||||
uni.showToast({ title: i18n.t(`订单信息异常`), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.reLaunch({
|
||||
url: `/pages/goods/order_details/index?is_gift=2&order_id=${o_id.value}&is_gift=2`
|
||||
url: `/pages/center/order/detail/index?order_no=${orderNo.value}`
|
||||
});
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
@ -89,9 +95,12 @@ function getOrderInfo() {
|
||||
gift_mark.value = res.data.gift_mark;
|
||||
nickname.value = res.data.nickname;
|
||||
refund_status.value = res.data.refund_status;
|
||||
image.value = res.data.cartInfo[0].productInfo.image;
|
||||
store_name.value = res.data.cartInfo[0].productInfo.store_name;
|
||||
o_id.value = res.data.order_id
|
||||
// order_items 为扁平商品快照;兼容旧结构 productInfo.*
|
||||
const first = (res.data.cartInfo && res.data.cartInfo[0]) || {};
|
||||
const product = first.productInfo || first;
|
||||
image.value = product.image || first.image || '';
|
||||
store_name.value = product.store_name || first.store_name || '';
|
||||
orderNo.value = res.data.order_no || '';
|
||||
if (gift_uid === 0) {
|
||||
giftStatus.value = 1;
|
||||
giftStatusText.value = i18n.t('收下礼物');
|
||||
@ -101,6 +110,8 @@ function getOrderInfo() {
|
||||
} else if (gift_uid !== uidSnapshot) {
|
||||
giftStatus.value = 3;
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.showToast({ title: (err && err.msg) || i18n.t('加载失败'), icon: 'none' });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -24,13 +24,15 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import i18n from '@/utils/lang.js';
|
||||
|
||||
const status = ref(0);
|
||||
const orderId = ref(0);
|
||||
const orderNo = ref('');
|
||||
|
||||
onLoad((options) => {
|
||||
status.value = options.status;
|
||||
orderId.value = options.order_id;
|
||||
// order_no 为现行参数;order_id 兼容历史链接(历史链接中携带的同样是订单号)
|
||||
orderNo.value = options.order_no || options.order_id || '';
|
||||
});
|
||||
|
||||
function goPage(type) {
|
||||
@ -38,11 +40,16 @@ function goPage(type) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: `/pages/goods/order_details/index?order_id=${orderId.value}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 订单详情按订单号跳转(中台主键 id 不能作为 order_no 使用)
|
||||
if (!orderNo.value) {
|
||||
uni.showToast({ title: i18n.t(`订单信息异常`), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.reLaunch({
|
||||
url: `/pages/center/order/detail/index?order_no=${orderNo.value}`
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<view class="acea-row row-between-wrapper px-20 py-14" style="height: 100%">
|
||||
<div class="acea-row">
|
||||
<block v-if="!isCustomEntry">
|
||||
<block v-for="(item_id, index) in showIcons" :key="index">
|
||||
<block v-for="(item_id, index) in displayIcons" :key="index">
|
||||
<navigator :render-link="false"
|
||||
v-if="item_id === 3"
|
||||
hover-class="none"
|
||||
@ -358,6 +358,22 @@ const showIcons = computed(() => {
|
||||
if (!bottomConfig.value) return [3, 1, 2, 0, 4, 5];
|
||||
return bottomConfig.value.showContent.type;
|
||||
});
|
||||
// 送礼入口会占一席,左侧最多只保留 3 个按钮(含送礼)。
|
||||
// 超出时按 首页(3) → 客服(0) → 购物车(2) 的优先级隐藏,收藏/分享保留。
|
||||
// 模板仅渲染 0/1/2/3/4,占位 id 不参与配额,避免误判按钮数量。
|
||||
const RENDERABLE_ICON_IDS = [3, 1, 2, 0, 4];
|
||||
const displayIcons = computed(() => {
|
||||
const icons = (showIcons.value || []).filter((id) => RENDERABLE_ICON_IDS.includes(id));
|
||||
if (!props.is_gift) return (showIcons.value || []).slice();
|
||||
const hideOrder = [3, 0, 2];
|
||||
// 含送礼共 3 个 => 常规图标最多 2 个
|
||||
for (const id of hideOrder) {
|
||||
if (icons.length <= 2) break;
|
||||
const idx = icons.indexOf(id);
|
||||
if (idx > -1) icons.splice(idx, 1);
|
||||
}
|
||||
return icons;
|
||||
});
|
||||
const showCartButton = computed(() => {
|
||||
if (!bottomConfig.value) return true;
|
||||
return bottomConfig.value.cartButton.tabVal === 0;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user