mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
fix(发票): 统一普票/专票口径,详情支持申请开票与查看发票,列表状态对齐中台
专用发票默认关闭并受后台开关控制;旧订单详情接口改走中台。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
ab7e327f7b
commit
2463150b5f
@ -497,7 +497,7 @@ GET /api/center/order/detail/{orderNo}
|
||||
"invoice": { // 发票信息(order_center.invoice 为空时从 eb_store_order_invoice 查询)
|
||||
"title": "公司名称", // 发票抬头名称
|
||||
"header_type": 2, // 1=个人 2=企业
|
||||
"type": 1, // 1=电子发票 2=纸质发票
|
||||
"type": 1, // 1=普通发票 2=专用发票
|
||||
"tax_number": "91110000MA00XXXXXX", // 税号(企业抬头才有)
|
||||
"email": "test@example.com", // 接收邮箱
|
||||
"phone": "13800138000", // 联系电话
|
||||
|
||||
@ -140,7 +140,7 @@ class CenterOrderController
|
||||
})
|
||||
);
|
||||
}
|
||||
// 送礼订单按查看者裁剪:购买者隐藏收货信息/物流/相关按钮,领取人保留只读物流能力
|
||||
// 送礼订单按查看者裁剪:送礼人隐藏收货/物流/评价/售后,领取人保留收货物流与评价
|
||||
$result = $services->applyGiftViewerPolicy($result, $uid);
|
||||
return app('json')->success($result);
|
||||
} catch (\Throwable $e) {
|
||||
@ -282,6 +282,9 @@ class CenterOrderController
|
||||
'door_service_postage' => $doorServicePostage, // 上门服务费用(元)
|
||||
'remark' => true, // 买家留言,与配送方式无关
|
||||
'invoice' => (bool)sys_config('invoice_func_status', 0), // 发票,与商品类型无关
|
||||
// 专用发票:须发票功能开启且专票开关开启
|
||||
'special_invoice' => (bool)sys_config('invoice_func_status', 0)
|
||||
&& (bool)sys_config('special_invoice_status', 0),
|
||||
'integral' => !$isActivityOrder && $integralOpen, // 积分抵扣,与商品类型无关
|
||||
'coupon' => !$isActivityOrder && !empty($couponList), // 优惠券,与商品类型无关
|
||||
'custom_form' => !empty($formattedCustomForm), // 自定义表单,与商品类型无关
|
||||
@ -557,6 +560,9 @@ class CenterOrderController
|
||||
'door_service_postage' => $doorServicePostage, // 上门服务费用(元)
|
||||
'remark' => true, // 买家留言
|
||||
'invoice' => (bool)sys_config('invoice_func_status', 0),// 发票
|
||||
// 专用发票:须发票功能开启且专票开关开启
|
||||
'special_invoice' => (bool)sys_config('invoice_func_status', 0)
|
||||
&& (bool)sys_config('special_invoice_status', 0),
|
||||
'integral' => $integralOpen, // 积分抵扣
|
||||
'coupon' => !empty($couponList), // 优惠券
|
||||
'custom_form' => !empty($cartCustomForm), // 自定义表单(取主商品定义)
|
||||
|
||||
@ -86,29 +86,99 @@ class StoreOrderInvoiceController
|
||||
if ((int)($order['uid'] ?? 0) !== $uid) {
|
||||
return app('json')->fail('订单不存在');
|
||||
}
|
||||
// 本人开票记录
|
||||
// 本人开票记录(申请表为真源);无记录时回退下单快照
|
||||
$orderInvoice = $this->services->getOne(['order_id' => (int)$order['id'], 'uid' => $uid]);
|
||||
$order['invoice'] = $orderInvoice ? $orderInvoice->toArray() : [];
|
||||
if ($orderInvoice) {
|
||||
$invoice = $orderInvoice->toArray();
|
||||
} else {
|
||||
$invoice = is_array($order['invoice'] ?? null) ? $order['invoice'] : [];
|
||||
}
|
||||
// 兼容旧页字段:备注列名、时间展示
|
||||
if ($invoice) {
|
||||
if (!isset($invoice['remark']) && isset($invoice['invoice_reamrk'])) {
|
||||
$invoice['remark'] = $invoice['invoice_reamrk'];
|
||||
}
|
||||
if (isset($invoice['add_time']) && is_numeric($invoice['add_time'])) {
|
||||
$invoice['add_time'] = date('Y-m-d H:i:s', (int)$invoice['add_time']);
|
||||
}
|
||||
if (isset($invoice['invoice_time']) && is_numeric($invoice['invoice_time']) && (int)$invoice['invoice_time'] > 0) {
|
||||
$invoice['invoice_time'] = date('Y-m-d H:i:s', (int)$invoice['invoice_time']);
|
||||
}
|
||||
}
|
||||
$order['invoice'] = $invoice ?: [];
|
||||
|
||||
// 以下映射旧版发票详情页所需的订单字段(旧 eb_store_order 结构已由订单中台替代)
|
||||
$order['order_id'] = $order['order_no'] ?? $uni;
|
||||
$order['add_time_y'] = date('Y-m-d', (int)($order['add_time'] ?? time()));
|
||||
$order['add_time_h'] = date('H:i:s', (int)($order['add_time'] ?? time()));
|
||||
$order['paid'] = (int)($order['pay_status'] ?? 0) == 1;
|
||||
$addTime = (int)($order['add_time'] ?? time());
|
||||
$order['add_time_y'] = date('Y-m-d', $addTime);
|
||||
$order['add_time_h'] = date('H:i:s', $addTime);
|
||||
$payStatus = (int)($order['pay_status'] ?? 0);
|
||||
$order['paid'] = in_array($payStatus, [1, 2], true) ? 1 : 0;
|
||||
$legacyStatus = $this->services->statusToLegacy((string)($order['status_code'] ?? ''));
|
||||
$order['status'] = $legacyStatus;
|
||||
$order['_status'] = [
|
||||
'_type' => $this->services->statusToLegacy($order['status_code'] ?? ''),
|
||||
'_payType' => $order['pay_type_name'] ?? ($order['pay_type'] ?? ''),
|
||||
'_type' => $legacyStatus,
|
||||
'_payType' => (string)($order['pay_type_name'] ?? ($order['pay_type'] ?? '')),
|
||||
];
|
||||
// 收货人信息优先取订单配送快照
|
||||
$contact = is_array($order['contact'] ?? null) ? $order['contact'] : [];
|
||||
$order['real_name'] = $contact['name'] ?? '';
|
||||
$order['user_phone'] = $contact['phone'] ?? '';
|
||||
// 物流信息
|
||||
$logistics = $order['extensions']['logistics'] ?? [];
|
||||
$order['delivery_type'] = ($order['delivery_type'] ?? '') ?: 'express';
|
||||
$order['delivery_name'] = $logistics['delivery_name'] ?? '';
|
||||
$order['delivery_id'] = $logistics['delivery_id'] ?? '';
|
||||
// 商品快照合成旧版 cartInfo,供订单商品组件展示
|
||||
$order['cartInfo'] = $this->services->buildLegacyCartInfo(is_array($order['extra_info'] ?? null) ? $order['extra_info'] : []);
|
||||
|
||||
// 收货人:顶层 contact → extra_info.contact → delivery.contact
|
||||
$extra = is_array($order['extra_info'] ?? null) ? $order['extra_info'] : [];
|
||||
$contact = [];
|
||||
if (is_array($order['contact'] ?? null) && $order['contact']) {
|
||||
$contact = $order['contact'];
|
||||
} elseif (is_array($extra['contact'] ?? null)) {
|
||||
$contact = $extra['contact'];
|
||||
} elseif (is_array($order['delivery']['contact'] ?? null)) {
|
||||
$contact = $order['delivery']['contact'];
|
||||
}
|
||||
$order['real_name'] = trim((string)($contact['name'] ?? ($order['real_name'] ?? '')));
|
||||
if ($order['real_name'] === '') {
|
||||
$order['real_name'] = trim((string)($order['nickname'] ?? ''));
|
||||
}
|
||||
$order['user_phone'] = trim((string)($contact['phone'] ?? ($order['user_phone'] ?? '')));
|
||||
// 收货地址
|
||||
if (empty($order['user_address'])) {
|
||||
$addr = $contact['address'] ?? ($order['delivery']['user_address'] ?? '');
|
||||
if (is_array($addr)) {
|
||||
$order['user_address'] = implode('', array_filter([
|
||||
$addr['province'] ?? '',
|
||||
$addr['city'] ?? '',
|
||||
$addr['district'] ?? '',
|
||||
$addr['detail'] ?? '',
|
||||
]));
|
||||
} else {
|
||||
$order['user_address'] = trim((string)$addr);
|
||||
}
|
||||
}
|
||||
|
||||
// 物流:顶层 → extensions → extra_info.delivery
|
||||
$logistics = is_array($order['extensions']['logistics'] ?? null) ? $order['extensions']['logistics'] : [];
|
||||
$deliveryExtra = is_array($extra['delivery'] ?? null) ? $extra['delivery'] : [];
|
||||
if (empty($order['delivery_name'])) {
|
||||
$order['delivery_name'] = (string)($logistics['delivery_name'] ?? ($deliveryExtra['delivery_name'] ?? ''));
|
||||
}
|
||||
if (empty($order['delivery_id'])) {
|
||||
$order['delivery_id'] = (string)($logistics['delivery_id'] ?? ($deliveryExtra['delivery_id'] ?? ''));
|
||||
}
|
||||
if (empty($order['delivery_type'])) {
|
||||
$shippingType = (int)($order['shipping_type'] ?? 0);
|
||||
$order['delivery_type'] = (string)($logistics['delivery_type']
|
||||
?? ($deliveryExtra['delivery_type'] ?? ($extra['delivery_type'] ?? '')));
|
||||
if ($order['delivery_type'] === '') {
|
||||
$order['delivery_type'] = $shippingType === 2 ? 'pickup' : ($shippingType === 3 ? 'door' : 'express');
|
||||
}
|
||||
}
|
||||
|
||||
// 商品快照:extra_info.items 优先,缺省回退 order_items
|
||||
if (empty($extra['items']) && !empty($order['order_items']) && is_array($order['order_items'])) {
|
||||
$extra['items'] = $order['order_items'];
|
||||
}
|
||||
$order['cartInfo'] = $this->services->buildLegacyCartInfo($extra);
|
||||
$order['mark'] = (string)($order['mark'] ?? ($extra['mark'] ?? ''));
|
||||
$order['use_integral'] = (int)($order['use_integral'] ?? ($order['use_integral_num'] ?? 0));
|
||||
$order['system_store'] = is_array($order['store'] ?? null)
|
||||
? $order['store']
|
||||
: (is_array($order['system_store'] ?? null) ? $order['system_store'] : []);
|
||||
$order['mapKey'] = sys_config('tengxun_map_key');
|
||||
/** @var PayChannelServices $payChannelServices */
|
||||
$payChannelServices = app()->make(PayChannelServices::class);
|
||||
|
||||
@ -22,6 +22,7 @@ use crmeb\services\invoice\Invoice;
|
||||
use app\dao\order\OrderInvoiceDao;
|
||||
use app\services\user\UserInvoiceServices;
|
||||
use app\services\user\UserServices;
|
||||
use crmeb\trade\order\type\OrderTypeRegistry;
|
||||
use think\facade\Log;
|
||||
|
||||
|
||||
@ -78,10 +79,12 @@ class OrderInvoiceServices extends BaseService
|
||||
$field = 'id as invoice_id,order_id,header_type,type,name,duty_number,drawer_phone,email,tell,address,bank,card_number,is_invoice,invoice_number,remark as invoice_reamrk,invoice_time,add_time as invoice_add_time,unique_num,invoice_num,invoice_type,invoice_serial_number,red_invoice_num';
|
||||
$where['is_pay'] = 1;
|
||||
$where['is_del'] = 0;
|
||||
// 订单中台关联查询:仅选择中台表实际存在的字段
|
||||
// 订单中台关联查询:带 source_type/source_id,便于实时推导待发货/待收货
|
||||
$list = $this->dao->getList($where, $field, ['order' => function ($query) {
|
||||
$query->field('id,order_no,uid,pay_price,status_code,refund_status,extra_info,add_time');
|
||||
$query->field('id,order_no,uid,pay_price,status_code,refund_status,extra_info,add_time,source_type,source_id');
|
||||
}], 'add_time desc', $page, $list);
|
||||
// 按源订单类型批量解析业务子状态(extra_info 快照会被 syncStatus 覆盖成 processing,不能直接用)
|
||||
$businessStatusMap = $this->resolveBusinessStatusMap($list);
|
||||
// 批量取下单用户,作为收货人姓名电话的兜底数据
|
||||
$uids = array_filter(array_unique(array_column(array_column($list, 'order'), 'uid')));
|
||||
$userList = $uids ? app()->make(UserServices::class)->getColumn([['uid', 'in', $uids]], 'uid,real_name,nickname,phone', 'uid') : [];
|
||||
@ -100,29 +103,98 @@ class OrderInvoiceServices extends BaseService
|
||||
$contactPhone = trim((string)($contact['phone'] ?? ''));
|
||||
$item['real_name'] = $contactName !== '' ? $contactName : trim((string)($user['real_name'] ?? ($user['nickname'] ?? '')));
|
||||
$item['user_phone'] = $contactPhone !== '' ? $contactPhone : trim((string)($user['phone'] ?? ''));
|
||||
$item['status'] = $this->statusToLegacy($order['status_code'] ?? '');
|
||||
$statusCode = (string)($order['status_code'] ?? '');
|
||||
$item['status_code'] = $statusCode;
|
||||
$centerId = (int)($order['id'] ?? 0);
|
||||
$businessStatus = $businessStatusMap[$centerId] ?? null;
|
||||
if (is_array($businessStatus)) {
|
||||
if (empty($businessStatus['label']) && !empty($businessStatus['text'])) {
|
||||
$businessStatus['label'] = $businessStatus['text'];
|
||||
}
|
||||
$item['business_status'] = $businessStatus;
|
||||
// 列表唯一展示文案:优先业务子状态(待收货),否则中台统一状态
|
||||
$item['status_text'] = (string)($businessStatus['label'] ?: ($businessStatus['text'] ?? ''));
|
||||
} else {
|
||||
$item['business_status'] = null;
|
||||
$item['status_text'] = '';
|
||||
}
|
||||
if ($item['status_text'] === '') {
|
||||
// 管理端与订单中台列表一致:processing 展示「待处理」
|
||||
$item['status_text'] = $statusCode === OrderCenter::STATUS_PROCESSING
|
||||
? '待处理'
|
||||
: (OrderCenter::$statusMap[$statusCode] ?? ($statusCode !== '' ? $statusCode : '--'));
|
||||
}
|
||||
// 兼容旧前端/移动端数字状态
|
||||
$item['status'] = $this->statusToLegacy($statusCode);
|
||||
$item['refund_status'] = $order['refund_status'] ?? 0;
|
||||
$item['add_time'] = date('Y-m-d H:i:s', $order['add_time'] ?? $item['invoice_add_time'] ?? time());
|
||||
$item['invoice_add_time'] = date('Y-m-d H:i:s', $item['invoice_add_time']);
|
||||
// 中台关联原始数据不再下发给前端
|
||||
unset($item['order']);
|
||||
}
|
||||
unset($item);
|
||||
$count = $this->dao->count($where);
|
||||
return compact('list', 'count');
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单中台状态码映射为旧版订单状态数值(兼容发票模块前端展示)
|
||||
* 批量从源订单适配器推导业务子状态
|
||||
* @param array $list 含 order 关联的发票列表
|
||||
* @return array<int, array|null> order_center.id => business_status
|
||||
*/
|
||||
protected function resolveBusinessStatusMap(array $list): array
|
||||
{
|
||||
$grouped = [];
|
||||
$centerIndex = [];
|
||||
foreach ($list as $row) {
|
||||
$order = $row['order'] ?? [];
|
||||
$centerId = (int)($order['id'] ?? 0);
|
||||
$sourceType = (string)($order['source_type'] ?? '');
|
||||
$sourceId = (int)($order['source_id'] ?? 0);
|
||||
if ($centerId <= 0 || $sourceType === '' || $sourceId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$grouped[$sourceType][$sourceId] = true;
|
||||
$centerIndex[$sourceType][$sourceId][] = $centerId;
|
||||
}
|
||||
$result = [];
|
||||
foreach ($grouped as $sourceType => $idMap) {
|
||||
try {
|
||||
$adapter = OrderTypeRegistry::getAdapter($sourceType);
|
||||
$modelClass = $adapter::getModelClass();
|
||||
/** @var \crmeb\basic\BaseModel $model */
|
||||
$model = app()->make($modelClass);
|
||||
$sourceRows = $model->whereIn('id', array_keys($idMap))->select()->toArray();
|
||||
foreach ($sourceRows as $sourceOrder) {
|
||||
$sourceId = (int)($sourceOrder['id'] ?? 0);
|
||||
$businessStatus = $adapter->resolveBusinessStatus($sourceOrder);
|
||||
foreach ($centerIndex[$sourceType][$sourceId] ?? [] as $centerId) {
|
||||
$result[$centerId] = $businessStatus;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('发票列表推导订单业务状态失败: ' . $e->getMessage(), ['source_type' => $sourceType]);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单中台状态码映射为旧版订单状态数值(兼容移动端发票列表 _type)
|
||||
* @param string $statusCode
|
||||
* @return int
|
||||
*/
|
||||
public function statusToLegacy(string $statusCode): int
|
||||
{
|
||||
return match ($statusCode) {
|
||||
OrderCenter::STATUS_PENDING_PAYMENT => 0,
|
||||
OrderCenter::STATUS_PROCESSING => 0,
|
||||
OrderCenter::STATUS_PENDING_REVIEW => 2,
|
||||
OrderCenter::STATUS_COMPLETED => 3,
|
||||
OrderCenter::STATUS_CANCELED => -1,
|
||||
OrderCenter::STATUS_AFTER_SALE, OrderCenter::STATUS_REFUNDING => -3,
|
||||
OrderCenter::STATUS_REFUNDED => -2,
|
||||
OrderCenter::STATUS_DELETED => -4,
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
@ -145,11 +217,13 @@ class OrderInvoiceServices extends BaseService
|
||||
$extra = $order['extra_info'] ?? '';
|
||||
$extra = is_string($extra) ? (json_decode($extra, true) ?: []) : ($extra ?: []);
|
||||
$order['cartInfo'] = $this->buildLegacyCartInfo($extra);
|
||||
$order['pay_price'] = $order['pay_price'] ?? '0.00';
|
||||
// 移动端跳转开票详情携带的订单号对应中台 order_no
|
||||
$order['order_id'] = $order['order_no'] ?? '';
|
||||
unset($order['extra_info']);
|
||||
$item['order'] = $order;
|
||||
}
|
||||
unset($item);
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ INSERT INTO `eb_page_link` (`id`, `cate_id`, `type`, `name`, `url`, `param`, `ex
|
||||
(37, 20, 1, '待评价订单', '/pages/goods/order_list/index?status=3', '', '', 1, 0, 1772520919),
|
||||
(38, 20, 1, '联系客服', '/pages/extension/customer_list/chat', '', '', 1, 0, 1772520919),
|
||||
(39, 20, 1, '我的收藏', '/pages/users/user_goods_collection/index', '', '', 1, 0, 1772520919),
|
||||
(40, 20, 1, '发票管理', '/pages/users/user_invoice_list/index', '', '', 1, 0, 1772520919),
|
||||
(40, 20, 1, '发票管理', '/pages/users/user_invoice_list/index', '', '', 1, 2, 1772520919),
|
||||
(41, 20, 1, '每日签到', '/pages/users/user_sgin/index', '', '', 1, 0, 1772520919),
|
||||
(42, 20, 1, '客服接待', '/kefu/mobile_list', '', '', 1, 0, 1772520919),
|
||||
(54, 20, 1, '订单核销', '/pages/admin/order_cancellation/index', '', '', 1, 0, 1772520919);
|
||||
(54, 20, 1, '订单核销', '/pages/admin/order_cancellation/index', '', '', 1, 1, 1772520919);
|
||||
@ -83,11 +83,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" min-width="130">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.status === 0">未发货</div>
|
||||
<div v-else-if="scope.row.status === 1">待收货</div>
|
||||
<div v-else-if="scope.row.status === 2">待评价</div>
|
||||
<div v-else-if="scope.row.status === 3">已完成</div>
|
||||
<div v-else-if="scope.row.status === -2">已退款</div>
|
||||
<el-tag :type="getOrderStatusClass(scope.row)">{{ getOrderStatusLabel(scope.row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="300">
|
||||
@ -131,8 +127,8 @@
|
||||
>
|
||||
<el-divider v-if="scope.row.is_invoice !== 1 && scope.row.refund_status == 0" direction="vertical" />
|
||||
</template>
|
||||
<a v-if="scope.row.status != -2" v-db-click @click="edit(scope.row)">操作</a>
|
||||
<el-divider v-if="scope.row.status != -2" direction="vertical" />
|
||||
<a v-if="!isRefundedOrder(scope.row)" v-db-click @click="edit(scope.row)">操作</a>
|
||||
<el-divider v-if="!isRefundedOrder(scope.row)" direction="vertical" />
|
||||
<a v-db-click @click="orderInfo(scope.row.id)">订单信息</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -332,6 +328,77 @@ const orderData = ref({
|
||||
const orderId = ref(0);
|
||||
const invoiceId = ref(0);
|
||||
|
||||
/** 中台统一状态码(有业务子状态时不叠展示「待处理」等) */
|
||||
const CENTER_STATUS_CODES = [
|
||||
'pending_payment',
|
||||
'processing',
|
||||
'pending_review',
|
||||
'completed',
|
||||
'canceled',
|
||||
'after_sale',
|
||||
'refunding',
|
||||
'refunded',
|
||||
'deleted',
|
||||
];
|
||||
|
||||
/** 与订单中台列表 statusCodeOptions 同口径 */
|
||||
const ORDER_STATUS_OPTIONS = [
|
||||
{ key: 'pending_payment', label: '待支付' },
|
||||
{ key: 'processing', label: '待处理' },
|
||||
{ key: 'pending_review', label: '待评价' },
|
||||
{ key: 'completed', label: '已完成' },
|
||||
{ key: 'canceled', label: '已取消' },
|
||||
{ key: 'after_sale', label: '售后中' },
|
||||
{ key: 'refunding', label: '退款中' },
|
||||
{ key: 'refunded', label: '已退款' },
|
||||
{ key: 'deleted', label: '已删除' },
|
||||
];
|
||||
|
||||
function getBusinessStatusText(row) {
|
||||
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 = ORDER_STATUS_OPTIONS.map((o) => o.label).concat(['进行中']);
|
||||
// 待发货/待收货等业务文案优先;中台统一码快照(待处理/进行中)忽略
|
||||
if (!genericTexts.includes(text)) return text;
|
||||
if (code && !CENTER_STATUS_CODES.includes(code)) return text;
|
||||
return '';
|
||||
}
|
||||
|
||||
function getOrderStatusLabel(row) {
|
||||
if (row && Number(row.is_del) === 1) return '已删除';
|
||||
// 后端已按源订单实时推导 status_text(待收货/待发货优先)
|
||||
if (row?.status_text) return row.status_text;
|
||||
const businessText = getBusinessStatusText(row);
|
||||
if (businessText) return businessText;
|
||||
const code = row?.status_code;
|
||||
const item = ORDER_STATUS_OPTIONS.find((opt) => opt.key === code);
|
||||
return item ? item.label : code || '--';
|
||||
}
|
||||
|
||||
function getOrderStatusClass(row) {
|
||||
if (row && Number(row.is_del) === 1) return 'danger';
|
||||
if (getBusinessStatusText(row)) return 'primary';
|
||||
const map = {
|
||||
pending_payment: 'warning',
|
||||
processing: 'primary',
|
||||
pending_review: 'success',
|
||||
completed: 'success',
|
||||
canceled: 'danger',
|
||||
after_sale: 'warning',
|
||||
refunding: 'warning',
|
||||
refunded: 'info',
|
||||
deleted: 'danger',
|
||||
};
|
||||
return map[row?.status_code] || 'info';
|
||||
}
|
||||
|
||||
function isRefundedOrder(row) {
|
||||
return row?.status_code === 'refunded' || Number(row?.status) === -2;
|
||||
}
|
||||
|
||||
// created
|
||||
getTabs();
|
||||
getList();
|
||||
@ -541,4 +608,8 @@ function onClickTab() {
|
||||
:deep(.order-detail-drawer .el-drawer__body) {
|
||||
padding: 20px 0 !important;
|
||||
}
|
||||
.business-status {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -138,11 +138,13 @@ export function getGiftOrderDetail(id) {
|
||||
return request.get('order/gift_detail/' + id);
|
||||
}
|
||||
/**
|
||||
* 订单详情
|
||||
* @param string uni
|
||||
* 订单详情(兼容旧调用:已迁移至中台 center/order/detail)
|
||||
* @param string uni 订单号
|
||||
*/
|
||||
export function getOrderDetail(uni, cart_id) {
|
||||
return request.get('order/detail/' + uni + `${cart_id ? `/${cart_id}`:''}`);
|
||||
// 旧路由 order/detail 已下线;统一走中台详情。cart_id 参数已废弃。
|
||||
void cart_id;
|
||||
return request.get(`center/order/detail/${uni}`);
|
||||
}
|
||||
/**
|
||||
* 退款订单详情
|
||||
|
||||
@ -128,14 +128,18 @@ export default {
|
||||
buyAgain(order, params) {
|
||||
repurchaseToOrderCreate(order, params);
|
||||
},
|
||||
/** 删除订单 */
|
||||
deleteOrder(order, params) {
|
||||
/** 删除订单(详情页通过 context.onDelete 自行调接口并返回列表;列表页走事件总线) */
|
||||
deleteOrder(order, params, context) {
|
||||
const orderNo = params.order_no || order.order_no || '';
|
||||
uni.showModal({
|
||||
title: i18n.t(`提示`),
|
||||
content: i18n.t(`确定删除该订单吗?删除后不可恢复`),
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
if (context && typeof context.onDelete === 'function') {
|
||||
context.onDelete({ order_no: orderNo });
|
||||
return;
|
||||
}
|
||||
uni.$emit('order:delete', { order_no: orderNo });
|
||||
}
|
||||
},
|
||||
@ -208,4 +212,24 @@ export default {
|
||||
refunded(order, params) {
|
||||
uni.navigateTo({ url: routes.orderDetail + '?order_no=' + order.order_no });
|
||||
},
|
||||
/**
|
||||
* 申请开票(详情页通过 context.onApplyInvoice 打开抬头选择弹窗)
|
||||
*/
|
||||
applyInvoice(order, params, context) {
|
||||
if (context && typeof context.onApplyInvoice === 'function') {
|
||||
context.onApplyInvoice(params || {});
|
||||
return;
|
||||
}
|
||||
uni.showToast({ title: i18n.t(`请在订单详情中申请开票`), icon: 'none' });
|
||||
},
|
||||
/**
|
||||
* 查看发票(详情页通过 context.onViewInvoice 打开发票信息弹窗)
|
||||
*/
|
||||
viewInvoice(order, params, context) {
|
||||
if (context && typeof context.onViewInvoice === 'function') {
|
||||
context.onViewInvoice(params || {});
|
||||
return;
|
||||
}
|
||||
uni.showToast({ title: i18n.t(`请在订单详情中查看发票`), icon: 'none' });
|
||||
},
|
||||
};
|
||||
|
||||
@ -107,8 +107,8 @@ const props = defineProps({
|
||||
},
|
||||
/** 当前选中的抬头 ID(字符串) */
|
||||
checked: { type: String, default: '' },
|
||||
/** 是否启用专用发票(受 features.special_invoice 控制) */
|
||||
specialInvoice: { type: Boolean, default: true },
|
||||
/** 是否启用专用发票(受 features.special_invoice 控制,缺省关闭) */
|
||||
specialInvoice: { type: Boolean, default: false },
|
||||
/** 跳转 user_invoice_form 时携带的回跳参数(不含 ?) */
|
||||
urlQuery: { type: String, default: '' },
|
||||
/** 弹窗显隐(支持 v-model:popupShow) */
|
||||
|
||||
@ -1,20 +1,43 @@
|
||||
<!--
|
||||
底部操作按钮组组件
|
||||
展示订单操作按钮(如去支付、取消订单、确认收货、申请退款等),点击触发对应 action
|
||||
样式对齐旧版订单详情 footer:固定于屏幕底部 + 底部安全区适配 + 右对齐胶囊按钮
|
||||
数据来源:section.props.buttons(每项含 {text, action, primary, params})
|
||||
样式对齐旧版订单详情 footer:固定于屏幕底部 + 底部安全区适配
|
||||
- 右侧:主操作胶囊按钮
|
||||
- 左侧:placement=more 的次要操作收入「更多」菜单(如申请开票/查看发票)
|
||||
数据来源:section.props.buttons(每项含 {text, action, primary, params, placement?})
|
||||
-->
|
||||
<template>
|
||||
<view class="btn-group" v-if="buttons && buttons.length > 0">
|
||||
<view
|
||||
v-for="(btn, idx) in buttons"
|
||||
:key="idx"
|
||||
class="btn-item"
|
||||
:class="btn.primary ? 'btn-primary' : 'btn-default'"
|
||||
@tap="onTap(btn)"
|
||||
>
|
||||
{{ btn.text }}
|
||||
<view class="btn-group" v-if="hasAnyButton">
|
||||
<!-- 左下角「更多」(开票等次要操作) -->
|
||||
<view class="more-wrap" v-if="moreButtons.length">
|
||||
<view class="more" @tap.stop="toggleMore">
|
||||
{{ $t('更多') }}
|
||||
<text class="iconfont icon-xiangshang"></text>
|
||||
</view>
|
||||
<view class="more-box" v-if="moreOpen">
|
||||
<view
|
||||
class="more-btn"
|
||||
v-for="(btn, idx) in moreButtons"
|
||||
:key="'more-' + idx"
|
||||
@tap.stop="onTap(btn)"
|
||||
>{{ btn.text }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="more-placeholder"></view>
|
||||
|
||||
<view class="right-btn">
|
||||
<view
|
||||
v-for="(btn, idx) in mainButtons"
|
||||
:key="'main-' + idx"
|
||||
class="btn-item"
|
||||
:class="btn.primary ? 'btn-primary' : 'btn-default'"
|
||||
@tap="onTap(btn)"
|
||||
>
|
||||
{{ btn.text }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="more-mask" v-if="moreOpen" @tap="moreOpen = false"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -32,7 +55,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
/** section 对象,含 props.buttons 和 _ctx 全局状态 */
|
||||
@ -43,16 +66,34 @@ const emit = defineEmits(['action']);
|
||||
|
||||
/** 后端直填 props */
|
||||
const p = computed(() => (props.section && props.section.props) || {});
|
||||
/** 引擎注入的全局状态(预留) */
|
||||
const ctx = computed(() => (props.section && props.section._ctx) || {});
|
||||
/** 按钮数组 */
|
||||
const buttons = computed(() => p.value.buttons || []);
|
||||
/** placement=more 收入左下角「更多」 */
|
||||
const moreButtons = computed(() =>
|
||||
(buttons.value || []).filter((btn) => (btn.placement || '') === 'more')
|
||||
);
|
||||
/** 右侧主操作按钮 */
|
||||
const mainButtons = computed(() =>
|
||||
(buttons.value || []).filter((btn) => (btn.placement || '') !== 'more')
|
||||
);
|
||||
const hasAnyButton = computed(() => moreButtons.value.length > 0 || mainButtons.value.length > 0);
|
||||
|
||||
const moreOpen = ref(false);
|
||||
|
||||
watch(buttons, () => {
|
||||
moreOpen.value = false;
|
||||
});
|
||||
|
||||
function toggleMore() {
|
||||
moreOpen.value = !moreOpen.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按钮点击事件
|
||||
* @param {object} btn 按钮对象
|
||||
*/
|
||||
function onTap(btn) {
|
||||
moreOpen.value = false;
|
||||
emit('action', {
|
||||
action: btn.action,
|
||||
params: btn.params || {},
|
||||
@ -70,7 +111,7 @@ function onTap(btn) {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom)); /*兼容 IOS<11.2*/
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); /*兼容 IOS>11.2*/
|
||||
@ -78,6 +119,49 @@ function onTap(btn) {
|
||||
border-top: 1rpx solid #f5f5f5;
|
||||
z-index: 20;
|
||||
}
|
||||
.more-placeholder { width: 1rpx; }
|
||||
.more-wrap { position: relative; z-index: 22; }
|
||||
.more {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.more .iconfont { font-size: 22rpx; margin-left: 4rpx; }
|
||||
.more-box {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 70rpx;
|
||||
min-width: 180rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.12);
|
||||
overflow: hidden;
|
||||
z-index: 23;
|
||||
}
|
||||
.more-btn {
|
||||
padding: 24rpx 30rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
.more-btn + .more-btn { border-top: 1rpx solid #f5f5f5; }
|
||||
.more-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 21;
|
||||
background: transparent;
|
||||
}
|
||||
.right-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
/* 按钮胶囊样式(对齐旧版 .footer .bnt) */
|
||||
.btn-item {
|
||||
height: 60rpx;
|
||||
|
||||
@ -16,10 +16,10 @@
|
||||
<text class="invoice-tag">{{ invoice.header_type === 2 ? $t('企业') : $t('个人') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 发票类型:电子/纸质 -->
|
||||
<!-- 发票类型:1=普通 2=专用(与 eb_user_invoice / eb_order_invoice.type 一致) -->
|
||||
<view class="info-row acea-row row-between">
|
||||
<text class="info-label">{{ $t('发票类型') }}</text>
|
||||
<text class="info-value">{{ invoice.type === 2 ? $t('纸质发票') : $t('电子发票') }}</text>
|
||||
<text class="info-value">{{ invoice.type === 2 ? $t('增值税电子专用发票') : $t('增值税电子普通发票') }}</text>
|
||||
</view>
|
||||
<!-- 税号(企业抬头才有) -->
|
||||
<view v-if="invoice.tax_number" class="info-row acea-row row-between">
|
||||
|
||||
@ -242,7 +242,7 @@ const features = ref({
|
||||
door_service: false, // 是否支持上门服务
|
||||
remark: true,
|
||||
invoice: true,
|
||||
special_invoice: true,
|
||||
special_invoice: false,
|
||||
integral: false,
|
||||
coupon: false,
|
||||
custom_form: false,
|
||||
@ -301,7 +301,7 @@ const remark = ref('');
|
||||
const invoiceList = ref([]); // 已保存的抬头列表
|
||||
const invoiceId = ref(''); // 选中的抬头 ID(字符串)
|
||||
const invoicePopupShow = ref(false); // 抬头选择弹窗显隐
|
||||
const specialInvoice = ref(true); // 是否启用专用发票(由 features.special_invoice 控制)
|
||||
const specialInvoice = ref(false); // 是否启用专用发票(由 features.special_invoice 控制)
|
||||
|
||||
// 提交
|
||||
const submitting = ref(false);
|
||||
@ -363,7 +363,7 @@ const invoiceUrlQuery = computed(() => {
|
||||
'&new=' + news.value +
|
||||
'&address_id=' + addressId.value +
|
||||
'&submit_api=' + encodeURIComponent(submitApi.value) +
|
||||
'&specialInvoice=' + (specialInvoice.value ? 'true' : 'false');
|
||||
'&specialInvoice=' + (specialInvoice.value ? '1' : '0');
|
||||
} else {
|
||||
query =
|
||||
'product_id=' + encodeURIComponent(productId.value) +
|
||||
@ -373,7 +373,7 @@ const invoiceUrlQuery = computed(() => {
|
||||
'&activity_id=' + activityId.value +
|
||||
'&num=' + num.value +
|
||||
'&submit_api=' + encodeURIComponent(submitApi.value) +
|
||||
'&specialInvoice=' + (specialInvoice.value ? 'true' : 'false');
|
||||
'&specialInvoice=' + (specialInvoice.value ? '1' : '0');
|
||||
}
|
||||
return query;
|
||||
});
|
||||
@ -482,8 +482,8 @@ async function loadConfirmData() {
|
||||
}
|
||||
// 保存活动订单信息(含免费支付方式标识与跳转地址,由交易模式 Handler 下发)
|
||||
activityInfo.value = data.activity_info || {};
|
||||
// 同步发票相关配置:后端未返回时默认开启专用发票
|
||||
specialInvoice.value = features.value.special_invoice !== false;
|
||||
// 同步发票相关配置(专用发票须后台显式开启,缺省为关)
|
||||
specialInvoice.value = !!features.value.special_invoice;
|
||||
usableIntegral.value = parseInt(data.usable_integral || 0);
|
||||
integralOpen.value = !!data.integral_open;
|
||||
integralRatio.value = data.integral_ratio || 0;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<view :style="colorStyle">
|
||||
<!-- 弹层打开时锁页面滚动,避免滑动穿透到订单详情 -->
|
||||
<page-meta :page-style="pageLocked ? 'overflow:hidden;' : ''" />
|
||||
<view :style="colorStyle" :class="{ 'page-locked': pageLocked }">
|
||||
<page-loading v-if="loading" />
|
||||
<view v-else class="order-details">
|
||||
<!-- =============================================== -->
|
||||
@ -34,7 +36,26 @@
|
||||
@shareH5="shareH5"
|
||||
@close="giftModalShow = false"
|
||||
/>
|
||||
<view class="mask" v-if="giftModalShow" @click="giftModalShow = false"></view>
|
||||
<view class="mask" v-if="giftModalShow || invoiceModalShow" @click="closeInvoiceOverlays"></view>
|
||||
<!-- 查看发票 -->
|
||||
<invoiceModal
|
||||
:aleartStatus="invoiceModalShow"
|
||||
:invoiceData="invoiceModalData"
|
||||
@close="invoiceModalShow = false"
|
||||
/>
|
||||
<!-- 申请开票:选择已有抬头并提交 make_up_invoice -->
|
||||
<invoice-picker
|
||||
:inv-show="invoicePickerShow"
|
||||
:is-special="specialInvoice"
|
||||
:url-query="invoiceUrlQuery"
|
||||
:inv-checked="invoiceCheckedId"
|
||||
:order-id="orderNo"
|
||||
:inv-list="invoiceList"
|
||||
:is-order="1"
|
||||
@inv-close="invoicePickerShow = false"
|
||||
@inv-change="submitInvoiceApply"
|
||||
@inv-cancel="invoicePickerShow = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@ -61,19 +82,25 @@ import { resolveSectionComponent, InfoListComponent } from '../components/order/
|
||||
import SectionDispatcher from '../components/order/detail/SectionDispatcher.vue';
|
||||
// #endif
|
||||
import actionMap from '../api/action-map.js';
|
||||
import { getCenterOrderDetail, takeOrder, cancelOrder } from '@/api/order-center.js';
|
||||
import { express } from '@/api/order.js';
|
||||
import { getCenterOrderDetail, takeOrder, cancelOrder, delOrder } from '@/api/order-center.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';
|
||||
import invoiceModal from '@/pages/goods/components/invoiceModal/index.vue';
|
||||
import invoicePicker from '@/pages/goods/components/invoicePicker/index.vue';
|
||||
import { invoiceList as fetchInvoiceList, makeUpinvoice } from '@/api/user.js';
|
||||
|
||||
defineOptions({ name: 'OrderDetailEngine' });
|
||||
|
||||
const props = defineProps({
|
||||
/** 订单号 */
|
||||
orderNo: { type: String, default: '' },
|
||||
/** 从发票抬头页带回的待选抬头 ID;有值且订单尚未开票时自动打开开票弹层 */
|
||||
pendingInvoiceId: { type: String, default: '' },
|
||||
});
|
||||
/** 向包装页同步开票弹层显隐(用于隐藏悬浮 home) */
|
||||
const emit = defineEmits(['invoice-overlay', 'pending-invoice-consumed']);
|
||||
|
||||
const { colorStyle } = useColorTheme();
|
||||
|
||||
@ -83,9 +110,15 @@ const pageConfig = ref({ sections: [], extensions: {} });
|
||||
const loading = ref(true);
|
||||
const confirmingReceive = ref(false);
|
||||
const cancellingOrder = ref(false);
|
||||
const logisticsLoading = ref(false);
|
||||
const deletingOrder = ref(false);
|
||||
// 送礼分享
|
||||
const giftModalShow = ref(false);
|
||||
// 发票:申请开票 / 查看发票
|
||||
const invoicePickerShow = ref(false);
|
||||
const invoiceModalShow = ref(false);
|
||||
const invoiceList = ref([]);
|
||||
const invoiceCheckedId = ref('');
|
||||
const invoiceApplying = ref(false);
|
||||
|
||||
// ---------- computed ----------
|
||||
/**
|
||||
@ -129,6 +162,27 @@ const viewerRole = computed(() => {
|
||||
const orderInfoFields = computed(() => {
|
||||
return (sectionPropsMap.value['order_info'] && sectionPropsMap.value['order_info'].fields) || {};
|
||||
});
|
||||
/** 是否启用专用发票(extensions.special_invoice) */
|
||||
const specialInvoice = computed(() => !!(pageConfig.value.extensions && pageConfig.value.extensions.special_invoice));
|
||||
/** 跳转抬头表单时携带的回跳参数 */
|
||||
const invoiceUrlQuery = computed(() => `specialInvoice=${specialInvoice.value ? 1 : 0}`);
|
||||
/**
|
||||
* 查看发票弹窗数据(兼容 invoiceModal 使用的 name/duty_number 字段)
|
||||
*/
|
||||
const invoiceModalData = computed(() => {
|
||||
const inv = (pageConfig.value.extensions && pageConfig.value.extensions.invoice) || {};
|
||||
const phone = inv.drawer_phone || inv.phone || inv.tell || '';
|
||||
return {
|
||||
...inv,
|
||||
name: inv.name || inv.title || '',
|
||||
duty_number: inv.duty_number || inv.tax_number || '',
|
||||
drawer_phone: phone,
|
||||
phone,
|
||||
remark: inv.remark || '',
|
||||
is_invoice: Number(inv.is_invoice || 0),
|
||||
pay_price: payPrice.value,
|
||||
};
|
||||
});
|
||||
/** giftModal 分享数据 */
|
||||
const giftShareData = computed(() => {
|
||||
// section.props.product = { product: {title,image,items}, product_id }
|
||||
@ -170,9 +224,9 @@ async function loadDetail() {
|
||||
uni[r.mode || 'redirectTo']({ url: r.url });
|
||||
return;
|
||||
}
|
||||
pageConfig.value = (res.data && res.data.page_config) || { sections: [], extensions: {} };
|
||||
// 详情接口首屏只返回已缓存的物流轨迹,未命中时异步补齐,避免被第三方物流慢请求阻塞
|
||||
loadLogisticsTracks();
|
||||
pageConfig.value = sanitizePageConfig((res.data && res.data.page_config) || { sections: [], extensions: {} });
|
||||
// 详情页配送信息只展示公司/单号,轨迹由「查看物流」单独查询,此处不再异步拉轨迹
|
||||
maybeOpenPendingInvoice();
|
||||
} catch (e) {
|
||||
uni.showToast({ title: i18n.t('加载失败'), icon: 'none' });
|
||||
} finally {
|
||||
@ -181,44 +235,65 @@ async function loadDetail() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步加载物流轨迹
|
||||
* 详情接口不再同步请求第三方物流,仅返回已缓存轨迹;未命中时由前端按需查询并回填 section
|
||||
* 失败时静默降级,仍展示快递公司 + 单号
|
||||
* @return {Promise<void>}
|
||||
* 从发票抬头编辑页返回时:若带回 invoice_id 且订单尚未开票,自动打开抬头选择并预选该抬头
|
||||
*/
|
||||
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;
|
||||
function maybeOpenPendingInvoice() {
|
||||
const invoiceId = String(props.pendingInvoiceId || '');
|
||||
if (!invoiceId) return;
|
||||
emit('pending-invoice-consumed');
|
||||
const inv = (pageConfig.value.extensions && pageConfig.value.extensions.invoice) || {};
|
||||
// 已有开票申请则不再弹层
|
||||
if (inv && (inv.id || inv.invoice_id || inv.name || inv.title)) {
|
||||
return;
|
||||
}
|
||||
openInvoiceApply(invoiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪查看物流按钮:虚拟发货 / 商家送货 / 无配送信息区时不展示「查看物流」
|
||||
* (配送信息区本身由后端按类型下发;虚拟发货有备注时仍保留区块)
|
||||
* @param {object} config page_config
|
||||
* @return {object}
|
||||
*/
|
||||
function sanitizePageConfig(config) {
|
||||
const next = config || { sections: [], extensions: {} };
|
||||
const sections = Array.isArray(next.sections) ? next.sections : [];
|
||||
const logistics = sections.find(function (s) { return s && s.key === 'logistics'; });
|
||||
const deliveryType = String((logistics && logistics.props && logistics.props.delivery_type) || '');
|
||||
const stripLogisticsBtn = deliveryType === 'fictitious'
|
||||
|| deliveryType === 'send'
|
||||
|| !logistics
|
||||
|| logistics.visible === false;
|
||||
if (!stripLogisticsBtn) return next;
|
||||
|
||||
const stripViewLogistics = function (list) {
|
||||
if (!Array.isArray(list)) return list;
|
||||
return list.filter(function (btn) { return (btn && btn.action) !== 'viewLogistics'; });
|
||||
};
|
||||
const ex = next.extensions || {};
|
||||
if (ex.order_buttons) ex.order_buttons = stripViewLogistics(ex.order_buttons);
|
||||
if (ex.buttons) ex.buttons = stripViewLogistics(ex.buttons);
|
||||
next.extensions = ex;
|
||||
next.sections = sections.map(function (s) {
|
||||
if (!s || !s.props || !Array.isArray(s.props.buttons)) return s;
|
||||
return Object.assign({}, s, {
|
||||
props: Object.assign({}, s.props, { buttons: stripViewLogistics(s.props.buttons) }),
|
||||
});
|
||||
});
|
||||
return next;
|
||||
}
|
||||
|
||||
// 包装页 onShow 返回时调用(评价完成/售后进度变化等场景,返回详情需看到最新状态)
|
||||
defineExpose({ refresh: loadDetail });
|
||||
/** 开票/查看发票弹层是否打开(供包装页隐藏悬浮 home) */
|
||||
const invoiceOverlayOpen = computed(() => invoicePickerShow.value || invoiceModalShow.value);
|
||||
/** 任意遮罩弹层打开时锁定页面滚动 */
|
||||
const pageLocked = computed(() => invoiceOverlayOpen.value || giftModalShow.value);
|
||||
|
||||
watch(invoiceOverlayOpen, (open) => {
|
||||
emit('invoice-overlay', open);
|
||||
}, { immediate: true });
|
||||
|
||||
defineExpose({ refresh: loadDetail, invoiceOverlayOpen });
|
||||
|
||||
// ========== 全局状态注入 ==========
|
||||
|
||||
@ -291,8 +366,14 @@ function onButtonAction(payload) {
|
||||
context = { onConfirm: confirmReceive };
|
||||
} else if (payload.action === 'cancelOrder') {
|
||||
context = { onCancel: cancelUnpaidOrder };
|
||||
} else if (payload.action === 'deleteOrder') {
|
||||
context = { onDelete: deleteOrderAndBack };
|
||||
} else if (payload.action === 'shareGift') {
|
||||
context = { onShareGift: openGiftShare };
|
||||
} else if (payload.action === 'applyInvoice') {
|
||||
context = { onApplyInvoice: openInvoiceApply };
|
||||
} else if (payload.action === 'viewInvoice') {
|
||||
context = { onViewInvoice: openInvoiceView };
|
||||
}
|
||||
handler(Object.assign({}, sectionPropsMap.value, { order_no: orderNo }), payload.params || {}, context);
|
||||
}
|
||||
@ -305,6 +386,72 @@ function openGiftShare() {
|
||||
giftModalShow.value = true;
|
||||
}
|
||||
|
||||
/** 关闭发票相关浮层 */
|
||||
function closeInvoiceOverlays() {
|
||||
invoiceModalShow.value = false;
|
||||
giftModalShow.value = false;
|
||||
}
|
||||
|
||||
/** 查看发票 */
|
||||
function openInvoiceView() {
|
||||
if (!invoiceModalData.value || !(invoiceModalData.value.name || invoiceModalData.value.title)) {
|
||||
uni.showToast({ title: i18n.t('暂无发票信息'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
invoiceModalShow.value = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请开票:拉取抬头列表并打开选择弹窗
|
||||
* @param {string} [preselectId] 预选抬头 ID(从抬头编辑页返回时传入)
|
||||
*/
|
||||
function openInvoiceApply(preselectId = '') {
|
||||
uni.showLoading({ title: i18n.t('正在加载中'), mask: true });
|
||||
fetchInvoiceList()
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
invoiceList.value = (res.data || []).map((item) => {
|
||||
item.id = String(item.id);
|
||||
return item;
|
||||
});
|
||||
invoiceCheckedId.value = preselectId ? String(preselectId) : '';
|
||||
invoicePickerShow.value = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: (typeof err === 'string' ? err : err?.msg) || i18n.t('加载失败'), icon: 'none' });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认提交开票申请
|
||||
* @param {string} invoiceId 选中的抬头 ID
|
||||
*/
|
||||
function submitInvoiceApply(invoiceId) {
|
||||
if (!invoiceId) {
|
||||
uni.showToast({ title: i18n.t('请选择发票抬头'), icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (invoiceApplying.value) return;
|
||||
invoiceApplying.value = true;
|
||||
makeUpinvoice({
|
||||
order_id: props.orderNo,
|
||||
invoice_id: invoiceId,
|
||||
})
|
||||
.then(async () => {
|
||||
uni.showToast({ title: i18n.t('申请成功'), icon: 'success' });
|
||||
invoicePickerShow.value = false;
|
||||
await loadDetail();
|
||||
invoiceModalShow.value = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.showToast({ title: (typeof err === 'string' ? err : err?.msg) || i18n.t('申请失败'), icon: 'none' });
|
||||
})
|
||||
.finally(() => {
|
||||
invoiceApplying.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页确认收货
|
||||
* @param {object} payload {order_no}
|
||||
@ -343,6 +490,32 @@ async function cancelUnpaidOrder(payload) {
|
||||
cancellingOrder.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页删除订单:成功后通知列表刷新并返回列表(订单已不存在,不可留在详情)
|
||||
* @param {object} payload {order_no}
|
||||
*/
|
||||
async function deleteOrderAndBack(payload) {
|
||||
if (deletingOrder.value) return;
|
||||
deletingOrder.value = true;
|
||||
try {
|
||||
await delOrder(payload.order_no);
|
||||
uni.$emit('order:list-refresh');
|
||||
uni.showToast({ title: i18n.t('删除成功'), icon: 'success' });
|
||||
setTimeout(() => {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
} else {
|
||||
uni.redirectTo({ url: '/pages/goods/order_list/index' });
|
||||
}
|
||||
}, 500);
|
||||
} catch (e) {
|
||||
uni.showToast({ title: (typeof e === 'string' ? e : e?.msg) || i18n.t('删除失败'), icon: 'none' });
|
||||
} finally {
|
||||
deletingOrder.value = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 行动入口卡片点击(ActionCard emit)
|
||||
* @param {object} payload {action, link}
|
||||
@ -456,6 +629,12 @@ function shareH5() {
|
||||
padding-bottom: calc(150rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* H5:弹层打开时锁住页面滚动(小程序侧靠 page-meta) */
|
||||
.page-locked {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
@ -463,7 +642,7 @@ function shareH5() {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
/* 低于 giftModal 的 999,避免遮罩盖住弹窗按钮 */
|
||||
/* 低于 giftModal 的 999,避免遮罩盖住弹窗按钮;高于开票弹层由 invoicePicker 自管 */
|
||||
z-index: 998;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
<template>
|
||||
<view>
|
||||
<OrderDetailEngine ref="engine" :order-no="orderNo" />
|
||||
<!-- 系统悬浮菜单(小程序端不显示) -->
|
||||
<OrderDetailEngine
|
||||
ref="engine"
|
||||
:order-no="orderNo"
|
||||
:pending-invoice-id="pendingInvoiceId"
|
||||
@invoice-overlay="onInvoiceOverlay"
|
||||
@pending-invoice-consumed="pendingInvoiceId = ''"
|
||||
/>
|
||||
<!-- 系统悬浮菜单(小程序端不显示);开票弹层打开时隐藏,避免压在弹层上 -->
|
||||
<!-- #ifndef MP -->
|
||||
<home></home>
|
||||
<home v-show="!invoiceOverlayOpen"></home>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
@ -15,10 +21,18 @@ import OrderDetailEngine from '@/dynamic-pages/engines/OrderDetailEngine.vue';
|
||||
import home from '@/components/home';
|
||||
|
||||
const orderNo = ref('');
|
||||
/** 从发票抬头页带回的待选抬头 ID,用于自动打开开票弹层 */
|
||||
const pendingInvoiceId = ref('');
|
||||
const engine = ref(null);
|
||||
const invoiceOverlayOpen = ref(false);
|
||||
|
||||
function onInvoiceOverlay(open) {
|
||||
invoiceOverlayOpen.value = !!open;
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
orderNo.value = options.order_no || '';
|
||||
pendingInvoiceId.value = options.invoice_id ? String(options.invoice_id) : '';
|
||||
});
|
||||
|
||||
// 首次 onShow 与 onLoad 引擎的首次加载重复,跳过;此后每次返回本页(评价完成 navigateBack 等)刷新详情数据
|
||||
|
||||
@ -1,212 +1,207 @@
|
||||
<template>
|
||||
<view class="aleart" v-if="aleartStatus" :style="colorStyle">
|
||||
<!-- 顶部状态:图标半悬于弹窗外,文案居中 -->
|
||||
<view class="icon-top">
|
||||
<text class="iconfont icon-fapiao2"
|
||||
:style="invoiceData.is_invoice?'background-color: var(--view-theme)':'background-color: #999'"></text>
|
||||
<view
|
||||
class="icon-wrap"
|
||||
:class="invoiceData.is_invoice ? 'on' : 'off'"
|
||||
>
|
||||
<text class="iconfont icon-fapiao2"></text>
|
||||
</view>
|
||||
<view class="bill">
|
||||
{{invoiceData.is_invoice?$t(`已开票`): $t(`未开票`)}}
|
||||
{{ invoiceData.is_invoice ? $t(`已开票`) : $t(`未开票`) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="aleart-body">
|
||||
<view class="body-head">{{$t(`发票信息`)}}</view>
|
||||
<view class="body-head">{{ $t(`发票信息`) }}</view>
|
||||
<view class="label">
|
||||
<view class="">
|
||||
{{$t(`发票抬头`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.name}}
|
||||
</view>
|
||||
<view>{{ $t(`发票抬头`) }}</view>
|
||||
<view class="label-value">{{ invoiceData.name || invoiceData.title || '--' }}</view>
|
||||
</view>
|
||||
<view class="label">
|
||||
<view class="">
|
||||
{{$t(`发票抬头类型`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.header_type == 1?$t(`个人`):$t(`企业`)}}
|
||||
</view>
|
||||
<view>{{ $t(`发票抬头类型`) }}</view>
|
||||
<view class="label-value">{{ invoiceData.header_type == 1 ? $t(`个人`) : $t(`企业`) }}</view>
|
||||
</view>
|
||||
<view class="label">
|
||||
<view class="">
|
||||
{{$t(`发票类型`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.type==1?$t(`电子普通发票`):$t(`电子专用发票`)}}
|
||||
</view>
|
||||
<view>{{ $t(`发票类型`) }}</view>
|
||||
<view class="label-value">{{ invoiceData.type == 1 ? $t(`电子普通发票`) : $t(`电子专用发票`) }}</view>
|
||||
</view>
|
||||
<view class="label" v-if="invoiceData.duty_number">
|
||||
<view class="">
|
||||
{{$t(`企业税号`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.duty_number}}
|
||||
</view>
|
||||
<view class="label" v-if="invoiceData.duty_number || invoiceData.tax_number">
|
||||
<view>{{ $t(`企业税号`) }}</view>
|
||||
<view class="label-value">{{ invoiceData.duty_number || invoiceData.tax_number }}</view>
|
||||
</view>
|
||||
|
||||
<view class="body-head">{{$t(`联系信息`)}}</view>
|
||||
<view class="body-head">{{ $t(`联系信息`) }}</view>
|
||||
<view class="label">
|
||||
<view class="">
|
||||
{{$t(`真实姓名`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.name}}
|
||||
</view>
|
||||
<view>{{ $t(`真实姓名`) }}</view>
|
||||
<view class="label-value">{{ invoiceData.name || invoiceData.title || '--' }}</view>
|
||||
</view>
|
||||
<view class="label">
|
||||
<view class="">
|
||||
{{$t(`联系电话`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.drawer_phone}}
|
||||
</view>
|
||||
<view>{{ $t(`联系电话`) }}</view>
|
||||
<view class="label-value">{{ contactPhone || '--' }}</view>
|
||||
</view>
|
||||
<view class="label">
|
||||
<view class="">
|
||||
{{$t(`联系邮箱`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.email}}
|
||||
</view>
|
||||
<view>{{ $t(`联系邮箱`) }}</view>
|
||||
<view class="label-value">{{ invoiceData.email || '--' }}</view>
|
||||
</view>
|
||||
<view class="label">
|
||||
<view class="">
|
||||
{{$t(`发票备注`)}}
|
||||
</view>
|
||||
<view class="label-value">
|
||||
{{invoiceData.remark}}
|
||||
</view>
|
||||
<view>{{ $t(`发票备注`) }}</view>
|
||||
<view class="label-value">{{ invoiceData.remark || '--' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn" @click="close">
|
||||
{{$t(`确认`)}}
|
||||
</view>
|
||||
<view class="btn" @click="close">{{ $t(`确认`) }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useColorTheme } from '@/composables/useColorTheme';
|
||||
import { computed } from 'vue';
|
||||
import { useColorTheme } from '@/composables/useColorTheme';
|
||||
|
||||
const { colorStyle } = useColorTheme();
|
||||
const { colorStyle } = useColorTheme();
|
||||
|
||||
defineProps({
|
||||
aleartStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
invoiceData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
const props = defineProps({
|
||||
aleartStatus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
invoiceData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
function close() {
|
||||
emit('close')
|
||||
}
|
||||
/** 联系电话:个人 drawer_phone / 契约 phone / 企业 tell */
|
||||
const contactPhone = computed(() => {
|
||||
const d = props.invoiceData || {};
|
||||
return d.drawer_phone || d.phone || d.tell || '';
|
||||
});
|
||||
|
||||
function close() {
|
||||
emit('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.aleart {
|
||||
width: 80%;
|
||||
// height: 714rpx;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 9999;
|
||||
top: 45%;
|
||||
margin-top: -357rpx;
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
background-image: -webkit-gradient(linear, //表示渐变的为直线 另外一个值是radial
|
||||
50% 0, //直线型渐变的起点位置 后边有一个属性background-size规定背景的大小,30 X 15px 50% 0 都是乘以父元素的宽高。
|
||||
0 100%, //结束点的位置 和上类似
|
||||
from(transparent), //起点的颜色
|
||||
color-stop(.5, transparent), //中间某一个点必须达到这个颜色,表示变化过程 .5b表示这个渐变范围长度的总长的50%
|
||||
color-stop(.5, #999999), //同上
|
||||
to(#999999)), //结束段的颜色
|
||||
//一个背景块的分为两个15X 15 组成。
|
||||
.aleart {
|
||||
width: 80%;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 9999;
|
||||
background-color: #fff;
|
||||
padding: 0 30rpx 30rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: visible;
|
||||
/* 底部锯齿票根效果 */
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
50% 0,
|
||||
0 100%,
|
||||
from(transparent),
|
||||
color-stop(0.5, transparent),
|
||||
color-stop(0.5, #999999),
|
||||
to(#999999)
|
||||
),
|
||||
-webkit-gradient(
|
||||
linear,
|
||||
50% 0,
|
||||
100% 100%,
|
||||
from(transparent),
|
||||
color-stop(0.5, transparent),
|
||||
color-stop(0.5, #999999),
|
||||
to(#999999)
|
||||
);
|
||||
background-size: 20rpx 10rpx;
|
||||
background-repeat: repeat-x;
|
||||
background-position: 0 100%;
|
||||
|
||||
-webkit-gradient(linear, 50% 0, 100% 100%, from(transparent),
|
||||
color-stop(.5, transparent),
|
||||
color-stop(.5, #999999),
|
||||
to(#999999));
|
||||
background-size: 20rpx 10rpx;
|
||||
background-repeat: repeat-x;
|
||||
background-position: 0 100%;
|
||||
.icon-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: -48rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
.icon-top {
|
||||
margin-left: calc(50% - 40rpx);
|
||||
margin-top: -40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.icon-wrap {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
|
||||
.icon-fapiao2 {
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
font-size: 80rpx;
|
||||
color: #fff;
|
||||
background-color: var(--view-theme);
|
||||
padding: 20rpx;
|
||||
border: 4rpx solid #fff;
|
||||
margin-top: -40rpx;
|
||||
}
|
||||
.bill {
|
||||
width: 172rpx;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
color: var(--view-theme);
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding-bottom: 10rpx;
|
||||
border-bottom: 1px solid var(--view-op-ten);
|
||||
}
|
||||
|
||||
.aleart-body {
|
||||
border: 4rpx solid #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 60rpx 0;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.body-head {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
padding-bottom: 10rpx;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
margin: 10rpx 0;
|
||||
.iconfont {
|
||||
font-size: 48rpx;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15rpx;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
&.on {
|
||||
background-color: var(--view-theme, #e93323);
|
||||
}
|
||||
|
||||
.label-value {
|
||||
color: #666666;
|
||||
}
|
||||
&.off {
|
||||
background-color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 15rpx 0;
|
||||
color: #fff;
|
||||
background: var(--view-theme);
|
||||
border-radius: 20px;
|
||||
.bill {
|
||||
margin-top: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.aleart-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20rpx 0 10rpx;
|
||||
|
||||
.body-head {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
padding-bottom: 10rpx;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
margin: 16rpx 0 12rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 15rpx;
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
gap: 24rpx;
|
||||
|
||||
.label-value {
|
||||
color: #666666;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 15rpx 0;
|
||||
color: #fff;
|
||||
background: var(--view-theme, #e93323);
|
||||
border-radius: 20px;
|
||||
text-align: center;
|
||||
margin-top: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
<template>
|
||||
<view>
|
||||
<view :class="{ mask: invShow }" @touchmove.stop.prevent @click="invClose"></view>
|
||||
<view class="popup" :class="{ on: invShow }">
|
||||
<view class="popup-hd">{{$t(`抬头选择`)}}<text class="iconfont icon-guanbi" @click="invClose"></text></view>
|
||||
<scroll-view class="popup-bd" scroll-y="true">
|
||||
<!-- 独立层级容器:盖住订单底栏(z=20)与悬浮 home(z=999);背景滚动由 mask + 页面锁处理 -->
|
||||
<view class="invoice-picker-root" v-if="invShow">
|
||||
<view class="mask" @touchmove.stop.prevent @click="invClose"></view>
|
||||
<!-- stop 阻断冒泡到页面;勿 prevent,否则内部 scroll-view 无法滑动 -->
|
||||
<view class="popup on" @touchmove.stop>
|
||||
<view class="popup-hd" @touchmove.stop.prevent>{{$t(`抬头选择`)}}<text class="iconfont icon-guanbi" @click="invClose"></text></view>
|
||||
<!-- scroll-view 需可纵向滚动,勿在其上 prevent -->
|
||||
<scroll-view class="popup-bd" scroll-y="true" :show-scrollbar="false">
|
||||
<radio-group v-if="invList.length" name="inv" @change="invChange">
|
||||
<template v-for="(item, index) in invList" :key="item.id">
|
||||
<label v-if="item.type === 1 || item.type === 2 && isSpecial"
|
||||
@ -35,7 +38,7 @@
|
||||
hover-class="none"><text class="iconfont icon-bianji"></text>{{$t(`编辑`)}}
|
||||
</navigator>
|
||||
<navigator :render-link="false" v-else class="navigator"
|
||||
:url="`/pages/users/user_invoice_form/index?from=order_details&id=${item.id}&order_id=${orderId}`"
|
||||
:url="`/pages/users/user_invoice_form/index?from=order_details&id=${item.id}&order_id=${orderId}&${urlQuery}`"
|
||||
hover-class="none"><text class="iconfont icon-bianji"></text>{{$t(`编辑`)}}
|
||||
</navigator>
|
||||
</view>
|
||||
@ -48,7 +51,7 @@
|
||||
<view>{{$t(`您还没有添加发票信息哟`)}}~</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="popup-ft">
|
||||
<view class="popup-ft" @touchmove.stop.prevent>
|
||||
<navigator :render-link="false" v-if="!isOrder" class="navigator"
|
||||
:url="`/pages/users/user_invoice_form/index?from=order_confirm&${urlQuery}`" hover-class="none">
|
||||
<text class="iconfont icon-fapiao"></text>{{$t(`添加新的抬头`)}}
|
||||
@ -132,21 +135,46 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.popup {
|
||||
/* 高于订单底栏(20)与悬浮 home(999),避免底栏/悬浮钮压在弹层上 */
|
||||
.invoice-picker-root {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10050;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.popup {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
max-height: 85vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top-left-radius: 16rpx;
|
||||
border-top-right-radius: 16rpx;
|
||||
background-color: #F5F5F5;
|
||||
transform: translateY(100%);
|
||||
transition: 0.3s;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.popup.on {
|
||||
transform: translateY(0);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.popup-hd {
|
||||
@ -168,7 +196,9 @@
|
||||
}
|
||||
|
||||
.popup-bd {
|
||||
flex: 1;
|
||||
height: 600rpx;
|
||||
max-height: 50vh;
|
||||
padding-right: 30rpx;
|
||||
padding-left: 30rpx;
|
||||
box-sizing: border-box;
|
||||
@ -274,7 +304,9 @@
|
||||
}
|
||||
|
||||
.popup-ft {
|
||||
flex-shrink: 0;
|
||||
padding: 14rpx 30rpx 44rpx;
|
||||
background-color: #F5F5F5;
|
||||
|
||||
.navigator {
|
||||
height: 86rpx;
|
||||
|
||||
@ -432,8 +432,8 @@
|
||||
<view class="conter">-{{ $t(`¥`) }}{{ parseFloat(orderInfo.memberPrice).toFixed(2) }}</view>
|
||||
</view>
|
||||
<view v-if="orderInfo.gift_price > 0" class="item acea-row row-between">
|
||||
<view>{{ $t(`礼品附加费用`) }}:</view>
|
||||
<view class="conter">-{{ $t(`¥`) }}{{ parseFloat(orderInfo.gift_price).toFixed(2) }}</view>
|
||||
<view>{{ $t(`礼品附加费`) }}:</view>
|
||||
<view class="conter">+{{ $t(`¥`) }}{{ parseFloat(orderInfo.gift_price).toFixed(2) }}</view>
|
||||
</view>
|
||||
<view class="item acea-row row-between" v-if="orderInfo.coupon_price > 0">
|
||||
<view>{{ $t(`优惠券抵扣`) }}:</view>
|
||||
@ -777,11 +777,18 @@ const refundedPriceText = computed(() => {
|
||||
});
|
||||
|
||||
// ---------- 生命周期 ----------
|
||||
// 已重定向到中台详情时,禁止 onShow 再调废弃的 order/detail
|
||||
const redirectedToCenter = ref(false);
|
||||
|
||||
onLoad((options) => {
|
||||
// 订单数据结构已迁移至中台 OrderCenter,旧版扁平接口(order/detail)已废弃
|
||||
// 延用原页面路径作为入口,重定向到新版引擎页面,避免修改 20+ 个跳转入口
|
||||
if (options.order_id) {
|
||||
uni.redirectTo({ url: '/pages/center/order/detail/index?order_no=' + options.order_id });
|
||||
redirectedToCenter.value = true;
|
||||
const q = [`order_no=${options.order_id}`];
|
||||
if (options.invoice_id) q.push(`invoice_id=${options.invoice_id}`);
|
||||
if (options.header_type) q.push(`header_type=${options.header_type}`);
|
||||
uni.redirectTo({ url: '/pages/center/order/detail/index?' + q.join('&') });
|
||||
return;
|
||||
}
|
||||
if (options.order_id) {
|
||||
@ -794,6 +801,7 @@ onLoad((options) => {
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
if (redirectedToCenter.value) return;
|
||||
if (isLogin.value) {
|
||||
getOrderInfo();
|
||||
getUserInfo();
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
</label>
|
||||
</radio-group>
|
||||
</view>
|
||||
<view class="acea-row row-middle" v-if="basicConfigData.special_invoice_status === '1' && header_type === '2'">
|
||||
<!-- 仅企业 + 后台开启专用发票时可选专票(个人只能普票) -->
|
||||
<view class="acea-row row-middle" v-if="specialInvoice && header_type === '2'">
|
||||
<view>{{$t(`发票类型`)}}</view>
|
||||
<text class="w-fill" @click="callType">
|
||||
<text>{{ type === '2' ? $t(`增值税电子专用发票`) : $t(`增值税电子普通发票`) }} </text>
|
||||
@ -131,9 +132,19 @@ const popupType = ref(false);
|
||||
const typeName = ref('');
|
||||
const urlQuery = ref('');
|
||||
const from = ref('');
|
||||
const specialInvoice = ref(true);
|
||||
/** 是否允许企业选择专用发票(由 BASIC_CONFIG / 路由参数共同决定) */
|
||||
const specialInvoice = ref(false);
|
||||
const order_id = ref('');
|
||||
const basicConfigData = uni.getStorageSync('BASIC_CONFIG') || '';
|
||||
const basicConfigData = uni.getStorageSync('BASIC_CONFIG') || {};
|
||||
|
||||
/**
|
||||
* 配置开关真值判断(兼容 1/'1'/true/'true')
|
||||
* @param {*} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isConfigEnabled(value) {
|
||||
return value === true || value === 1 || value === '1' || value === 'true';
|
||||
}
|
||||
|
||||
// ---------- computed ----------
|
||||
const backUrl = computed(() => {
|
||||
@ -152,6 +163,8 @@ onHide(() => {
|
||||
from.value = ''
|
||||
});
|
||||
onLoad((options) => {
|
||||
// 先读基础配置;路由参数可覆盖(下单/详情页会显式传入)
|
||||
specialInvoice.value = isConfigEnabled(basicConfigData.special_invoice_status);
|
||||
|
||||
if (options.id) uni.setNavigationBarTitle({
|
||||
title: i18n.t(`编辑发票`)
|
||||
@ -177,9 +190,7 @@ onLoad((options) => {
|
||||
getInvoiceDetail();
|
||||
break;
|
||||
case 'specialInvoice':
|
||||
if (options[key] === 'false') {
|
||||
specialInvoice.value = false;
|
||||
}
|
||||
specialInvoice.value = isConfigEnabled(options[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -397,17 +408,14 @@ function formSubmit(e) {
|
||||
})
|
||||
}
|
||||
break;
|
||||
case 'order_details':
|
||||
if (id.value) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/goods/order_details/index?order_id=${order_id.value}&invoice_id=${id.value}&header_type=${header_type.value}`
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/goods/order_details/index?order_id=${order_id.value}&invoice_id=${res.data.id}&header_type=${header_type.value}`
|
||||
})
|
||||
}
|
||||
case 'order_details': {
|
||||
// 订单详情已迁中台引擎,勿再跳旧 order_details(会请求已废弃的 order/detail)
|
||||
const invoiceId = id.value || (res.data && res.data.id) || '';
|
||||
uni.redirectTo({
|
||||
url: `/pages/center/order/detail/index?order_no=${order_id.value}&invoice_id=${invoiceId}&header_type=${header_type.value}`
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
uni.navigateTo({
|
||||
url: '/pages/users/user_invoice_list/index?from=invoice_form'
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
<view v-show="nav === 1" class="record-wrapper">
|
||||
<view v-for="item in orderList" :key="item.id" class="item">
|
||||
<view class="item-hd acea-row">
|
||||
<image class="image" :src="item.order.cartInfo[0].productInfo.image"></image>
|
||||
<view class="text">{{ item.order.cartInfo[0].productInfo.store_name + item.order.cartInfo[0].productInfo.attrInfo.suk || '' }}</view>
|
||||
<image class="image" :src="invoiceGoodsImage(item)"></image>
|
||||
<view class="text">{{ invoiceGoodsName(item) }}</view>
|
||||
</view>
|
||||
<view class="item-bd acea-row row-between-wrapper">
|
||||
<view>
|
||||
@ -17,14 +17,18 @@
|
||||
</view>
|
||||
<view class="money">
|
||||
{{ $t(`¥`) }}
|
||||
<text class="num">{{ item.order.pay_price }}</text>
|
||||
<text class="num">{{ (item.order && item.order.pay_price) || '0.00' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-ft acea-row row-between-wrapper">
|
||||
<view>{{ item.is_invoice ? $t(`已开票`) : $t(`未开票`) }}</view>
|
||||
<view class="acea-row row-center-wrapper">
|
||||
<view class="link mr20" @click="getInvoiceLink(item.id)" v-if="item.is_invoice == 1 && item.unique_num != '' && item.red_invoice_num == ''">{{ $t(`复制`) }}</view>
|
||||
<navigator :render-link="false" class="link" :url="`/pages/users/user_invoice_order/index?order_id=${item.order.order_id}`">{{ $t(`查看详情`) }}</navigator>
|
||||
<navigator
|
||||
:render-link="false"
|
||||
class="link"
|
||||
:url="`/pages/users/user_invoice_order/index?order_id=${(item.order && (item.order.order_id || item.order.order_no)) || ''}`"
|
||||
>{{ $t(`查看详情`) }}</navigator>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -68,7 +72,7 @@
|
||||
<image :src="imgHost + '/statics/images/noInvoice.png'"></image>
|
||||
<view>{{ $t(`没有发票信息哟~`) }}</view>
|
||||
</view>
|
||||
<navigator :render-link="false" class="add-link" :url="`/pages/users/user_invoice_form/index?specialInvoice=${specialInvoice}`">
|
||||
<navigator :render-link="false" class="add-link" :url="`/pages/users/user_invoice_form/index?specialInvoice=${specialInvoice ? 1 : 0}`">
|
||||
<text class="iconfont icon-fapiao"></text>
|
||||
{{ $t(`添加新发票`) }}
|
||||
</navigator>
|
||||
@ -105,7 +109,7 @@ const page = ref(1);
|
||||
const limit = ref(30);
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
const specialInvoice = ref(true);
|
||||
const specialInvoice = ref(false);
|
||||
|
||||
// ---------- watch ----------
|
||||
watch(nav, (value) => {
|
||||
@ -131,6 +135,15 @@ onLoad((option) => {
|
||||
});
|
||||
|
||||
// ---------- methods ----------
|
||||
function invoiceGoodsImage(item) {
|
||||
const product = item?.order?.cartInfo?.[0]?.productInfo || {};
|
||||
return product?.attrInfo?.image || product?.image || '';
|
||||
}
|
||||
function invoiceGoodsName(item) {
|
||||
const product = item?.order?.cartInfo?.[0]?.productInfo || {};
|
||||
const suk = product?.attrInfo?.suk || '';
|
||||
return `${product?.store_name || ''}${suk}`;
|
||||
}
|
||||
function getInvoiceLink(id) {
|
||||
uni.showLoading({
|
||||
title: i18n.t(`复制中`)
|
||||
|
||||
@ -56,22 +56,32 @@
|
||||
<view class="conter">{{orderInfo.invoice.remark}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<orderGoods :evaluate='evaluate' :orderId="order_id" :cartInfo="cartInfo" :jump="true" :paid="orderInfo.paid" :oid="orderInfo.id" :isShow="false" :statusType="status.type"></orderGoods>
|
||||
<orderGoods
|
||||
v-if="cartInfo && cartInfo.length"
|
||||
:evaluate="evaluate"
|
||||
:orderId="order_id"
|
||||
:cartInfo="cartInfo"
|
||||
:jump="true"
|
||||
:paid="orderInfo.paid"
|
||||
:oid="orderInfo.id"
|
||||
:isShow="false"
|
||||
:statusType="status.type"
|
||||
></orderGoods>
|
||||
<view class='wrapper'>
|
||||
<view class='item acea-row row-between'>
|
||||
<view>{{$t(`订单编号`)}}:</view>
|
||||
<view class='conter acea-row row-middle row-right'>{{orderInfo.order_id}}
|
||||
<view class='conter acea-row row-middle row-right'>{{orderInfo.order_id || orderInfo.order_no}}
|
||||
<!-- #ifndef H5 -->
|
||||
<text class='copy' @tap='copy'>{{$t(`复制`)}}</text>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<text class='copy copy-data' :data-clipboard-text="orderInfo.order_id">{{$t(`复制`)}}</text>
|
||||
<text class='copy copy-data' :data-clipboard-text="orderInfo.order_id || orderInfo.order_no">{{$t(`复制`)}}</text>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
<view class='item acea-row row-between'>
|
||||
<view>{{$t(`下单时间`)}}:</view>
|
||||
<view class='conter'>{{(orderInfo.add_time_y || '') +' '+(orderInfo.add_time_h || 0)}}</view>
|
||||
<view class='conter'>{{(orderInfo.add_time_y || '') +' '+(orderInfo.add_time_h || '')}}</view>
|
||||
</view>
|
||||
<view class='item acea-row row-between'>
|
||||
<view>{{$t(`支付状态`)}}:</view>
|
||||
@ -80,7 +90,7 @@
|
||||
</view>
|
||||
<view class='item acea-row row-between'>
|
||||
<view>{{$t(`支付方式`)}}:</view>
|
||||
<view class='conter'>{{orderInfo._status._payType}}</view>
|
||||
<view class='conter'>{{ (orderInfo._status && orderInfo._status._payType) || orderInfo.pay_type_name || orderInfo.pay_type || '' }}</view>
|
||||
</view>
|
||||
<view class='item acea-row row-between' v-if="orderInfo.mark">
|
||||
<view>{{$t(`买家留言`)}}:</view>
|
||||
@ -688,13 +698,14 @@ function getOrderInfo() {
|
||||
title: i18n.t(`正在加载中`)
|
||||
});
|
||||
orderInvoiceDetail(order_id.value).then(res => {
|
||||
let _type = res.data._status._type;
|
||||
const data = res.data || {};
|
||||
const statusInfo = data._status || {};
|
||||
uni.hideLoading();
|
||||
orderInfo.value = res.data;
|
||||
cartInfo.value = res.data.cartInfo;
|
||||
evaluate.value = _type == 3 ? 3 : 0;
|
||||
system_store.value = res.data.system_store;
|
||||
if (orderInfo.value.refund_status != 0) {
|
||||
orderInfo.value = Object.assign({ system_store: {}, _status: {} }, data);
|
||||
cartInfo.value = Array.isArray(data.cartInfo) ? data.cartInfo : [];
|
||||
evaluate.value = statusInfo._type == 3 ? 3 : 0;
|
||||
system_store.value = data.system_store || {};
|
||||
if (Number(orderInfo.value.refund_status) !== 0) {
|
||||
isGoodsReturn.value = true;
|
||||
}
|
||||
getOrderStatus();
|
||||
@ -712,7 +723,7 @@ function getOrderInfo() {
|
||||
// #ifndef H5
|
||||
function copy() {
|
||||
uni.setClipboardData({
|
||||
data: orderInfo.value.order_id
|
||||
data: orderInfo.value.order_id || orderInfo.value.order_no || ''
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user