feat(buyer): 更新电子卡券(E_COUPON)相关逻辑与展示

- 优化购物车与支付页面,确保电子卡券只能通过立即购买结算
- 增强商品详情页,添加限时折扣与第N件优惠展示
- 更新促销逻辑,支持拼团与积分购买选项
- 规范化商品类型与库存提示,提升用户体验
This commit is contained in:
田香琪
2026-08-02 13:55:19 +08:00
parent 1ae7ab3036
commit 1accdf5826
24 changed files with 920 additions and 311 deletions

View File

@@ -45,7 +45,15 @@
<div class="wap-sku">
{{ item.goodsUnit }}
<el-tag
style="margin-left: 10px"
v-if="item.goodsType"
style="margin-left: 6px"
size="small"
:type="goodsTypeTagType(item.goodsType)"
>
{{ goodsTypeLabel(item.goodsType) }}
</el-tag>
<el-tag
style="margin-left: 6px"
:type="item.salesModel === 'RETAIL' ? 'info' : 'primary'"
>
{{ item.salesModel === "RETAIL" ? "零售型" : "批发型" }}
@@ -53,6 +61,9 @@
</div>
<div class="wap-content-desc-bottom">
<div>{{ $filters.unitPrice(item.price) }}</div>
<div v-if="item.goodsType === 'E_COUPON'" class="pool-stock-hint">
库存 {{ item.quantity != null ? item.quantity : "" }}卡池
</div>
</div>
</div>
</div>
@@ -75,6 +86,7 @@
</template>
<script>
import * as API_Goods from "@/api/goods";
import { goodsTypeLabel, goodsTypeTagType } from "@/constants/goodsType";
export default {
props: {
selectedWay: {
@@ -129,6 +141,8 @@ export default {
this.init();
},
methods: {
goodsTypeLabel,
goodsTypeTagType,
onSearchGoods() {
this.goodsData = [];
this.goodsParams.pageNumber = 1;
@@ -270,7 +284,9 @@ export default {
}
</style>
<style lang="scss">
.goods-dialog-cascader-popper {
z-index: 10001 !important;
.pool-stock-hint {
font-size: 12px;
color: #999;
margin-top: 4px;
}
</style>

View File

@@ -10,6 +10,7 @@
export const CARD_KEY_STATUS_TEXT = {
UNUSED: "未使用",
ALLOCATED: "已分配",
RESERVED: "已预占",
VOIDED: "已作废",
};

View File

@@ -0,0 +1,35 @@
/**
* 卡密商品E_COUPON— 平台端公共常量
*
* @author Mike
* @date 2026-08-02
*/
export const E_COUPON_GOODS_TYPE = "E_COUPON";
export function isECoupon(goodsType) {
return goodsType === E_COUPON_GOODS_TYPE;
}
export function goodsTypeLabel(goodsType) {
const map = {
E_COUPON: "电子卡券",
VIRTUAL_GOODS: "虚拟商品",
PHYSICAL_GOODS: "实物商品",
};
return map[goodsType] || goodsType || "—";
}
export function goodsTypeTagType(goodsType) {
if (goodsType === E_COUPON_GOODS_TYPE) return "warning";
if (goodsType === "VIRTUAL_GOODS") return "info";
return "";
}
export function eCouponStockHint(goodsType) {
if (!isECoupon(goodsType)) return "";
return "库存来自卡池同步quantity";
}
export function promotionPriceMin(goodsType) {
return isECoupon(goodsType) ? 0 : 0.01;
}

View File

@@ -105,6 +105,7 @@
import { saveKanJiaActivityGoods } from "@/api/promotion";
import { regular } from "@/utils";
import skuSelect from "@/components/lili-dialog";
import { isECoupon } from "@/constants/goodsType";
export default {
name: "add-kanjia-activity-goods",
@@ -175,6 +176,7 @@ export default {
let checkResult = true;
this.form.promotionGoodsList.forEach((res) => {
const eCoupon = isECoupon(res.goodsType);
if (res.stock <= 0 || res.stock > res.quantity) {
checkResult = false;
this.$Message.error("活动库存不能为0且不能超过商品库存");
@@ -186,9 +188,16 @@ export default {
this.$Message.error("结算价格金额格式不正确");
return;
}
if (res.settlementPrice < 0 || res.settlementPrice > res.price) {
if (
(eCoupon ? res.settlementPrice < 0 : res.settlementPrice <= 0) ||
res.settlementPrice > res.price
) {
checkResult = false;
this.$Message.error("结算价格金额不能为0且不能超过商品价格");
this.$Message.error(
eCoupon
? "结算价格不能小于0且不能超过商品价格"
: "结算价格金额不能为0且不能超过商品价格"
);
return;
}
if (!regular.money.test(res.highestPrice)) {
@@ -206,9 +215,9 @@ export default {
this.$Message.error("最低可砍金额格式错误");
return;
}
if (res.lowestPrice <= 0 || res.lowestPrice > res.price) {
if (res.lowestPrice < 0 || (!eCoupon && res.lowestPrice <= 0) || res.lowestPrice > res.price) {
checkResult = false;
this.$Message.error("最低可砍金额不能为0");
this.$Message.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
return;
}
if (parseInt(res.lowestPrice) > parseInt(res.highestPrice)) {
@@ -277,6 +286,7 @@ export default {
storeId: e.storeId,
storeName: e.storeName,
skuId: e.id,
goodsType: e.goodsType,
});
});
this.form.promotionGoodsList = list;

View File

@@ -90,6 +90,7 @@
<script>
import { getKanJiaActivityGoodsById, editKanJiaActivityGoods } from "@/api/promotion";
import { regular } from "@/utils";
import { isECoupon } from "@/constants/goodsType";
export default {
name: "editKanjiaActivityGoods",
@@ -200,6 +201,8 @@ export default {
params.endTime = this.$filters.unixToDate(endDate.getTime() / 1000);
delete params.rangeTime;
const eCoupon = isECoupon(params.goodsType || params.goodsSku?.goodsType);
if (params.stock <= 0 || params.stock > params.goodsSku.quantity) {
this.$Message.error("活动库存不能为0且不能超过商品库存");
return;
@@ -208,8 +211,15 @@ export default {
this.$Message.error("结算价格金额格式不正确");
return;
}
if (params.settlementPrice < 0 || params.settlementPrice > params.price) {
this.$Message.error("结算价格金额不能为0且不能超过商品价格");
if (
(eCoupon ? params.settlementPrice < 0 : params.settlementPrice <= 0) ||
params.settlementPrice > params.price
) {
this.$Message.error(
eCoupon
? "结算价格不能小于0且不能超过商品价格"
: "结算价格金额不能为0且不能超过商品价格"
);
return;
}
if (!regular.money.test(params.highestPrice)) {
@@ -224,8 +234,12 @@ export default {
this.$Message.error("最低可砍金额格式错误");
return;
}
if (params.lowestPrice <= 0 || params.lowestPrice > params.price) {
this.$Message.error("最低可砍金额不能为0");
if (
params.lowestPrice < 0 ||
(!eCoupon && params.lowestPrice <= 0) ||
params.lowestPrice > params.price
) {
this.$Message.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
return;
}
if (params.lowestPrice > params.highestPrice) {

View File

@@ -23,6 +23,11 @@
>
<el-table-column type="selection" width="60" align="center" />
<el-table-column prop="goodsName" label="商品名称" min-width="120" show-overflow-tooltip />
<el-table-column label="类型" width="100">
<template #default="{ row }">
<span v-if="row">{{ goodsTypeLabel(row.goodsType) }}</span>
</template>
</el-table-column>
<el-table-column label="SKU编码" min-width="120">
<template #default="{ row }">
<span v-if="row">{{ row.skuId }}</span>
@@ -117,6 +122,7 @@
<script>
import { addPointsGoods, getPointsGoodsCategoryList } from "@/api/promotion";
import skuSelect from "@/components/lili-dialog";
import { isECoupon, goodsTypeLabel } from "@/constants/goodsType";
export default {
name: "addPoinsGoods",
@@ -160,6 +166,7 @@ export default {
await this.getCategory();
},
methods: {
goodsTypeLabel,
async getCategory() {
let res = await getPointsGoodsCategoryList();
this.categoryList = res.result.records;
@@ -172,6 +179,13 @@ export default {
this.$Modal.warning({ title: "提示", content: "请选择指定商品" });
return;
}
for (const row of params) {
const stock = Number(row.quantity) || 0;
if (isECoupon(row.goodsType) && Number(row.activeStock) > stock) {
this.$Message.error(`${row.goodsName}】活动库存不能超过卡池库存(${stock}`);
return;
}
}
this.form.startTime = this.form.rangeTime[0];
this.form.endTime = this.form.rangeTime[1];
const start = this.$filters.unixToDate(this.form.startTime / 1000);
@@ -251,6 +265,7 @@ export default {
quantity: e.quantity || "",
storeName: e.storeName || "",
price: e.price || "",
goodsType: e.goodsType,
};
list.push(obj);
});