mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
feat(送礼订单): 完善礼物信息、领取人操作与订单金额展示
- 订单详情新增礼物信息卡:送礼人查看领取人,领取人查看赠送人,含礼物留言与领取时间 - 领取人按订单流程可见确认收货、查看物流、去评价,评价接口权限放开至领取人 - 商品信息卡增加标题,商品金额与商品总额按规格原价(attr_info.price)展示 - 订单列表商品图放大、规格文字与图片底部对齐,修复次级按钮双层边框
This commit is contained in:
parent
000adafb42
commit
d5e2f6066a
@ -2841,11 +2841,33 @@ class CenterOrderController
|
||||
return app('json')->success($cartProduct);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否可对订单执行用户端操作:下单人本人或送礼订单领取人
|
||||
* @param array $order 订单数据(含 uid / gift_uid / extra_info)
|
||||
* @param int $uid 当前用户 ID
|
||||
* @return bool
|
||||
*/
|
||||
protected function canOperateOrder(array $order, int $uid): bool
|
||||
{
|
||||
if ((int)($order['uid'] ?? 0) === $uid) {
|
||||
return true;
|
||||
}
|
||||
$giftUid = (int)($order['gift_uid'] ?? 0);
|
||||
if ($giftUid === 0) {
|
||||
$extra = $order['extra_info'] ?? [];
|
||||
if (is_string($extra)) {
|
||||
$extra = json_decode($extra, true) ?: [];
|
||||
}
|
||||
$giftUid = (int)($extra['gift']['gift_uid'] ?? ($extra['gift_uid'] ?? 0));
|
||||
}
|
||||
return $giftUid > 0 && $giftUid === $uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 SKU 唯一码从中台订单快照定位商品行
|
||||
* 查询订单中台的 extra_info.items,匹配 attr_info.unique(与订单评价 unique 同源)
|
||||
* @param string $unique SKU 唯一码
|
||||
* @param int|null $uid 限定用户(评价场景必传防越权),null 不限定
|
||||
* @param int|null $uid 限定用户(评价场景必传防越权,下单人或送礼领取人),null 不限定
|
||||
* @return array|null 商品行(附 _order_no/_center_id),未找到返回 null
|
||||
*/
|
||||
protected function findOrderItemBySkuUnique(string $unique, ?int $uid = null): ?array
|
||||
@ -2853,12 +2875,13 @@ class CenterOrderController
|
||||
if ($unique === '') {
|
||||
return null;
|
||||
}
|
||||
// 不直接按 uid 过滤:送礼订单的领取人也需要能评价(领取人 uid 不等于订单 uid)
|
||||
$where = [['extra_info', 'like', '%' . $unique . '%']];
|
||||
if ($uid !== null) {
|
||||
$where[] = ['uid', '=', $uid];
|
||||
}
|
||||
$orders = app()->make(OrderCenterDao::class)->selectList($where, 'id, order_no, extra_info', 0, 0);
|
||||
$orders = app()->make(OrderCenterDao::class)->selectList($where, 'id, uid, gift_uid, order_no, extra_info', 0, 0);
|
||||
foreach ($orders as $order) {
|
||||
if ($uid !== null && !$this->canOperateOrder($order, $uid)) {
|
||||
continue;
|
||||
}
|
||||
$extra = is_string($order['extra_info']) ? json_decode($order['extra_info'], true) : ($order['extra_info'] ?? []);
|
||||
foreach ($extra['items'] ?? [] as $item) {
|
||||
if (($item['attr_info']['unique'] ?? '') === $unique) {
|
||||
@ -2876,7 +2899,7 @@ class CenterOrderController
|
||||
* 评价必须落到用户指定的那笔订单:同商品重复购买时各订单 unique 相同,仅凭 unique 反查会命中旧订单
|
||||
* @param string $orderNo 中台订单号
|
||||
* @param string $unique SKU 唯一码
|
||||
* @param int|null $uid 限定归属用户
|
||||
* @param int|null $uid 限定归属用户(下单人或送礼领取人)
|
||||
* @return array|null
|
||||
*/
|
||||
protected function findOrderItemByOrderNo(string $orderNo, string $unique, ?int $uid = null): ?array
|
||||
@ -2889,7 +2912,7 @@ class CenterOrderController
|
||||
return null;
|
||||
}
|
||||
$order = is_object($order) ? $order->toArray() : (array)$order;
|
||||
if ($uid !== null && (int)($order['uid'] ?? 0) !== $uid) {
|
||||
if ($uid !== null && !$this->canOperateOrder($order, $uid)) {
|
||||
return null;
|
||||
}
|
||||
$extra = is_string($order['extra_info'] ?? null) ? json_decode($order['extra_info'], true) : ($order['extra_info'] ?? []);
|
||||
@ -2951,8 +2974,9 @@ class CenterOrderController
|
||||
$orderInfo = $orderInfo->toArray();
|
||||
|
||||
// ==================== 6. 权限验证 ====================
|
||||
if ($uid != $orderInfo['uid']) {
|
||||
return app('json')->fail('不是您自己的订单,无法评价');
|
||||
// 下单人本人或送礼订单领取人均可评价(领取人评价时 uid 为领取人)
|
||||
if (!$this->canOperateOrder($orderInfo, $uid)) {
|
||||
return app('json')->fail('无权评价该订单');
|
||||
}
|
||||
|
||||
// ==================== 7. 调用评价服务(业务逻辑和事件触发都在Service中)===================
|
||||
|
||||
@ -449,10 +449,21 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
}
|
||||
$detail = $detail->toArray();
|
||||
$detail['extra_info'] = $this->parseExtraInfo($detail['extra_info']);
|
||||
// 下单用户昵称/绑定电话(eb_user),供详情"用户信息"区展示
|
||||
$detailUser = User::where('uid', (int)$detail['uid'])->field('nickname,phone')->find();
|
||||
// 下单用户昵称/绑定电话/头像(eb_user),供详情"用户信息"与送礼信息区展示
|
||||
$detailUser = User::where('uid', (int)$detail['uid'])->field('nickname,phone,avatar')->find();
|
||||
$detail['nickname'] = $detailUser['nickname'] ?? '';
|
||||
$detail['user_phone'] = $detailUser['phone'] ?? '';
|
||||
$detail['user_avatar'] = set_file_url((string)($detailUser['avatar'] ?? ''));
|
||||
// 送礼订单:领取人信息(gift_uid 非 0 时),供送礼人查看"谁领取了礼物"
|
||||
$giftUid = (int)($detail['gift_uid'] ?? $detail['extra_info']['gift']['gift_uid'] ?? ($detail['extra_info']['gift_uid'] ?? 0));
|
||||
if ($giftUid > 0) {
|
||||
$receiver = User::where('uid', $giftUid)->field('nickname,avatar')->find();
|
||||
$detail['gift_receiver'] = [
|
||||
'uid' => $giftUid,
|
||||
'nickname' => (string)($receiver['nickname'] ?? ''),
|
||||
'avatar' => set_file_url((string)($receiver['avatar'] ?? '')),
|
||||
];
|
||||
}
|
||||
// 解析 JSON 字段
|
||||
if (!empty($detail['custom_form'])) {
|
||||
$detail['custom_form'] = $this->parseExtraInfo($detail['custom_form']);
|
||||
@ -621,20 +632,51 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
return (float)($detail['gift_price'] ?? 0) > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算订单商品原价总额(规格价 × 数量)
|
||||
* total_price 为优惠后金额,价格明细中的“商品总额”需使用原价基数,才能与各项优惠对平
|
||||
* @param array $detail 订单详情数据
|
||||
* @return string 金额字符串(两位小数)
|
||||
*/
|
||||
protected function resolveOriginalGoodsTotal(array $detail): string
|
||||
{
|
||||
$total = '0.00';
|
||||
foreach (($detail['order_items'] ?? []) as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$price = is_array($item['attr_info'] ?? null) ? ($item['attr_info']['price'] ?? '') : '';
|
||||
// 订单快照 item.price 可能是商品级价格,规格金额以 attr_info.price 为准
|
||||
if ($price === '' || $price === null) {
|
||||
$price = $item['price'] ?? '';
|
||||
}
|
||||
if ($price === '' || !is_numeric($price)) {
|
||||
continue;
|
||||
}
|
||||
$num = max(1, (int)($item['cart_num'] ?? 1));
|
||||
$total = bcadd($total, bcmul((string)$price, (string)$num, 2), 2);
|
||||
}
|
||||
if (bccomp($total, '0', 2) <= 0) {
|
||||
$total = number_format((float)($detail['total_price'] ?? 0), 2, '.', '');
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装价格明细数组
|
||||
* 将金额实体列转为 {label, value, type} 数组格式
|
||||
* 金额字段为中台实体列(价格链规范固定);插件可通过 OrderPricingExtend 事件追加金额项
|
||||
* @param array $detail 订单详情数据
|
||||
* @return array 返回 {items: [...], pay_price: string}
|
||||
* @return array 返回 {items: [...], pay_price: string, original_total: string}
|
||||
*/
|
||||
protected function buildPricingItems(array $detail): array
|
||||
{
|
||||
$items = [];
|
||||
// 商品总额(始终存在)
|
||||
// 商品总额按规格原价 × 数量汇总(total_price 是优惠后金额,不能作为明细基数)
|
||||
$originalTotal = $this->resolveOriginalGoodsTotal($detail);
|
||||
$items[] = [
|
||||
'label' => '商品总额',
|
||||
'value' => number_format((float)($detail['total_price'] ?? 0), 2, '.', ''),
|
||||
'value' => $originalTotal,
|
||||
'type' => 'subtotal',
|
||||
];
|
||||
|
||||
@ -693,8 +735,9 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
event('OrderPricingExtend', $pricingEvent);
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'pay_price' => number_format((float)($detail['pay_price'] ?? 0), 2, '.', ''),
|
||||
'items' => $items,
|
||||
'pay_price' => number_format((float)($detail['pay_price'] ?? 0), 2, '.', ''),
|
||||
'original_total' => $originalTotal,
|
||||
];
|
||||
}
|
||||
|
||||
@ -824,6 +867,16 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
];
|
||||
}
|
||||
|
||||
// 45. 礼物信息(送礼订单):赠送人/领取人展示由查看者身份决定,在 applyGiftViewerPolicy 中填充
|
||||
$gift = $detail['gift'] ?? null;
|
||||
$sections[] = [
|
||||
'key' => 'gift',
|
||||
'component' => 'gift_info',
|
||||
'props' => ['gift' => $gift ?? []],
|
||||
'visible' => !empty($gift),
|
||||
'order' => 45,
|
||||
];
|
||||
|
||||
// 50. 商品卡片(始终存在)
|
||||
// product_id 供前端引擎跳转商品详情(goProductDetail)使用
|
||||
$product = $detail['product'] ?? [];
|
||||
@ -1083,7 +1136,8 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
$pricing = [
|
||||
'items' => $pricingData['items'],
|
||||
'pay_price' => $pricingData['pay_price'],
|
||||
'total_price' => $detail['total_price'] ?? '0.00',
|
||||
// 展示口径:商品总额按规格原价 × 数量汇总(原始 total_price 为优惠后金额,保留在详情顶层供业务计算)
|
||||
'total_price' => $pricingData['original_total'] ?? ($detail['total_price'] ?? '0.00'),
|
||||
'pay_postage' => $detail['pay_postage'] ?? '0.00',
|
||||
'refund_price' => $detail['refund_price'] ?? '0.00',
|
||||
'coupon_price' => $detail['coupon_price'] ?? '0.00',
|
||||
@ -1232,6 +1286,25 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
// 原始 extra_info(供深度定制用)
|
||||
$extensions['extra_info'] = $detail['extra_info'] ?? [];
|
||||
|
||||
// 送礼订单信息:赠送人 / 领取人 / 留言 / 领取时间,供详情礼物卡片按查看者展示
|
||||
$gift = null;
|
||||
if ($this->resolveIsGiftFlag($detail) === 1) {
|
||||
$extraInfo = $detail['extra_info'] ?? [];
|
||||
$giftUid = (int)($detail['gift_uid'] ?? $extraInfo['gift']['gift_uid'] ?? ($extraInfo['gift_uid'] ?? 0));
|
||||
$gift = [
|
||||
'is_gift' => 1,
|
||||
'gift_uid' => $giftUid,
|
||||
'gift_mark' => (string)($extraInfo['gift_mark'] ?? ''),
|
||||
'gift_time' => (int)($extraInfo['gift']['gift_time'] ?? 0),
|
||||
'sender' => [
|
||||
'uid' => (int)($detail['uid'] ?? 0),
|
||||
'nickname' => (string)($detail['nickname'] ?? ''),
|
||||
'avatar' => (string)($detail['user_avatar'] ?? ''),
|
||||
],
|
||||
'receiver' => $detail['gift_receiver'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// 返回分组结构:仅输出有值的分组,空分组(null/空数组)不下发,避免响应占位
|
||||
$result = [
|
||||
'status' => $status,
|
||||
@ -1240,6 +1313,9 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
|
||||
'order_info' => $orderInfo,
|
||||
'extensions' => $extensions,
|
||||
];
|
||||
if ($gift !== null) {
|
||||
$result['gift'] = $gift;
|
||||
}
|
||||
if (!empty($buttons)) {
|
||||
$result['buttons'] = $buttons;
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
fn($section) => !in_array($section['key'] ?? '', ['pricing', 'payment', 'invoice', 'coupon_gift', 'points_reward'], true)
|
||||
));
|
||||
$result['page_config']['extensions']['viewer_role'] = 'gift_receiver';
|
||||
$visibleActions = ['confirmReceive', 'viewLogistics'];
|
||||
$visibleActions = ['confirmReceive', 'viewLogistics', 'goComment'];
|
||||
$hiddenActions = [];
|
||||
} else {
|
||||
// 购买者:不展示收货信息与物流轨迹,隐藏确认收货/申请退款/查看物流
|
||||
@ -105,9 +105,22 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
$hiddenActions = ['confirmReceive', 'applyRefund', 'viewLogistics'];
|
||||
}
|
||||
|
||||
// 按查看者身份过滤底部操作按钮
|
||||
// 按查看者身份处理礼物信息与底部操作按钮
|
||||
foreach ($sections as &$section) {
|
||||
if (($section['key'] ?? '') !== 'buttons') {
|
||||
$sectionKey = (string)($section['key'] ?? '');
|
||||
// 礼物信息:送礼人展示领取人,领取人展示赠送人
|
||||
if ($sectionKey === 'gift') {
|
||||
$gift = $section['props']['gift'] ?? [];
|
||||
if ($isReceiver) {
|
||||
$section['props']['role'] = 'receiver';
|
||||
$section['props']['display'] = $gift['sender'] ?? null;
|
||||
} else {
|
||||
$section['props']['role'] = 'sender';
|
||||
$section['props']['display'] = $gift['receiver'] ?? null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($sectionKey !== 'buttons') {
|
||||
continue;
|
||||
}
|
||||
$section['props']['buttons'] = array_values(array_filter(
|
||||
@ -216,7 +229,7 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
if ($item['is_gift_receiver']) {
|
||||
$item['order_buttons'] = array_values(array_filter(
|
||||
$item['order_buttons'] ?? [],
|
||||
fn($button) => ($button['action'] ?? '') === 'viewLogistics'
|
||||
fn($button) => in_array($button['action'] ?? '', ['confirmReceive', 'viewLogistics', 'goComment'], true)
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -348,7 +361,8 @@ class OrderCenterUserServices extends OrderCenterServices
|
||||
}
|
||||
|
||||
// 订单大类文案:从 OrderTypeRegistry 动态获取,各适配器注册即生效
|
||||
if ($orderCategory) {
|
||||
// 普通商品订单已有「实物商品」角标,不再重复展示“商品订单”占位文案
|
||||
if ($orderCategory && $orderCategory !== 'goods') {
|
||||
$categoryMap = OrderTypeRegistry::getCategoryMap();
|
||||
if (isset($categoryMap[$orderCategory])) {
|
||||
return $categoryMap[$orderCategory];
|
||||
|
||||
@ -192,6 +192,7 @@ class NormalOrderAdapter extends BaseOrderTypeAdapter
|
||||
])
|
||||
->userButtons([
|
||||
Button::primary('去评价', 'goComment'),
|
||||
Button::make('查看物流', 'viewLogistics'),
|
||||
Button::make('申请售后', 'applyAfterSale'),
|
||||
])
|
||||
->filter([['pt_normal_order.status', '=', self::EXT_STATUS_PENDING_REVIEW]]),
|
||||
@ -202,6 +203,7 @@ class NormalOrderAdapter extends BaseOrderTypeAdapter
|
||||
->label('已完成')
|
||||
->adminButtons([])
|
||||
->userButtons([
|
||||
Button::make('查看物流', 'viewLogistics'),
|
||||
Button::make('申请售后', 'applyAfterSale'),
|
||||
Button::primary('再次购买', 'repurchase'),
|
||||
])
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
<!--
|
||||
礼物信息卡片(送礼订单专用)
|
||||
送礼人:展示领取人(未领取时展示等待提示)
|
||||
领取人:展示赠送人
|
||||
数据来源:section.props.gift + section.props.role / display
|
||||
展示对象由后端 applyGiftViewerPolicy 按查看者身份下发,前端不判断用户关系
|
||||
-->
|
||||
<template>
|
||||
<view v-if="gift" class="info-card gift-card">
|
||||
<view class="card-header acea-row row-between-wrapper">
|
||||
<view class="card-title">{{ $t('礼物信息') }}</view>
|
||||
<view v-if="role === 'sender' && hasDisplay && gift.gift_time" class="gift-time">
|
||||
{{ $t('领取时间') }}:{{ formatTime(gift.gift_time) }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="gift-user acea-row row-middle">
|
||||
<image v-if="display.avatar" class="avatar" :src="display.avatar" mode="aspectFill" />
|
||||
<view v-else class="avatar avatar-empty">
|
||||
<text class="iconfont icon-ic_gift1"></text>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<view v-if="role === 'receiver'" class="gift-text">
|
||||
<text class="nickname">{{ displayName }}</text>
|
||||
<text class="gift-suffix">{{ $t('赠您一份礼物,请查收!') }}</text>
|
||||
</view>
|
||||
<view v-else-if="hasDisplay" class="gift-text">
|
||||
<text class="nickname">{{ displayName }}</text>
|
||||
<text class="gift-suffix">{{ $t('已领取您的礼物') }}</text>
|
||||
</view>
|
||||
<view v-else class="gift-text">
|
||||
<text class="gift-suffix">{{ $t('等待好友领取') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="gift.gift_mark" class="gift-mark">
|
||||
<text class="gift-mark-label">{{ $t('礼物留言') }}:</text>
|
||||
<text class="gift-mark-text">{{ gift.gift_mark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 礼物信息卡片
|
||||
* 送礼订单详情中展示赠送人 / 领取人信息与礼物留言
|
||||
*/
|
||||
export default {
|
||||
name: 'GiftInfoCard',
|
||||
// DIY 通用化契约:组件唯一标识
|
||||
componentKey: 'gift_info',
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
/** section 对象,含 props.gift / props.role / props.display */
|
||||
section: { type: Object, required: true },
|
||||
});
|
||||
|
||||
/** 后端直填 props */
|
||||
const p = computed(() => (props.section && props.section.props) || {});
|
||||
/** 礼物数据 */
|
||||
const gift = computed(() => p.value.gift || null);
|
||||
/** 查看者角色:sender=送礼人,receiver=领取人 */
|
||||
const role = computed(() => p.value.role || 'sender');
|
||||
/** 需要展示的用户(送礼人看领取人,领取人看赠送人) */
|
||||
const display = computed(() => p.value.display || {});
|
||||
/** 是否存在可展示对象:送礼人未领取时为 false */
|
||||
const hasDisplay = computed(() => !!p.value.display);
|
||||
/** 展示用户名,缺失时兜底为“好友” */
|
||||
const displayName = computed(() => display.value.nickname || '好友');
|
||||
|
||||
/**
|
||||
* 时间戳格式化为 YYYY-MM-DD HH:mm
|
||||
* @param {number} ts 秒级时间戳
|
||||
* @return {string} 格式化后的时间
|
||||
*/
|
||||
function formatTime(ts) {
|
||||
if (!ts) return '';
|
||||
const date = new Date(ts * 1000);
|
||||
const pad = function (n) {
|
||||
return n < 10 ? '0' + n : '' + n;
|
||||
};
|
||||
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
|
||||
+ ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.info-card { margin: 20rpx; background: #fff; border-radius: 16rpx; padding: 30rpx; }
|
||||
.card-header { margin-bottom: 20rpx; }
|
||||
.card-title { font-size: 30rpx; font-weight: bold; color: #333; }
|
||||
.gift-user { padding: 6rpx 0; }
|
||||
.avatar { width: 72rpx; height: 72rpx; border-radius: 50%; background: #f5f5f5; flex-shrink: 0; }
|
||||
.avatar-empty { display: flex; align-items: center; justify-content: center; }
|
||||
.avatar-empty .iconfont { font-size: 36rpx; color: #e93323; }
|
||||
.user-info { flex: 1; margin-left: 20rpx; min-width: 0; }
|
||||
.gift-text { font-size: 28rpx; color: #333; display: flex; align-items: center; flex-wrap: wrap; }
|
||||
.nickname { font-weight: 600; color: #333; margin-right: 8rpx; }
|
||||
.gift-suffix { color: #666; }
|
||||
.gift-mark {
|
||||
padding: 18rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #faf7f5;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.gift-mark-label { color: #999; }
|
||||
.gift-mark-text { color: #666; }
|
||||
.gift-time { flex-shrink: 0; margin-left: 20rpx; font-size: 24rpx; color: #999; }
|
||||
</style>
|
||||
@ -6,23 +6,26 @@
|
||||
-->
|
||||
<template>
|
||||
<view class="product-card">
|
||||
<!-- 多商品模式:遍历 items 渲染每个商品 -->
|
||||
<view
|
||||
v-for="(item, idx) in productList"
|
||||
:key="idx"
|
||||
class="product-row"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<image :src="item.image" mode="aspectFill" class="product-image" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ item.store_name || '' }}</text>
|
||||
<text v-if="getSkuText(item)" class="sku-text">{{ getSkuText(item) }}</text>
|
||||
<view class="product-meta">
|
||||
<text v-if="!isGiftReceiver" class="product-price">¥{{ item.true_price || item.price || '0.00' }}</text>
|
||||
<text class="product-qty">x{{ item.cart_num || 1 }}</text>
|
||||
<view class="card-title">{{ $t('商品信息') }}</view>
|
||||
<view class="product-list">
|
||||
<!-- 多商品模式:遍历 items 渲染每个商品 -->
|
||||
<view
|
||||
v-for="(item, idx) in productList"
|
||||
:key="idx"
|
||||
class="product-row"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<image :src="item.image" mode="aspectFill" class="product-image" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ item.store_name || '' }}</text>
|
||||
<text v-if="getSkuText(item)" class="sku-text">{{ getSkuText(item) }}</text>
|
||||
<view class="product-meta">
|
||||
<text v-if="!isGiftReceiver" class="product-price">¥{{ getItemPrice(item) }}</text>
|
||||
<text class="product-qty">x{{ item.cart_num || 1 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="arrow-icon"><text class="iconfont icon-xiangyou"></text></view>
|
||||
</view>
|
||||
<view class="arrow-icon"><text class="iconfont icon-xiangyou"></text></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@ -71,11 +74,24 @@ const productList = computed(() => {
|
||||
image: product.value.image || '',
|
||||
store_name: product.value.title || '',
|
||||
cart_num: product.value.total_num || 0,
|
||||
true_price: ctx.value.payPrice || '0.00',
|
||||
price: product.value.price || ctx.value.payPrice || '0.00',
|
||||
product_id: p.value.product_id || 0,
|
||||
}];
|
||||
});
|
||||
|
||||
/**
|
||||
* 商品金额:展示规格原价(true_price 为优惠后单价,不作为商品金额展示)
|
||||
* @param {object} item 商品项
|
||||
* @return {string} 金额
|
||||
*/
|
||||
function getItemPrice(item) {
|
||||
var attr = item.attr_info || {};
|
||||
// 订单快照 item.price 可能是商品级价格,规格金额以 attr_info.price 为准
|
||||
var val = attr.price || item.price || '0.00';
|
||||
var num = parseFloat(val);
|
||||
return isNaN(num) ? String(val) : num.toFixed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取规格文本
|
||||
* @param {object} item 商品项
|
||||
@ -100,9 +116,11 @@ function goDetail(item) {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.product-card { margin: 20rpx; background: #fff; border-radius: 16rpx; padding: 0 24rpx; }
|
||||
.product-row { display: flex; align-items: center; padding: 24rpx 0; border-bottom: 1rpx solid #f5f5f5; }
|
||||
.product-row:last-child { border-bottom: none; }
|
||||
.product-card { margin: 20rpx; background: #fff; border-radius: 16rpx; padding: 30rpx; }
|
||||
.card-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; }
|
||||
.product-row { display: flex; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #f5f5f5; }
|
||||
.product-row:first-child { padding-top: 0; }
|
||||
.product-row:last-child { border-bottom: none; padding-bottom: 0; }
|
||||
.product-image { width: 140rpx; height: 140rpx; border-radius: 12rpx; flex-shrink: 0; }
|
||||
.product-info { flex: 1; margin-left: 20rpx; display: flex; flex-direction: column; }
|
||||
.product-name { font-size: 28rpx; color: #333; line-height: 1.4; }
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
<countdown v-else-if="matchedKey === 'countdown'" :section="section" @click="emit('click', $event)" @preview="emit('preview', $event)" @action="emit('action', $event)" />
|
||||
<custom-form v-else-if="matchedKey === 'custom_form'" :section="section" @click="emit('click', $event)" @preview="emit('preview', $event)" @action="emit('action', $event)" />
|
||||
<faq-list v-else-if="matchedKey === 'faq_list'" :section="section" @click="emit('click', $event)" @preview="emit('preview', $event)" @action="emit('action', $event)" />
|
||||
<gift-info-card v-else-if="matchedKey === 'gift_info'" :section="section" @click="emit('click', $event)" @preview="emit('preview', $event)" @action="emit('action', $event)" />
|
||||
<fulfillment-card v-else-if="fulfillmentKeys.includes(matchedKey)" :section="section" @click="emit('click', $event)" @preview="emit('preview', $event)" @action="emit('action', $event)" />
|
||||
<invoice-card v-else-if="matchedKey === 'info_list:invoice'" :section="section" @click="emit('click', $event)" @preview="emit('preview', $event)" @action="emit('action', $event)" />
|
||||
<logistics-timeline v-else-if="matchedKey === 'timeline:logistics'" :section="section" @click="emit('click', $event)" @preview="emit('preview', $event)" @action="emit('action', $event)" />
|
||||
@ -61,6 +62,7 @@ import Countdown from './Countdown.vue';
|
||||
import CustomForm from './CustomForm.vue';
|
||||
import FaqList from './FaqList.vue';
|
||||
import FulfillmentCard from './FulfillmentCard.vue';
|
||||
import GiftInfoCard from './GiftInfoCard.vue';
|
||||
import InfoList from './InfoList.vue';
|
||||
import InvoiceCard from './InvoiceCard.vue';
|
||||
import LogisticsTimeline from './LogisticsTimeline.vue';
|
||||
|
||||
@ -57,10 +57,12 @@
|
||||
<image :src="item.image" mode="aspectFill" class="goods-img"></image>
|
||||
<view class="goods-text">
|
||||
<text class="goods-name line2">{{ item.store_name || '' }}</text>
|
||||
<text v-if="getItemSkuText(item)" class="goods-sku line1">{{ getItemSkuText(item) }}</text>
|
||||
<view class="goods-meta">
|
||||
<text v-if="!order.is_gift_receiver" class="goods-price">¥{{ item.true_price || item.price || '0.00' }}</text>
|
||||
<text class="goods-num">x{{ item.cart_num || 1 }}</text>
|
||||
<view class="goods-bottom">
|
||||
<text v-if="getItemSkuText(item)" class="goods-sku line1">{{ getItemSkuText(item) }}</text>
|
||||
<view class="goods-meta">
|
||||
<text v-if="!order.is_gift_receiver" class="goods-price">¥{{ item.true_price || item.price || '0.00' }}</text>
|
||||
<text class="goods-num">x{{ item.cart_num || 1 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -451,26 +453,32 @@ defineExpose({ refresh: refreshList });
|
||||
border-bottom: none;
|
||||
}
|
||||
.my-order .list .item .item-info .multi-goods .goods-img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 6rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.my-order .list .item .item-info .multi-goods .goods-text {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
height: 140rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.my-order .list .item .item-info .multi-goods .goods-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.my-order .list .item .item-info .multi-goods .goods-bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.my-order .list .item .item-info .multi-goods .goods-sku {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
margin-top: 0;
|
||||
}
|
||||
.my-order .list .item .item-info .multi-goods .goods-meta {
|
||||
display: flex;
|
||||
@ -489,8 +497,9 @@ defineExpose({ refresh: refreshList });
|
||||
}
|
||||
|
||||
.my-order .list .item .item-info .pictrue {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.my-order .list .item .item-info .pictrue image {
|
||||
@ -500,12 +509,14 @@ defineExpose({ refresh: refreshList });
|
||||
}
|
||||
|
||||
.my-order .list .item .item-info .text {
|
||||
width: 486rpx;
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
height: 140rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
line-height: 39rpx;
|
||||
}
|
||||
|
||||
@ -529,7 +540,7 @@ defineExpose({ refresh: refreshList });
|
||||
.my-order .list .item .item-info .text .sku-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.my-order .list .item .extend-text {
|
||||
@ -572,6 +583,11 @@ defineExpose({ refresh: refreshList });
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 去掉 uni-app 按钮默认 ::after 边框,避免与自定义描边叠加成双层边框 */
|
||||
.my-order .list .item .bottom .bnt::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.my-order .list .item .bottom .bnt.cancelBnt {
|
||||
border: 1rpx solid #ddd;
|
||||
color: #aaa;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user