From e7e6a1a18744054cfdc00eedbc3ea8a01fef08b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=98=8A=E5=A4=A9?= <442384644@qq.com> Date: Tue, 27 Oct 2026 13:09:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E8=AE=A2=E5=8D=95=E9=85=8D=E9=80=81):=20?= =?UTF-8?q?=E4=B8=8B=E5=8D=95=E9=85=8D=E9=80=81=E4=B8=8E=E5=8F=91=E8=B4=A7?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E5=88=86=E7=A6=BB=EF=BC=8C=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E5=8F=AA=E5=B1=95=E7=A4=BA=E9=85=8D=E9=80=81=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E3=80=81=E8=BD=A8=E8=BF=B9=E6=8C=89=E9=9C=80=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 虚拟发货/商家送货隐藏查看物流;管理端状态优先展示业务子状态。 Co-authored-by: Cursor --- .../services/order/OrderCenterServices.php | 281 ++++++++++-------- .../normal/handler/NormalOrderAdapter.php | 31 +- .../OrderCenterDetailExtendListener.php | 36 ++- .../OrderCenterListExtendListener.php | 35 ++- .../components/DetailOrderInfo.vue | 6 +- .../src/pages/order/orderCenter/detail.vue | 51 +++- .../src/pages/order/orderCenter/index.vue | 31 +- .../src/pages/plugin/normal/order/Detail.vue | 62 ++-- .../order/detail/LogisticsTimeline.vue | 115 +++++-- 9 files changed, 444 insertions(+), 204 deletions(-) diff --git a/backend/app/services/order/OrderCenterServices.php b/backend/app/services/order/OrderCenterServices.php index 5779d3ebd..6709e279f 100644 --- a/backend/app/services/order/OrderCenterServices.php +++ b/backend/app/services/order/OrderCenterServices.php @@ -10,7 +10,6 @@ use app\model\order\OrderCenter; use app\model\product\product\StoreVisit; use app\model\user\User; use app\services\product\product\StoreProductReplyServices; -use app\services\shipping\ExpressServices; use app\jobs\UnpaidOrderSend; use crmeb\services\CacheService; use crmeb\basic\BaseService; @@ -609,8 +608,7 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac // 前端 AfterSaleProgress 组件据此渲染,不再硬编码售后类型映射表 $this->loadAfterSaleExtension($detail); - // 加载物流轨迹扩展数据(已发货订单调用物流查询驱动获取 tracks) - // 前端 LogisticsTimeline 组件据此渲染物流轨迹卡片,无需点"查看物流"单独查询 + // 加载配送信息扩展(快递公司/单号);轨迹节点改由「查看物流」页按需查询,详情页不再拉第三方轨迹 $this->loadLogisticsExtension($detail, $withRemoteLogistics); // 加载订单完成态权益扩展数据(积分奖励/领券入口,系统内置能力,中台自行组装) @@ -920,7 +918,54 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac 'order' => 50, ]; - // 65. 核销码(有核销码的订单:自提订单 或 已支付的非自提订单如酒店预订) + // 模块顺序:收货信息(40) → 商品信息(50) → 订单信息 → 价格信息 → 配送信息 → 发票信息 + // 支付时间/方式已在订单信息,实付在价格信息,不再单独下发「支付详情」 + // 55. 订单信息(始终存在) + $sections[] = [ + 'key' => 'order_info', + 'component' => 'info_list', + 'props' => ['fields' => $detail['order_info'] ?? [], 'title' => '订单信息'], + 'visible' => true, + 'order' => 55, + ]; + + // 60. 价格明细(始终存在) + $sections[] = [ + 'key' => 'pricing', + 'component' => 'price_detail', + 'props' => [ + 'items' => $detail['pricing']['items'] ?? [], + 'pay_price' => $detail['pricing']['pay_price'] ?? '0.00', + ], + 'visible' => true, + 'order' => 60, + ]; + + // 70. 配送信息(express:公司/单号;send:送货人/电话) + $logisticsExt = $detail['extensions']['logistics'] ?? null; + $sections[] = [ + 'key' => 'logistics', + 'component' => 'timeline', + 'props' => [ + 'delivery_type' => $logisticsExt['delivery_type'] ?? '', + 'delivery_name' => $logisticsExt['delivery_name'] ?? '', + 'delivery_id' => $logisticsExt['delivery_id'] ?? '', + ], + 'visible' => !empty($logisticsExt), + 'order' => 70, + ]; + + // 75. 发票信息(从 extensions.invoice 取值,由 loadOrderInvoice 组装) + $invoiceExt = $detail['extensions']['invoice'] ?? null; + $sections[] = [ + 'key' => 'invoice', + 'component' => 'info_list', + 'props' => ['fields' => $invoiceExt ?? [], 'title' => '发票信息'], + 'visible' => !empty($invoiceExt), + 'order' => 75, + ]; + + // 52. 核销码(自提等;紧挨商品信息,便于核销) // 可见性由 buildGroupedData 中 verification 对象是否构造决定,此处只需检查 code 是否存在 $verificationVisible = !empty($detail['verification']['code']); $verification = $detail['verification'] ?? []; @@ -933,72 +978,17 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac 'status' => $verification['status'] ?? 'pending', ], 'visible' => $verificationVisible, - 'order' => 65, + 'order' => 52, ]; - // 70. 自定义表单 + // 82. 自定义表单(发票信息之后) $customForm = $detail['extensions']['custom_form'] ?? null; $sections[] = [ 'key' => 'custom_form', 'component' => 'custom_form', 'props' => ['items' => $customForm ?? []], 'visible' => !empty($customForm), - 'order' => 70, - ]; - - // 75. 物流轨迹 - $logisticsExt = $detail['extensions']['logistics'] ?? null; - $sections[] = [ - 'key' => 'logistics', - 'component' => 'timeline', - 'props' => [ - 'tracks' => $logisticsExt['tracks'] ?? [], - 'delivery_name' => $logisticsExt['delivery_name'] ?? '', - 'delivery_id' => $logisticsExt['delivery_id'] ?? '', - ], - 'visible' => !empty($logisticsExt), - 'order' => 75, - ]; - - // 80. 价格明细(始终存在) - $sections[] = [ - 'key' => 'pricing', - 'component' => 'price_detail', - 'props' => [ - 'items' => $detail['pricing']['items'] ?? [], - 'pay_price' => $detail['pricing']['pay_price'] ?? '0.00', - ], - 'visible' => true, - 'order' => 80, - ]; - - // 85. 订单信息(始终存在) - $sections[] = [ - 'key' => 'order_info', - 'component' => 'info_list', - 'props' => ['fields' => $detail['order_info'] ?? [], 'title' => '订单信息'], - 'visible' => true, - 'order' => 85, - ]; - - // 90. 发票信息(从 extensions.invoice 取值,由 loadOrderInvoice 组装) - $invoiceExt = $detail['extensions']['invoice'] ?? null; - $sections[] = [ - 'key' => 'invoice', - 'component' => 'info_list', - 'props' => ['fields' => $invoiceExt ?? [], 'title' => '发票信息'], - 'visible' => !empty($invoiceExt), - 'order' => 90, - ]; - - // 93. 支付详情(已支付订单) - $payment = $detail['payment'] ?? null; - $sections[] = [ - 'key' => 'payment', - 'component' => 'info_list', - 'props' => ['fields' => $payment ?? [], 'title' => '支付详情'], - 'visible' => !empty($payment), - 'order' => 93, + 'order' => 82, ]; // 100. 售后进度 @@ -1301,6 +1291,10 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac if (!empty($detail['invoice'])) { $extensions['invoice'] = $detail['invoice']; } + // 发票功能开关(前端申请开票弹窗依赖 special_invoice 控制专用票可选) + $extensions['invoice_func'] = (bool)sys_config('invoice_func_status', 0); + $extensions['special_invoice'] = $extensions['invoice_func'] + && (bool)sys_config('special_invoice_status', 0); // 上门服务订单:预约信息(供 service_appointment section 渲染) if (!empty($detail['service'])) { $extensions['service'] = $detail['service']; @@ -1388,16 +1382,26 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac return null; } $invoice = is_object($invoice) ? $invoice->toArray() : $invoice; + // 个人抬头电话在 drawer_phone,企业注册电话在 tell;弹窗联系电话优先 drawer_phone + $phone = trim((string)($invoice['drawer_phone'] ?? '')); + if ($phone === '') { + $phone = trim((string)($invoice['tell'] ?? '')); + } return [ + 'name' => $invoice['name'] ?? '', // 兼容查看发票弹窗字段 'title' => $invoice['name'] ?? '', // 发票抬头名称 'header_type' => (int)($invoice['header_type'] ?? 1), // 1=个人 2=企业 - 'type' => (int)($invoice['type'] ?? 1), // 1=电子发票 2=纸质发票 + 'type' => (int)($invoice['type'] ?? 1), // 1=普通发票 2=专用发票 + 'duty_number' => $invoice['duty_number'] ?? '', // 兼容弹窗字段 'tax_number' => $invoice['duty_number'] ?? '', // 税号(企业抬头才有) 'email' => $invoice['email'] ?? '', // 接收邮箱 - 'phone' => $invoice['tell'] ?? '', // 联系电话 + 'drawer_phone' => $phone, // 联系电话(弹窗字段) + 'phone' => $phone, // 联系电话(契约字段) + 'tell' => $invoice['tell'] ?? '', // 企业电话原值 'address' => $invoice['address'] ?? '', // 地址 'bank' => $invoice['bank'] ?? '', // 开户银行 'bank_account' => $invoice['card_number'] ?? '', // 银行账号 + 'remark' => $invoice['remark'] ?? '', // 发票备注 'is_invoice' => (int)($invoice['is_invoice'] ?? 0), // 0=未开票 1=已开票 'invoice_number' => $invoice['invoice_number'] ?? '', // 发票号码(已开票时才有) 'invoice_time' => (int)($invoice['invoice_time'] ?? 0), // 开票时间 @@ -1790,78 +1794,46 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac } /** - * 加载物流轨迹扩展数据 - * 已发货且填了快递单号的订单,读取物流轨迹(默认允许同步查询第三方;详情首屏可只读缓存)并注入 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 + * 加载配送信息扩展数据 + * - express:快递公司 + 运单号(轨迹由「查看物流」按需查询) + * - send:送货人 + 联系电话 + * - fictitious:有人工备注时展示发货备注(无备注时 delivery_id 为自动时间戳,不展示) + * 自提 / 上门不展示本区块 * @param array $detail 订单详情数据(引用传递) - * @param bool $allowRemote 是否允许同步请求第三方物流接口;false 时仅读取已有缓存,避免详情首屏被慢请求阻塞 + * @param bool $allowRemote 兼容旧调用签名,详情页不再使用 * @return void */ protected function loadLogisticsExtension(array &$detail, bool $allowRemote = true): void { - // 仅处理有快递单号的订单(已发货且填写了物流单号) + unset($allowRemote); $deliveryId = (string)($detail['delivery_id'] ?? ''); - if ($deliveryId === '') { - return; - } - - // 到店自提 / 上门服务订单无物流轨迹,仅处理快递配送 - $deliveryType = (string)($detail['delivery_type'] ?? ''); - if ($deliveryType !== '' && $deliveryType !== 'express') { - return; - } - - $orderNo = (string)($detail['order_no'] ?? ''); $deliveryName = (string)($detail['delivery_name'] ?? ''); - // delivery_code 传空字符串而非 null:一号通驱动 query() 第二参数声明为 string,null 会触发 TypeError - $deliveryCode = (string)($detail['delivery_code'] ?? ''); - - // 物流轨迹查询(失败时 tracks 置空,仍展示快递公司 + 单号,不阻断详情渲染) - $tracks = []; - try { - // 收件人手机号(顺丰等物流查询需要) - $phone = ''; - $contact = $detail['contact'] ?? ($detail['extra_info']['contact'] ?? []); - if (is_array($contact) && !empty($contact['phone'])) { - $phone = (string)$contact['phone']; - } - - // 缓存键名:订单号 + 快递单号(ExpressServices 内部按此缓存,已签收缓存 0 秒,未签收缓存 1800 秒) - $cacheName = $orderNo . $deliveryId; - - /** @var ExpressServices $expressServices */ - $expressServices = $this->app->make(ExpressServices::class); - // 详情首屏只读缓存;轨迹按需由前端调用 order/express 异步补齐,避免第三方慢请求阻塞详情 - $rawTracks = $allowRemote - ? $expressServices->query($cacheName, $deliveryId, $deliveryCode, $phone) - : $expressServices->getCachedTracks($cacheName); - - // 归一化轨迹字段为前端统一格式 {title, time, desc} - // 驱动返回格式差异:一号通 {time,status} / 阿里云 {time,context} / 其他兼容 - foreach ($rawTracks ?? [] as $track) { - if (!is_array($track)) { - continue; - } - $tracks[] = [ - 'title' => (string)($track['title'] ?? $track['status'] ?? $track['context'] ?? $track['name'] ?? ''), - 'time' => (string)($track['time'] ?? $track['ftime'] ?? ''), - 'desc' => (string)($track['desc'] ?? ''), - ]; - } - } catch (\Throwable $e) { - // 物流查询失败不阻断订单详情渲染(驱动未配置 / 网络异常等场景降级为仅展示快递公司+单号) - Log::warning('[OrderCenter] loadLogisticsExtension failed: ' . $e->getMessage()); + if ($deliveryId === '' && $deliveryName === '') { + return; + } + + $deliveryType = (string)($detail['delivery_type'] + ?? ($detail['extra_info']['delivery']['delivery_type'] ?? '')); + if (!in_array($deliveryType, ['express', 'send', 'fictitious'], true)) { + return; + } + // 快递须有运单号 + if ($deliveryType === 'express' && $deliveryId === '') { + return; + } + // 虚拟发货:仅当存在发货备注时展示(未填备注时后端用 YmdHis 占位,不算备注) + if ($deliveryType === 'fictitious') { + if ($deliveryId === '' || preg_match('/^\d{14}$/', $deliveryId)) { + return; + } + $deliveryName = $deliveryName !== '' ? $deliveryName : '虚拟发货'; } - // 始终写入 extend.logistics:即使 tracks 查询失败,也展示快递公司 + 单号 $detail['extend']['logistics'] = [ + 'delivery_type' => $deliveryType, 'delivery_name' => $deliveryName, 'delivery_id' => $deliveryId, - 'tracks' => $tracks, + 'tracks' => [], ]; } @@ -2075,6 +2047,61 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac unset($button); } + // 已支付且无退款时,按发票开关/已有申请记录追加「申请开票 / 查看发票」(左下角「更多」) + return $this->appendInvoiceButtons($buttons, $detail); + } + + /** + * 追加发票相关底部按钮(对齐旧版订单详情左下角「更多」) + * + * 条件:已支付且无退款;开启发票功能时可申请;已有开票申请/快照时可查看。 + * placement=more 由前端 ButtonGroup 收入左下角「更多」菜单,不占右侧主操作位。 + * + * @param array $buttons 已有按钮 + * @param array $detail 订单详情 + * @return array + */ + protected function appendInvoiceButtons(array $buttons, array $detail): array + { + $payStatus = (int)($detail['pay_status'] ?? 0); + $refundStatus = (int)($detail['refund_status'] ?? 0); + if ($payStatus !== 1 || $refundStatus !== 0) { + return $buttons; + } + + $invoiceFunc = (bool)sys_config('invoice_func_status', 0); + $hasInvoice = !empty($detail['invoice']); + if (!$invoiceFunc && !$hasInvoice) { + return $buttons; + } + + $orderNo = (string)($detail['order_no'] ?? ''); + $params = $orderNo !== '' ? ['order_no' => $orderNo] : []; + + // 避免适配器或其它扩展重复注入 + $existing = []; + foreach ($buttons as $btn) { + $existing[(string)($btn['action'] ?? '')] = true; + } + + if ($hasInvoice && empty($existing['viewInvoice'])) { + $buttons[] = [ + 'text' => '查看发票', + 'action' => 'viewInvoice', + 'primary' => false, + 'placement' => 'more', + 'params' => $params, + ]; + } elseif ($invoiceFunc && !$hasInvoice && empty($existing['applyInvoice'])) { + $buttons[] = [ + 'text' => '申请开票', + 'action' => 'applyInvoice', + 'primary' => false, + 'placement' => 'more', + 'params' => $params, + ]; + } + return $buttons; } @@ -2853,7 +2880,11 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac } // 订单扩展信息 $item['extra_info'] = $this->parseExtraInfo($item['extra_info']); - $item['business_status'] = $item['extra_info']['business_status'] ?? null; + $businessStatus = $item['extra_info']['business_status'] ?? null; + if (is_array($businessStatus) && empty($businessStatus['label']) && !empty($businessStatus['text'])) { + $businessStatus['label'] = $businessStatus['text']; + } + $item['business_status'] = is_array($businessStatus) ? $businessStatus : null; // 提取联系人信息到顶层,供列表页展示 $item['contact_name'] = $item['extra_info']['contact']['name'] ?? ''; $item['contact_phone'] = $item['extra_info']['contact']['phone'] ?? ''; diff --git a/backend/modules/product/normal/handler/NormalOrderAdapter.php b/backend/modules/product/normal/handler/NormalOrderAdapter.php index 805e60763..3fa5da541 100644 --- a/backend/modules/product/normal/handler/NormalOrderAdapter.php +++ b/backend/modules/product/normal/handler/NormalOrderAdapter.php @@ -380,7 +380,7 @@ class NormalOrderAdapter extends BaseOrderTypeAdapter } } - // 到店自提 / 上门服务无快递物流,列表与详情都不展示「查看物流」 + // 非快递履约(自提/上门/虚拟发货/商家送货)不展示「查看物流」 return $this->filterLogisticsButtons($buttons, $detail); } @@ -401,12 +401,13 @@ class NormalOrderAdapter extends BaseOrderTypeAdapter } /** - * 到店自提(shipping_type=2)/ 上门服务(shipping_type=3)去掉「查看物流」 + * 无真实快递轨迹时去掉「查看物流」: + * - 到店自提 / 上门服务 + * - 虚拟发货(fictitious)/ 商家送货(send) */ protected function filterLogisticsButtons(array $buttons, array $detail): array { - $shippingType = (int)($detail['shipping_type'] ?? 0); - if (!in_array($shippingType, [2, 3], true)) { + if ($this->hasExpressLogistics($detail)) { return $buttons; } return array_values(array_filter( @@ -415,6 +416,28 @@ class NormalOrderAdapter extends BaseOrderTypeAdapter )); } + /** + * 是否具备可查询的快递物流(仅 express 发货) + */ + protected function hasExpressLogistics(array $detail): bool + { + $shippingType = (int)($detail['shipping_type'] ?? 0); + if (in_array($shippingType, [2, 3], true)) { + return false; + } + $deliveryType = (string)($detail['delivery_type'] + ?? ($detail['extra_info']['delivery']['delivery_type'] ?? '')); + if (in_array($deliveryType, ['fictitious', 'send', 'pickup', 'door'], true)) { + return false; + } + // 兼容:发货方式字段被误写成 express,但公司名为「虚拟发货」 + if ((string)($detail['delivery_name'] ?? '') === '虚拟发货') { + return false; + } + // 仅快递发货且已有运单号才展示查看物流 + return $deliveryType === 'express' && (string)($detail['delivery_id'] ?? '') !== ''; + } + /** * 是否送礼订单 */ diff --git a/backend/modules/product/normal/listener/OrderCenterDetailExtendListener.php b/backend/modules/product/normal/listener/OrderCenterDetailExtendListener.php index ccc1b2e31..f9c4d6679 100644 --- a/backend/modules/product/normal/listener/OrderCenterDetailExtendListener.php +++ b/backend/modules/product/normal/listener/OrderCenterDetailExtendListener.php @@ -90,19 +90,33 @@ class OrderCenterDetailExtendListener implements ListenerInterface } } - // 发货配送方式以 extra_info.delivery.delivery_type(发货时写入)为准; - // 顶层 extra_info.delivery_type 是下单收货方式(express/pickup/door), - // 存量订单发货后两者可能不一致,不能让顶层旧值覆盖实际发货方式 - $deliveryType = (string)(($extraInfo['delivery']['delivery_type'] ?? '') ?: ($extraInfo['delivery_type'] ?? '')); - if ($deliveryType === '') { + // 下单配送方式(express/pickup/door)与发货方式(express/send/fictitious)语义不同,禁止互相覆盖。 + // extra_info.delivery_type:下单时写入,供详情「配送方式」展示; + // detail.delivery_type / extra_info.delivery.delivery_type:发货后写入,供物流/虚拟发货展示。 + $orderDeliveryType = (string)($extraInfo['delivery_type'] ?? ''); + if (!in_array($orderDeliveryType, ['express', 'pickup', 'door'], true)) { + // 兼容被误写成发货方式(fictitious/send)的旧数据:按 shipping_type 回退 $shippingType = (int)($detail['shipping_type'] ?? 0); - $deliveryType = $shippingType === 2 ? 'pickup' : ($shippingType === 3 ? 'door' : 'express'); + $orderDeliveryType = $shippingType === 2 ? 'pickup' : ($shippingType === 3 ? 'door' : 'express'); + } + $extraInfo['delivery_type'] = $orderDeliveryType; + + $shipDeliveryType = (string)($detail['delivery_type'] ?? ''); + if (!in_array($shipDeliveryType, ['express', 'send', 'fictitious'], true)) { + $nestedShipType = (string)(($extraInfo['delivery']['delivery_type'] ?? '')); + $shipDeliveryType = in_array($nestedShipType, ['express', 'send', 'fictitious'], true) + ? $nestedShipType + : ''; + } + if ($shipDeliveryType !== '') { + $detail['delivery_type'] = $shipDeliveryType; + $extraInfo['delivery'] = array_merge($extraInfo['delivery'] ?? [], [ + 'delivery_type' => $shipDeliveryType, + ]); + } else { + // 未发货:不把下单配送方式写回顶层 delivery_type,避免物流区误判为已快递发货 + $detail['delivery_type'] = $detail['delivery_type'] ?? ''; } - $extraInfo['delivery_type'] = $deliveryType; - $extraInfo['delivery'] = array_merge($extraInfo['delivery'] ?? [], [ - 'delivery_type' => $deliveryType, - ]); - $detail['delivery_type'] = $deliveryType; // 前端统一的到店自提/上门服务标识(与中台保持一致计算口径) $isPickup = ($detail['shipping_type'] ?? 0) == 2; diff --git a/backend/modules/product/normal/listener/OrderCenterListExtendListener.php b/backend/modules/product/normal/listener/OrderCenterListExtendListener.php index d2531d21d..d1e16e4d0 100644 --- a/backend/modules/product/normal/listener/OrderCenterListExtendListener.php +++ b/backend/modules/product/normal/listener/OrderCenterListExtendListener.php @@ -97,28 +97,37 @@ class OrderCenterListExtendListener $delivery = $extraInfo['delivery'] ?? []; $extDelivery = $mappedExtDeliveries[$orderNo] ?? []; - $deliveryType = (string)($delivery['delivery_type'] ?? ($extraInfo['delivery_type'] ?? '')); - if ($deliveryType === '') { - $deliveryType = (string)($extDelivery['delivery_type'] ?? ''); - } - if ($deliveryType === '') { + // 下单配送方式(express/pickup/door)与发货方式(express/send/fictitious)分离,禁止互相覆盖 + $orderDeliveryType = (string)($extraInfo['delivery_type'] ?? ''); + if (!in_array($orderDeliveryType, ['express', 'pickup', 'door'], true)) { $shippingType = (int)($item['shipping_type'] ?? 0); - $deliveryType = $shippingType === 2 ? 'pickup' : ($shippingType === 3 ? 'door' : 'express'); + $orderDeliveryType = $shippingType === 2 ? 'pickup' : ($shippingType === 3 ? 'door' : 'express'); + } + + $shipDeliveryType = (string)($delivery['delivery_type'] ?? ''); + if (!in_array($shipDeliveryType, ['express', 'send', 'fictitious'], true)) { + $shipDeliveryType = (string)($extDelivery['delivery_type'] ?? ''); + } + if (!in_array($shipDeliveryType, ['express', 'send', 'fictitious'], true)) { + $shipDeliveryType = ''; } $deliveryName = (string)($delivery['delivery_name'] ?? ($extDelivery['delivery_name'] ?? '')); $deliveryId = (string)($delivery['delivery_id'] ?? ($extDelivery['delivery_id'] ?? '')); - $item['delivery_type'] = $deliveryType; + // 顶层 delivery_type:已发货用发货方式,未发货用下单配送方式(列表按钮/标签依赖) + $item['delivery_type'] = $shipDeliveryType !== '' ? $shipDeliveryType : $orderDeliveryType; $item['delivery_name'] = $deliveryName; $item['delivery_id'] = $deliveryId; - $extraInfo['delivery_type'] = $deliveryType; - $extraInfo['delivery'] = array_merge($delivery, [ - 'delivery_type' => $deliveryType, - 'delivery_name' => $deliveryName, - 'delivery_id' => $deliveryId, - ]); + $extraInfo['delivery_type'] = $orderDeliveryType; + if ($shipDeliveryType !== '') { + $extraInfo['delivery'] = array_merge($delivery, [ + 'delivery_type' => $shipDeliveryType, + 'delivery_name' => $deliveryName, + 'delivery_id' => $deliveryId, + ]); + } $item['extra_info'] = $extraInfo; } unset($item); diff --git a/frontend/admin/src/pages/order/orderCenter/components/DetailOrderInfo.vue b/frontend/admin/src/pages/order/orderCenter/components/DetailOrderInfo.vue index 2598d0a85..95d284e3e 100644 --- a/frontend/admin/src/pages/order/orderCenter/components/DetailOrderInfo.vue +++ b/frontend/admin/src/pages/order/orderCenter/components/DetailOrderInfo.vue @@ -103,6 +103,10 @@
付费会员优惠:
¥{{ data.vip_discount || '0.00' }}
+
  • +
    礼品附加费:
    +
    ¥{{ data.gift_price || '0.00' }}
    +
  • 实际支付:
    ¥{{ data.pay_price || '0.00' }}
    @@ -150,7 +154,7 @@
    {{ isExpress ? '物流公司:' : deliveryType === 'send' ? '送货人姓名:' : '发货方式:' }}
    {{ data.delivery_name || '--' }}
  • -
  • +
  • {{ isExpress ? '物流单号:' : deliveryType === 'send' ? '送货人电话:' : '发货备注:' }}
    {{ data.delivery_id || '--' }} diff --git a/frontend/admin/src/pages/order/orderCenter/detail.vue b/frontend/admin/src/pages/order/orderCenter/detail.vue index a82578f8a..a24e6bcff 100644 --- a/frontend/admin/src/pages/order/orderCenter/detail.vue +++ b/frontend/admin/src/pages/order/orderCenter/detail.vue @@ -115,10 +115,47 @@ function getLogisticsData() { }); } -/** 详情状态:用户已删除优先展示「已删除」,否则按 status_code / status_text */ +const CENTER_STATUS_CODES = [ + 'pending_payment', + 'processing', + 'pending_review', + 'completed', + 'canceled', + 'after_sale', + 'refunding', + 'refunded', + 'deleted', +]; + +function getBusinessStatusText(detail) { + if (!detail || typeof detail === 'string') return ''; + const bs = detail.business_status; + if (!bs || typeof bs !== 'object') return ''; + const text = String(bs.label || bs.text || '').trim(); + if (!text) return ''; + const code = String(bs.code || ''); + const genericTexts = [ + '待支付', + '待处理', + '进行中', + '待评价', + '已完成', + '已取消', + '售后中', + '退款中', + '已退款', + '已删除', + ]; + if (!genericTexts.includes(text)) return text; + if (code && !CENTER_STATUS_CODES.includes(code)) return text; + return ''; +} + +/** 有业务子状态(待发货/待收货等)时只展示这一项,否则按中台 status_code */ function getStatusLabel(detail) { if (detail && Number(detail.is_del) === 1) return '已删除'; - if (detail?.status_text) return detail.status_text; + const businessText = getBusinessStatusText(detail); + if (businessText) return businessText; const code = typeof detail === 'string' ? detail : detail?.status_code; const map = { pending_payment: '待支付', @@ -131,12 +168,13 @@ function getStatusLabel(detail) { refunded: '已退款', deleted: '已删除', }; - return map[code] || code || '--'; + return map[code] || detail?.status_text || code || '--'; } /* 与列表 getStatusClass 同口径 */ function getStatusClass(detail) { if (detail && Number(detail.is_del) === 1) return 'danger'; + if (getBusinessStatusText(detail)) return 'primary'; const code = typeof detail === 'string' ? detail : detail?.status_code; const map = { pending_payment: 'warning', @@ -187,6 +225,13 @@ defineExpose({ loadDetail }); line-height: 40px !important; } +.business-status { + margin-top: 8px; + font-size: 12px; + color: #999; + line-height: 1.2; +} + .head { padding: 0 30px 24px; diff --git a/frontend/admin/src/pages/order/orderCenter/index.vue b/frontend/admin/src/pages/order/orderCenter/index.vue index 33387fd9a..84b95a85e 100644 --- a/frontend/admin/src/pages/order/orderCenter/index.vue +++ b/frontend/admin/src/pages/order/orderCenter/index.vue @@ -205,9 +205,6 @@ @@ -469,9 +466,36 @@ function getProductTypeLabel(type) { return item ? item.label : type || '--'; } +const CENTER_STATUS_CODES = [ + 'pending_payment', + 'processing', + 'pending_review', + 'completed', + 'canceled', + 'after_sale', + 'refunding', + 'refunded', + 'deleted', +]; + +function getBusinessStatusText(row) { + if (!row || typeof row !== 'object') return ''; + const bs = row.business_status; + if (!bs || typeof bs !== 'object') return ''; + const text = String(bs.label || bs.text || '').trim(); + if (!text) return ''; + const code = String(bs.code || ''); + const genericTexts = statusCodeOptions.map((o) => o.label).concat(['进行中']); + if (!genericTexts.includes(text)) return text; + if (code && !CENTER_STATUS_CODES.includes(code)) return text; + return ''; +} + function getStatusLabel(rowOrCode) { const row = typeof rowOrCode === 'object' && rowOrCode ? rowOrCode : null; if (row && Number(row.is_del) === 1) return '已删除'; + const businessText = getBusinessStatusText(row); + if (businessText) return businessText; const code = row ? row.status_code : rowOrCode; const item = statusCodeOptions.find((opt) => opt.key === code); return item ? item.label : code || '--'; @@ -480,6 +504,7 @@ function getStatusLabel(rowOrCode) { function getStatusClass(rowOrCode) { const row = typeof rowOrCode === 'object' && rowOrCode ? rowOrCode : null; if (row && Number(row.is_del) === 1) return 'danger'; + if (getBusinessStatusText(row)) return 'primary'; const code = row ? row.status_code : rowOrCode; const map = { pending_payment: 'warning', diff --git a/frontend/admin/src/pages/plugin/normal/order/Detail.vue b/frontend/admin/src/pages/plugin/normal/order/Detail.vue index 8611be857..e86b93fa7 100644 --- a/frontend/admin/src/pages/plugin/normal/order/Detail.vue +++ b/frontend/admin/src/pages/plugin/normal/order/Detail.vue @@ -84,7 +84,7 @@
    - +
    配送方式
    @@ -97,25 +97,23 @@
    - +
    -
    {{ isExpressDelivery ? '物流信息' : '发货信息' }}
    +
    {{ shipInfoSectionTitle }}
    - {{ isExpressDelivery ? '物流公司:' : deliveryType === 'send' ? '送货人姓名:' : '发货方式:' }}{{ shipInfoNameLabel }}{{ detailData.delivery_name || '--' }}
    -
    - {{ - isExpressDelivery ? '物流单号:' : deliveryType === 'send' ? '送货人电话:' : '发货备注:' - }}{{ shipInfoIdLabel }}{{ detailData.delivery_id || '--' }} - -
    + +
    @@ -470,21 +468,47 @@ const orderItems = computed(() => { /** 订单状态与列表同口径(待发货/待收货/待到店等业务状态) */ const businessStatus = computed(() => getOrderBusinessStatus(detailData.value || {})); -/** 配送方式:express 快递 / send 送货 / fictitious 无需配送 / pickup 自提 / door 上门 */ -const deliveryType = computed( - () => detailData.value?.extra_info?.delivery_type || detailData.value?.delivery_type || '', -); +/** 下单配送方式(express/pickup/door),用于「配送方式」区块 */ +const orderDeliveryType = computed(() => { + const t = String(detailData.value?.extra_info?.delivery_type || ''); + if (['express', 'pickup', 'door'].includes(t)) return t; + const shippingType = Number(detailData.value?.shipping_type || 0); + if (shippingType === 2) return 'pickup'; + if (shippingType === 3) return 'door'; + return 'express'; +}); const deliveryTypeText = computed( () => ({ express: '快递配送', - send: '送货上门', - fictitious: '无需配送', pickup: '到店自提', door: '上门服务', - })[deliveryType.value] || '快递配送', + })[orderDeliveryType.value] || '快递配送', ); -const isExpressDelivery = computed(() => deliveryType.value === 'express'); + +/** 发货方式(express/send/fictitious);未发货为空 */ +const shipDeliveryType = computed(() => { + const t = String(detailData.value?.delivery_type || ''); + return ['express', 'send', 'fictitious'].includes(t) && detailData.value?.delivery_name ? t : ''; +}); + +const shipInfoSectionTitle = computed(() => { + if (shipDeliveryType.value === 'fictitious') return '发货信息'; + if (shipDeliveryType.value === 'send') return '送货人信息'; + return '物流信息'; +}); + +const shipInfoNameLabel = computed(() => { + if (shipDeliveryType.value === 'fictitious') return '发货方式:'; + if (shipDeliveryType.value === 'send') return '送货人姓名:'; + return '物流公司:'; +}); + +const shipInfoIdLabel = computed(() => { + if (shipDeliveryType.value === 'fictitious') return '发货备注:'; + if (shipDeliveryType.value === 'send') return '送货人电话:'; + return '物流单号:'; +}); /** 售后进度数据(后端注入到 detail.extend.after_sale) */ const afterSaleInfo = computed(() => { diff --git a/frontend/uni-app/src/dynamic-pages/components/order/detail/LogisticsTimeline.vue b/frontend/uni-app/src/dynamic-pages/components/order/detail/LogisticsTimeline.vue index 90d98b30b..9333ccd35 100644 --- a/frontend/uni-app/src/dynamic-pages/components/order/detail/LogisticsTimeline.vue +++ b/frontend/uni-app/src/dynamic-pages/components/order/detail/LogisticsTimeline.vue @@ -1,55 +1,120 @@