feat(buyer): 添加电子卡券(E_COUPON)支持

- 新增 E_COUPON 商品类型,支持电子卡券的展示与购买逻辑
- 优化商品详情页,调整促销、库存和购买按钮逻辑以适应电子卡券
- 增加卡密信息展示与管理功能,支持商家端卡池管理
- 更新相关页面以处理电子卡券的特殊逻辑,如免地址、隐藏优惠券等
This commit is contained in:
田香琪
2026-07-31 13:44:56 +08:00
parent 36878e6f7f
commit 1ae7ab3036
19 changed files with 1269 additions and 85 deletions

View File

@@ -0,0 +1,18 @@
/**
* 卡密商品E_COUPON— 平台端订单展示用常量
*
* 平台不管理卡池明文S-04本模块仅用于订单详情卡密状态文案。
*
* @author Mike
* @date 2026-07-31
*/
/** 卡密状态文案(与后端 CardKeyStatusEnum 一致) */
export const CARD_KEY_STATUS_TEXT = {
UNUSED: "未使用",
ALLOCATED: "已分配",
VOIDED: "已作废",
};
export function formatCardKeyStatus(status) {
return CARD_KEY_STATUS_TEXT[status] || status || "—";
}

View File

@@ -53,6 +53,8 @@
>
<el-option label="实物商品" value="PHYSICAL_GOODS" />
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
<!-- E_COUPON卡密商品平台无卡池管理权限S-04 -->
<el-option label="电子卡券" value="E_COUPON" />
</el-select>
</el-form-item>
<el-form-item label="商品分组" prop="groupId">
@@ -385,7 +387,8 @@ export default {
goodsTypeText(v) {
if (v === "PHYSICAL_GOODS") return "实物商品";
if (v === "VIRTUAL_GOODS") return "虚拟商品";
return "电子卡券";
if (v === "E_COUPON") return "电子卡券"; // 卡密商品
return v || "—";
},
marketEnableText(v) {
if (v === "DOWN") return "下架";

View File

@@ -8,7 +8,7 @@
<el-button v-if="allowOperation.cancel" plain type="warning" @click="orderCancel">订单取消</el-button>
<el-button v-if="orderInfo.order.orderStatus === 'UNPAID'" type="primary" @click="confirmPrice">收款</el-button>
<el-button plain @click="orderLog">订单日志</el-button>
<el-button v-if="$route.query.orderType != 'VIRTUAL'" plain type="primary" style="float:right;" @click="printOrder">打印发货单</el-button>
<el-button v-if="!isNonPhysicalOrder" plain type="primary" style="float:right;" @click="printOrder">打印发货单</el-button>
</div>
</el-card>
<el-card class="mt_10 clearfix">
@@ -90,7 +90,7 @@
</div>
</div>
<div style="width: 36%; float: left">
<div class="div-item">
<div class="div-item" v-if="!isECouponOrder && orderInfo.order.deliveryMethod != 'SELF_PICK_UP'">
<div class="div-item-left">收货信息</div>
<div class="div-item-right">
{{ orderInfo.order.consigneeName }}
@@ -165,7 +165,7 @@
</div>
</div> -->
<div class="div-item" v-if="$route.query.orderType != 'VIRTUAL'">
<div class="div-item" v-if="!isNonPhysicalOrder">
<div class="div-item-left">配送方式</div>
<div class="div-item-right">
{{ orderInfo.deliveryMethodValue }}
@@ -217,6 +217,42 @@
</template>
</el-table-column>
</el-table>
<!-- E_COUPON平台订单详情只读展示卡密无卡池代管S-04 -->
<div v-if="isECouponOrder" class="ecoupon-card-keys mt_10">
<h4>卡密信息</h4>
<el-alert
v-if="!ecouponCardKeyDelivered"
type="info"
show-icon
:closable="false"
title="卡密尚未发放"
class="mb_10"
/>
<el-table
v-else-if="ecouponCardKeyRows.length"
border
:data="ecouponCardKeyRows"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="goodsName" label="商品" min-width="140" show-overflow-tooltip />
<el-table-column prop="cardNo" label="卡号" min-width="140" show-overflow-tooltip />
<el-table-column prop="cardSecret" label="卡密" min-width="120" show-overflow-tooltip />
<el-table-column prop="allocatedTime" label="发卡时间" width="170" />
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<span v-if="row">{{ formatCardKeyStatus(row.status || 'ALLOCATED') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center">
<template #default="{ row }">
<el-button v-if="row" link type="primary" @click="copyCardKey(row)">复制</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="goods-total">
<ul>
<li>
@@ -280,7 +316,7 @@
<span class="label" v-if="typeList.length == 1 && index == 0" style="font-size:10px !important;"><a @click="gotoHomes" style="display: inline-block;border-top: 1px dashed;border-bottom: 1px dashed;color:black;width:80px;">{{item.promotionName}}</a><span class="op-split">|</span>
<span class="txt" v-if="typeList.length == 1 && index == 0" style="border-top: 1px dashed;border-bottom: 1px dashed;font-size:10px !important;">¥{{ unitPrice(item.discountPrice) }}</span>
</li> -->
<li>
<li v-if="!isECouponOrder">
<span class="label">运费:</span>
<span class="txt">{{
unitPrice(orderInfo.order.freightPrice, "¥")
@@ -440,6 +476,7 @@ import vueQr from "vue-qr";
import { printElement } from "@/utils/print";
import { ElMessage, ElMessageBox } from "element-plus";
import { unitPrice, clientTypeWay } from "@/utils/filters";
import { formatCardKeyStatus } from "@/constants/cardKey";
export default {
name: "orderList",
components: {
@@ -447,6 +484,35 @@ export default {
multipleMap,
"vue-qr": vueQr,
},
computed: {
/** 电子卡券订单:无物流区,卡密来自 orderItems[].cardKeys */
isECouponOrder() {
const t = this.orderInfo?.order?.orderType;
return t === "E_COUPON" || this.$route.query.orderType === "E_COUPON";
},
isVirtualOrder() {
const t = this.orderInfo?.order?.orderType;
return t === "VIRTUAL" || this.$route.query.orderType === "VIRTUAL";
},
isNonPhysicalOrder() {
return this.isVirtualOrder || this.isECouponOrder;
},
ecouponCardKeyDelivered() {
return (this.data || []).some((item) => item.cardKeyDelivered);
},
ecouponCardKeyRows() {
const rows = [];
(this.data || []).forEach((item) => {
(item.cardKeys || []).forEach((ck) => {
rows.push({
...ck,
goodsName: item.goodsName,
});
});
});
return rows;
},
},
data () {
return {
typeList: [],
@@ -546,6 +612,23 @@ export default {
methods: {
unitPrice,
clientTypeWay,
formatCardKeyStatus,
copyCardKey(row) {
const text = `卡号:${row.cardNo || ""}\n卡密${row.cardSecret || ""}`;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
ElMessage.success("已复制到剪贴板");
});
} else {
const ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
ElMessage.success("已复制到剪贴板");
}
},
getPromotionText(row) {
let resultText = "";
if (row && row.promotionType) {