From 1accdf5826d4d7f5b4145e4dcc95cdad70acec0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E9=A6=99=E7=90=AA?= <624506849@qq.com> Date: Sun, 2 Aug 2026 13:55:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(buyer):=20=E6=9B=B4=E6=96=B0=E7=94=B5?= =?UTF-8?q?=E5=AD=90=E5=8D=A1=E5=88=B8=EF=BC=88E=5FCOUPON=EF=BC=89?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=80=BB=E8=BE=91=E4=B8=8E=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=20-=20=E4=BC=98=E5=8C=96=E8=B4=AD=E7=89=A9=E8=BD=A6=E4=B8=8E?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E9=A1=B5=E9=9D=A2=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E7=94=B5=E5=AD=90=E5=8D=A1=E5=88=B8=E5=8F=AA=E8=83=BD=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E7=AB=8B=E5=8D=B3=E8=B4=AD=E4=B9=B0=E7=BB=93=E7=AE=97?= =?UTF-8?q?=20-=20=E5=A2=9E=E5=BC=BA=E5=95=86=E5=93=81=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=99=90=E6=97=B6=E6=8A=98?= =?UTF-8?q?=E6=89=A3=E4=B8=8E=E7=AC=ACN=E4=BB=B6=E4=BC=98=E6=83=A0?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=20-=20=E6=9B=B4=E6=96=B0=E4=BF=83=E9=94=80?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF=E6=8C=81=E6=8B=BC=E5=9B=A2?= =?UTF-8?q?=E4=B8=8E=E7=A7=AF=E5=88=86=E8=B4=AD=E4=B9=B0=E9=80=89=E9=A1=B9?= =?UTF-8?q?=20-=20=E8=A7=84=E8=8C=83=E5=8C=96=E5=95=86=E5=93=81=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=B8=8E=E5=BA=93=E5=AD=98=E6=8F=90=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buyer/src/api/cart.js | 6 +- .../src/components/goodsDetail/ShowGoods.vue | 100 +++++-- buyer/src/constants/goodsType.js | 184 +++++++++++- buyer/src/pages/Cart.vue | 16 +- .../pages/home/orderCenter/OrderDetail.vue | 134 ++++++--- buyer/src/pages/payment/Pay.vue | 32 +-- buyer/src/pages/payment/PayMent.vue | 6 +- .../src/pages/promotion/PointGoodsDetail.vue | 271 +++++++++--------- .../components/lili-dialog/goods-dialog.vue | 22 +- manager/src/constants/cardKey.js | 1 + manager/src/constants/goodsType.js | 35 +++ .../kanjia/kanjia-activity-add-goods.vue | 18 +- .../kanjia/kanjia-activity-edit-goods.vue | 22 +- .../points-goods/points-goods-add.vue | 15 + seller/src/constants/cardKey.js | 4 + seller/src/constants/goodsType.js | 152 ++++++++++ .../src/views/goods/card-key/cardKeyPool.vue | 12 +- .../goods/goods-seller/goodsOperationSec.vue | 30 +- seller/src/views/lili-dialog/goods-dialog.vue | 22 +- seller/src/views/order/order/orderDetail.vue | 115 +++++--- .../flash-discount/flash-discount-add.vue | 10 +- .../full-discount/full-discount-add.vue | 15 +- .../views/promotion/pintuan/pintuan-goods.vue | 4 +- .../views/promotion/seckill/seckill-goods.vue | 5 +- 24 files changed, 920 insertions(+), 311 deletions(-) create mode 100644 manager/src/constants/goodsType.js create mode 100644 seller/src/constants/goodsType.js diff --git a/buyer/src/api/cart.js b/buyer/src/api/cart.js index 4e77ab1a..60499586 100644 --- a/buyer/src/api/cart.js +++ b/buyer/src/api/cart.js @@ -65,7 +65,7 @@ export function addCartGoods (params) { /** * 创建交易 * @param client 客户端:H5/移动端 PC/PC端,WECHAT_MP/小程序端,APP/移动应用端 - * @param way 购物车购买:CART/立即购买:BUY_NOW / 积分购买:POINT + * @param way 购物车:CART / 立即购买:BUY_NOW / 积分:POINTS / 拼团:PINTUAN / 砍价:KANJIA * @param remark 备注 非必填 */ export function createTrade (data) { @@ -81,7 +81,7 @@ export function createTrade (data) { /** * 选择优惠券 * @param memberCouponId 优惠券id - * @param way 购物车购买:CART/立即购买:BUY_NOW/ 积分购买:POINT + * @param way 购物车:CART / 立即购买:BUY_NOW / 积分:POINTS * @param used 使用true 弃用 false */ export function selectCoupon (params) { @@ -95,7 +95,7 @@ export function selectCoupon (params) { /** * 选择礼品卡 - * @param way 购物车购买:CART/立即购买:BUY_NOW/拼团购买:PINTUAN / 积分购买:POINT + * @param way 购物车:CART / 立即购买:BUY_NOW / 拼团:PINTUAN / 积分:POINTS / 砍价:KANJIA * @param credentialId 礼品卡凭证 id;used=false 且不传则清空全部 * @param used 使用 true / 弃用 false */ diff --git a/buyer/src/components/goodsDetail/ShowGoods.vue b/buyer/src/components/goodsDetail/ShowGoods.vue index 44500b89..98a81c86 100644 --- a/buyer/src/components/goodsDetail/ShowGoods.vue +++ b/buyer/src/components/goodsDetail/ShowGoods.vue @@ -71,7 +71,7 @@ @@ -80,7 +80,7 @@

-
+
优 惠 券 @@ -165,8 +165,38 @@
+ +
+

+ 促     销 + 限时折扣 + + 享 {{ promotionMap["DISCOUNT"].discount }} 折 + +

+
+ +
+

+ 促     销 + 第N件 + + 第 {{ promotionMap["NTH"].nthNum || promotionMap["NTH"].num }} 件优惠 + +

+
+ +
+

+ 拼     团 + 拼团价 + + {{ $filters.unitPrice(promotionMap["PINTUAN"].price, "¥") }} + +

+
-
+

促     销 @@ -294,6 +324,14 @@ @click="addShoppingCartBtn" >加入购物车 + 拼团购买 { - this.loading1 = false; + this[loadingKey] = false; if (res.success) { this.$router.push({ path: "/pay", @@ -564,7 +614,7 @@ export default { } }) .catch(() => { - this.loading1 = false; + this[loadingKey] = false; }); }, async collect() { @@ -642,16 +692,20 @@ export default { }); }, promotion() { - this.promotionMap = {SECKILL: null, FULL_DISCOUNT: null, COUPON: []}; - // 格式化促销活动,返回当前促销的对象 - if (!this.detail.promotionMap) return false; - let keysArr = Object.keys(this.detail.promotionMap); + this.promotionMap = createEmptyPromotionMap(); + if (!this.detail || !this.detail.promotionMap) return false; + const keysArr = Object.keys(this.detail.promotionMap); if (keysArr.length === 0) return false; for (let i = 0; i < keysArr.length; i++) { - let key = keysArr[i].split("-")[0]; + const key = keysArr[i].split("-")[0]; if (key === "COUPON") { - this.promotionMap[key].push(this.detail.promotionMap[keysArr[i]]); + if (!Array.isArray(this.promotionMap.COUPON)) { + this.promotionMap.COUPON = []; + } + this.promotionMap.COUPON.push(this.detail.promotionMap[keysArr[i]]); + } else if (Object.prototype.hasOwnProperty.call(this.promotionMap, key)) { + this.promotionMap[key] = this.detail.promotionMap[keysArr[i]]; } else { this.promotionMap[key] = this.detail.promotionMap[keysArr[i]]; } diff --git a/buyer/src/constants/goodsType.js b/buyer/src/constants/goodsType.js index 883c1fcb..73f3bfe6 100644 --- a/buyer/src/constants/goodsType.js +++ b/buyer/src/constants/goodsType.js @@ -1,9 +1,9 @@ /** * 卡密商品(E_COUPON / 电子卡券)— 买家端公共常量与工具 * - * 与 VIRTUAL_GOODS(核销型虚拟)区分:E_COUPON 仅立即购买(BUY_NOW), - * 支付后自动发卡,订单终态 COMPLETED;可售库存优先读 poolStock。 - * 需求:lilishop/docs/requirements/card-key-goods-v4.md + * 与 VIRTUAL_GOODS(核销型虚拟)区分:E_COUPON 常规促销走 BUY_NOW, + * 特殊活动走 PINTUAN / POINTS / KANJIA;禁止 CART / VIRTUAL。 + * 需求:lilishop/docs/requirements/card-key-goods-v4.md · M-02 * * @author Mike * @date 2026-07-31 @@ -11,9 +11,15 @@ export const E_COUPON_GOODS_TYPE = "E_COUPON"; export const E_COUPON_ORDER_TYPE = "E_COUPON"; -/** 单次购买数量上限(O-09 待拍板默认值:min(poolStock, 999)) */ +/** 单次购买数量上限(O-09 默认:min(quantity, 999)) */ export const E_COUPON_MAX_BUY_NUM = 999; +/** 常规立即购买(秒杀/满减/券/限时/第N件等) */ +export const E_COUPON_CART_BUY_NOW = "BUY_NOW"; +export const E_COUPON_CART_PINTUAN = "PINTUAN"; +export const E_COUPON_CART_POINTS = "POINTS"; +export const E_COUPON_CART_KANJIA = "KANJIA"; + export function isECoupon(goodsType) { return goodsType === E_COUPON_GOODS_TYPE; } @@ -22,13 +28,9 @@ export function isECouponOrder(orderType) { return orderType === E_COUPON_ORDER_TYPE; } -/** 可售库存:优先 poolStock */ +/** 可售库存:E_COUPON 读 SKU quantity(由卡池 syncSkuStock 同步) */ export function getECouponStock(sku) { if (!sku) return 0; - const pool = sku.poolStock; - if (pool !== undefined && pool !== null) { - return Number(pool) || 0; - } return Number(sku.quantity) || 0; } @@ -37,3 +39,167 @@ export function getECouponMaxBuyNum(sku) { if (stock <= 0) return 1; return Math.min(stock, E_COUPON_MAX_BUY_NUM); } + +/** + * 解析 E_COUPON 结算 cartType / way + * @param {object} opts + * @param {string} [opts.routeWay] 路由 query.way + * @param {'BUY_NOW'|'PINTUAN'|'POINTS'|'KANJIA'} [opts.action] 用户点击的购买入口 + */ +export function resolveECouponCartType({ routeWay, action } = {}) { + if (action === E_COUPON_CART_PINTUAN) return E_COUPON_CART_PINTUAN; + if (action === E_COUPON_CART_POINTS) return E_COUPON_CART_POINTS; + if (action === E_COUPON_CART_KANJIA) return E_COUPON_CART_KANJIA; + if (action === E_COUPON_CART_BUY_NOW) return E_COUPON_CART_BUY_NOW; + const way = routeWay ? String(routeWay).toUpperCase() : ""; + if (way === "KANJIA") return E_COUPON_CART_KANJIA; + if (way === "PINTUAN") return E_COUPON_CART_PINTUAN; + if (way === "POINT" || way === "POINTS") return E_COUPON_CART_POINTS; + return E_COUPON_CART_BUY_NOW; +} + +/** 详情页 promotionMap 初始结构(切换 SKU 时完整重置) */ +export function createEmptyPromotionMap() { + return { + SECKILL: null, + FULL_DISCOUNT: null, + COUPON: [], + DISCOUNT: null, + NTH: null, + PINTUAN: null, + KANJIA: null, + }; +} + +/** 卡密履约状态(与后端 CardKeyFulfillStatusEnum 一致) */ +export const CARD_KEY_FULFILL_STATUS = { + PENDING: "PENDING", + DELIVERED: "DELIVERED", + FAILED: "FAILED", + NOT_APPLICABLE: "NOT_APPLICABLE", +}; + +export const CARD_KEY_FULFILL_STATUS_TEXT = { + PENDING: "待发放", + DELIVERED: "已发放", + FAILED: "发放失败", + NOT_APPLICABLE: "不适用", +}; + +export function cardKeyFulfillAlertType(status) { + const map = { + PENDING: "info", + DELIVERED: "success", + FAILED: "warning", + }; + return map[status] || "info"; +} + +export function formatCardKeyFulfillStatus(status) { + return CARD_KEY_FULFILL_STATUS_TEXT[status] || status || "—"; +} + +export function resolveCardKeyFulfillMessage(line) { + if (line && line.message) return line.message; + return formatCardKeyFulfillStatus(line?.status); +} + +export function isCardKeyFulfillApplicable(item) { + if (!item) return false; + const status = item.cardKeyFulfillStatus; + if (status) return status !== CARD_KEY_FULFILL_STATUS.NOT_APPLICABLE; + if (item.cardKeyDelivered != null || (item.cardKeys && item.cardKeys.length)) return true; + return item.goodsType === E_COUPON_GOODS_TYPE; +} + +function normalizeCardKeyFulfillLine(source) { + const status = + source.cardKeyFulfillStatus || + (source.cardKeyDelivered + ? CARD_KEY_FULFILL_STATUS.DELIVERED + : CARD_KEY_FULFILL_STATUS.PENDING); + const cardKeys = + status === CARD_KEY_FULFILL_STATUS.DELIVERED && source.cardKeyDelivered + ? source.cardKeys || [] + : []; + return { + key: source.sn || source.orderSn || source.id || source.skuId, + goodsName: source.goodsName, + status, + message: source.cardKeyFulfillMessage, + cardKeys, + isGift: !!source.isGift, + }; +} + +/** 汇总 E_COUPON 主单行 + 满赠子单摘要的履约行 */ +export function collectCardKeyFulfillLines(orderItems = [], giftSummaries = []) { + const lines = []; + (orderItems || []).forEach((item) => { + if (!isCardKeyFulfillApplicable(item)) return; + lines.push(normalizeCardKeyFulfillLine(item)); + }); + (giftSummaries || []).forEach((gift) => { + lines.push(normalizeCardKeyFulfillLine({ ...gift, isGift: true })); + }); + return lines; +} + +export function orderHasCardKeySection({ orderItems, giftSummaries, isECouponOrder } = {}) { + if (isECouponOrder) return true; + if (Array.isArray(giftSummaries) && giftSummaries.length > 0) return true; + if (collectCardKeyFulfillLines(orderItems, []).length > 0) return true; + return (orderItems || []).some( + (item) => + item.goodsType === E_COUPON_GOODS_TYPE || + (Array.isArray(item.cardKeys) && item.cardKeys.length > 0) + ); +} + +export function flattenCardKeyFulfillLines(lines, withGoodsName = false) { + const rows = []; + (lines || []).forEach((line) => { + if (line.status !== CARD_KEY_FULFILL_STATUS.DELIVERED) return; + (line.cardKeys || []).forEach((ck) => { + rows.push(withGoodsName ? { ...ck, goodsName: line.goodsName } : { ...ck }); + }); + }); + return rows; +} + +/** 订单是否含已交付或可展示的卡密行(含满赠 E_COUPON 子单行) */ +export function orderHasCardKeyContent(orderItems, giftSummaries) { + const lines = collectCardKeyFulfillLines(orderItems, giftSummaries); + return flattenCardKeyFulfillLines(lines).length > 0; +} + +/** 扁平化订单项卡密(仅 cardKeyDelivered 且含 cardKeys;兼容旧逻辑) */ +export function flattenOrderCardKeys(orderItems, withGoodsName = false, giftSummaries) { + const lines = collectCardKeyFulfillLines(orderItems, giftSummaries); + if (lines.some((line) => line.status)) { + return flattenCardKeyFulfillLines(lines, withGoodsName); + } + const rows = []; + (orderItems || []).forEach((item) => { + if (!item.cardKeyDelivered || !item.cardKeys || !item.cardKeys.length) return; + item.cardKeys.forEach((ck) => { + rows.push(withGoodsName ? { ...ck, goodsName: item.goodsName } : { ...ck }); + }); + }); + return rows; +} + +/** createTrade 后是否无需进入收银台(积分单 / 0 元 / 服务端已标记) */ +export function shouldSkipPaymentPage(tradeResult, way) { + if (!tradeResult) return false; + if (way === E_COUPON_CART_POINTS) return true; + if (tradeResult.needPay === false || tradeResult.paid === true) return true; + const price = + tradeResult.price != null + ? Number(tradeResult.price) + : tradeResult.flowPrice != null + ? Number(tradeResult.flowPrice) + : null; + if (price != null && !Number.isNaN(price) && price <= 0) return true; + return false; +} diff --git a/buyer/src/pages/Cart.vue b/buyer/src/pages/Cart.vue index 98b5b567..938cef40 100644 --- a/buyer/src/pages/Cart.vue +++ b/buyer/src/pages/Cart.vue @@ -248,6 +248,7 @@ import Promotion from "@/components/goodsDetail/Promotion"; import Search from "@/components/Search"; import * as APICart from "@/api/cart"; import * as APIMember from "@/api/member"; +import { isECoupon } from "@/constants/goodsType"; export default { name: "Cart", beforeRouteEnter(to, from, next) { @@ -407,11 +408,20 @@ export default { }, // 跳转支付页面 pay() { - if (this.checkedNum) { - this.$router.push({ path: "/pay", query: { way: "CART" } }); - } else { + if (!this.checkedNum) { Message.warning("请至少选择一件商品"); + return; } + const hasECoupon = this.cartList.some((shop) => + (shop.skuList || []).some( + (goods) => goods.checked && isECoupon(goods.goodsSku && goods.goodsSku.goodsType) + ) + ); + if (hasECoupon) { + Message.warning("电子卡券请使用立即购买,不可通过购物车结算"); + return; + } + this.$router.push({ path: "/pay", query: { way: "CART" } }); }, // 展示优惠券 showCoupon(storeId, index) { diff --git a/buyer/src/pages/home/orderCenter/OrderDetail.vue b/buyer/src/pages/home/orderCenter/OrderDetail.vue index 03fbecae..10f1fcb4 100644 --- a/buyer/src/pages/home/orderCenter/OrderDetail.vue +++ b/buyer/src/pages/home/orderCenter/OrderDetail.vue @@ -132,35 +132,59 @@

未开发票
- -
+ +

卡密信息

- - + + +
+ + 复制全部卡密 +
@@ -378,7 +402,14 @@ import { getPackage } from "@/api/order.js"; import { afterSaleReason, receiptDetail } from "@/api/member"; -import { isECouponOrder as checkECouponOrder } from "@/constants/goodsType"; +import { + isECouponOrder as checkECouponOrder, + collectCardKeyFulfillLines, + orderHasCardKeySection, + flattenOrderCardKeys, + cardKeyFulfillAlertType, + resolveCardKeyFulfillMessage, +} from "@/constants/goodsType"; /** * 订单详情:E_COUPON 展示 orderItems[].cardKeys,屏蔽售后/物流(FR-B-03 / FR-B-04)。 @@ -415,20 +446,37 @@ export default { isNonPhysicalOrder() { return this.isVirtualOrder || this.isECouponOrder; }, - ecouponCardKeyDelivered() { - return (this.order.orderItems || []).some((item) => item.cardKeyDelivered); + /** 是否展示卡密区块(含满赠 E_COUPON 子单 giftECouponOrders) */ + hasCardKeySection() { + return orderHasCardKeySection({ + orderItems: this.order.orderItems, + giftSummaries: this.order.giftECouponOrders, + isECouponOrder: this.isECouponOrder, + }); + }, + cardKeyFulfillLines() { + return collectCardKeyFulfillLines( + this.order.orderItems, + this.order.giftECouponOrders + ); }, ecouponCardKeyRows() { - const rows = []; - (this.order.orderItems || []).forEach((item) => { - (item.cardKeys || []).forEach((ck) => { - rows.push({ ...ck }); - }); - }); - return rows; + return flattenOrderCardKeys( + this.order.orderItems, + false, + this.order.giftECouponOrders + ); }, }, methods: { + cardKeyFulfillAlertType, + resolveCardKeyFulfillMessage, + lineCardKeyRows(line) { + return (line.cardKeys || []).map((ck) => ({ + ...ck, + goodsName: line.goodsName, + })); + }, copyCardKey(row) { const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`; this.copyText(text); @@ -767,6 +815,18 @@ table { display: none; } +.ecoupon-fulfill-block + .ecoupon-fulfill-block { + margin-top: 16px; + padding-top: 16px; + border-top: 1px dashed #eee; +} + +.ecoupon-fulfill-title { + font-weight: 600; + color: #333; + margin-bottom: 8px; +} + .ecoupon-card-keys-pc { display: block; } diff --git a/buyer/src/pages/payment/Pay.vue b/buyer/src/pages/payment/Pay.vue index 3c515d10..757d8722 100644 --- a/buyer/src/pages/payment/Pay.vue +++ b/buyer/src/pages/payment/Pay.vue @@ -151,8 +151,8 @@
- -
+ +
发票信息 开企业抬头发票须填写纳税人识别号,以免影响报销 @@ -167,8 +167,8 @@ 编辑
- -
+ +
优惠券
@@ -204,7 +204,7 @@
-
+
使用礼品卡 @@ -284,13 +284,13 @@