mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-08-06 10:57:26 +08:00
feat(buyer): 更新电子卡券(E_COUPON)相关逻辑与展示
- 优化购物车与支付页面,确保电子卡券只能通过立即购买结算 - 增强商品详情页,添加限时折扣与第N件优惠展示 - 更新促销逻辑,支持拼团与积分购买选项 - 规范化商品类型与库存提示,提升用户体验
This commit is contained in:
@@ -65,7 +65,7 @@ export function addCartGoods (params) {
|
|||||||
/**
|
/**
|
||||||
* 创建交易
|
* 创建交易
|
||||||
* @param client 客户端:H5/移动端 PC/PC端,WECHAT_MP/小程序端,APP/移动应用端
|
* @param client 客户端:H5/移动端 PC/PC端,WECHAT_MP/小程序端,APP/移动应用端
|
||||||
* @param way 购物车购买:CART/立即购买:BUY_NOW / 积分购买:POINT
|
* @param way 购物车:CART / 立即购买:BUY_NOW / 积分:POINTS / 拼团:PINTUAN / 砍价:KANJIA
|
||||||
* @param remark 备注 非必填
|
* @param remark 备注 非必填
|
||||||
*/
|
*/
|
||||||
export function createTrade (data) {
|
export function createTrade (data) {
|
||||||
@@ -81,7 +81,7 @@ export function createTrade (data) {
|
|||||||
/**
|
/**
|
||||||
* 选择优惠券
|
* 选择优惠券
|
||||||
* @param memberCouponId 优惠券id
|
* @param memberCouponId 优惠券id
|
||||||
* @param way 购物车购买:CART/立即购买:BUY_NOW/ 积分购买:POINT
|
* @param way 购物车:CART / 立即购买:BUY_NOW / 积分:POINTS
|
||||||
* @param used 使用true 弃用 false
|
* @param used 使用true 弃用 false
|
||||||
*/
|
*/
|
||||||
export function selectCoupon (params) {
|
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 credentialId 礼品卡凭证 id;used=false 且不传则清空全部
|
||||||
* @param used 使用 true / 弃用 false
|
* @param used 使用 true / 弃用 false
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 限时秒杀 -->
|
<!-- 限时秒杀 -->
|
||||||
<Promotion
|
<Promotion
|
||||||
v-if="!isECouponGoods && promotionMap['SECKILL']"
|
v-if="promotionMap['SECKILL']"
|
||||||
:time="promotionMap['SECKILL'].endTime"
|
:time="promotionMap['SECKILL'].endTime"
|
||||||
></Promotion>
|
></Promotion>
|
||||||
<!-- 商品详细 价格、优惠券、促销 -->
|
<!-- 商品详细 价格、优惠券、促销 -->
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<!-- 秒杀价格 -->
|
<!-- 秒杀价格 -->
|
||||||
<div
|
<div
|
||||||
class="item-price-row"
|
class="item-price-row"
|
||||||
v-if="skuDetail.promotionPrice && !isECouponGoods && promotionMap['SECKILL']"
|
v-if="skuDetail.promotionPrice && promotionMap['SECKILL']"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<span class="item-price-title" v-if="promotionMap['SECKILL']"
|
<span class="item-price-title" v-if="promotionMap['SECKILL']"
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 优惠券展示 -->
|
<!-- 优惠券展示 -->
|
||||||
<div class="item-price-coupon-row" v-if="!isECouponGoods && promotionMap['COUPON'].length">
|
<div class="item-price-coupon-row" v-if="promotionMap['COUPON'].length">
|
||||||
<div class="Ellipsis">
|
<div class="Ellipsis">
|
||||||
<span class="item-price-title">优 惠 券</span>
|
<span class="item-price-title">优 惠 券</span>
|
||||||
<span>
|
<span>
|
||||||
@@ -165,8 +165,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 限时折扣 -->
|
||||||
|
<div class="item-price-row" v-if="promotionMap['DISCOUNT']">
|
||||||
|
<p>
|
||||||
|
<span class="item-price-title">促 销</span>
|
||||||
|
<span class="item-promotion">限时折扣</span>
|
||||||
|
<span class="item-desc-pintuan" v-if="promotionMap['DISCOUNT'].discount">
|
||||||
|
享 {{ promotionMap["DISCOUNT"].discount }} 折
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- 第N件优惠 -->
|
||||||
|
<div class="item-price-row" v-if="promotionMap['NTH']">
|
||||||
|
<p>
|
||||||
|
<span class="item-price-title">促 销</span>
|
||||||
|
<span class="item-promotion">第N件</span>
|
||||||
|
<span class="item-desc-pintuan">
|
||||||
|
第 {{ promotionMap["NTH"].nthNum || promotionMap["NTH"].num }} 件优惠
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- 拼团 -->
|
||||||
|
<div class="item-price-row" v-if="promotionMap['PINTUAN']">
|
||||||
|
<p>
|
||||||
|
<span class="item-price-title">拼 团</span>
|
||||||
|
<span class="item-promotion">拼团价</span>
|
||||||
|
<span class="item-desc-pintuan" v-if="promotionMap['PINTUAN'].price">
|
||||||
|
{{ $filters.unitPrice(promotionMap["PINTUAN"].price, "¥") }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<!-- 满减展示 -->
|
<!-- 满减展示 -->
|
||||||
<div class="item-price-row" v-if="!isECouponGoods && promotionMap['FULL_DISCOUNT']">
|
<div class="item-price-row" v-if="promotionMap['FULL_DISCOUNT']">
|
||||||
<p>
|
<p>
|
||||||
<span class="item-price-title"
|
<span class="item-price-title"
|
||||||
>促 销</span>
|
>促 销</span>
|
||||||
@@ -294,6 +324,14 @@
|
|||||||
@click="addShoppingCartBtn"
|
@click="addShoppingCartBtn"
|
||||||
>加入购物车</el-button
|
>加入购物车</el-button
|
||||||
>
|
>
|
||||||
|
<el-button
|
||||||
|
class="goods-action-btn"
|
||||||
|
v-if="promotionMap['PINTUAN']"
|
||||||
|
:loading="loadingPintuan"
|
||||||
|
:disabled="isOutOfStock"
|
||||||
|
@click="pintuanBuy"
|
||||||
|
>拼团购买</el-button
|
||||||
|
>
|
||||||
<el-button
|
<el-button
|
||||||
class="goods-action-btn"
|
class="goods-action-btn"
|
||||||
:loading="loading1"
|
:loading="loading1"
|
||||||
@@ -334,6 +372,11 @@ import {
|
|||||||
isECoupon,
|
isECoupon,
|
||||||
getECouponStock,
|
getECouponStock,
|
||||||
getECouponMaxBuyNum,
|
getECouponMaxBuyNum,
|
||||||
|
createEmptyPromotionMap,
|
||||||
|
resolveECouponCartType,
|
||||||
|
E_COUPON_CART_BUY_NOW,
|
||||||
|
E_COUPON_CART_PINTUAN,
|
||||||
|
E_COUPON_CART_POINTS,
|
||||||
} from "@/constants/goodsType";
|
} from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -377,15 +420,11 @@ export default {
|
|||||||
specList: [],
|
specList: [],
|
||||||
}, // sku详情
|
}, // sku详情
|
||||||
goodsSpecList: this.detail.specs, // 商品spec
|
goodsSpecList: this.detail.specs, // 商品spec
|
||||||
promotionMap: {
|
promotionMap: createEmptyPromotionMap(),
|
||||||
// 活动状态
|
|
||||||
SECKILL: null,
|
|
||||||
FULL_DISCOUNT: null,
|
|
||||||
COUPON: [],
|
|
||||||
}, // 促销活动
|
|
||||||
formatList: [], // 选择商品品类的数组
|
formatList: [], // 选择商品品类的数组
|
||||||
loading: false, // 立即购买loading
|
loading: false, // 立即购买loading
|
||||||
loading1: false, // 加入购物车loading
|
loading1: false, // 加入购物车loading
|
||||||
|
loadingPintuan: false,
|
||||||
isCollected: false, // 是否收藏
|
isCollected: false, // 是否收藏
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -412,7 +451,7 @@ export default {
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
/** 卡密商品:禁加购/禁促销,仅立即购买,库存用 poolStock(FR-B-01 / P-06) */
|
/** 卡密商品:禁加购,仅立即购买;库存读 quantity(M-02 起支持促销) */
|
||||||
isECouponGoods() {
|
isECouponGoods() {
|
||||||
return isECoupon(this.skuDetail.goodsType);
|
return isECoupon(this.skuDetail.goodsType);
|
||||||
},
|
},
|
||||||
@@ -539,21 +578,32 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
buyNow() {
|
buyNow() {
|
||||||
// 立即购买
|
this.startCheckout(E_COUPON_CART_BUY_NOW, "loading1");
|
||||||
|
},
|
||||||
|
pintuanBuy() {
|
||||||
|
this.startCheckout(E_COUPON_CART_PINTUAN, "loadingPintuan");
|
||||||
|
},
|
||||||
|
pointPay() {
|
||||||
|
this.startCheckout(E_COUPON_CART_POINTS, "loading");
|
||||||
|
},
|
||||||
|
startCheckout(action, loadingKey) {
|
||||||
const params = {
|
const params = {
|
||||||
num: this.count,
|
num: this.count,
|
||||||
skuId: this.skuDetail.id,
|
skuId: this.skuDetail.id,
|
||||||
cartType: "BUY_NOW",
|
cartType: "BUY_NOW",
|
||||||
};
|
};
|
||||||
// 虚拟商品:cartType=VIRTUAL(核销码流程)
|
|
||||||
if (this.skuDetail.goodsType === "VIRTUAL_GOODS") {
|
if (this.skuDetail.goodsType === "VIRTUAL_GOODS") {
|
||||||
params.cartType = "VIRTUAL";
|
params.cartType = "VIRTUAL";
|
||||||
|
} else if (this.isECouponGoods) {
|
||||||
|
params.cartType = resolveECouponCartType({
|
||||||
|
routeWay: this.$route.query.way,
|
||||||
|
action,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// E_COUPON 卡密商品:cartType 须保持 BUY_NOW,勿用 VIRTUAL(FR-B-01 / EC-24)
|
this[loadingKey] = true;
|
||||||
this.loading1 = true;
|
|
||||||
addCartGoods(params)
|
addCartGoods(params)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.loading1 = false;
|
this[loadingKey] = false;
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: "/pay",
|
path: "/pay",
|
||||||
@@ -564,7 +614,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.loading1 = false;
|
this[loadingKey] = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async collect() {
|
async collect() {
|
||||||
@@ -642,16 +692,20 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
promotion() {
|
promotion() {
|
||||||
this.promotionMap = {SECKILL: null, FULL_DISCOUNT: null, COUPON: []};
|
this.promotionMap = createEmptyPromotionMap();
|
||||||
// 格式化促销活动,返回当前促销的对象
|
if (!this.detail || !this.detail.promotionMap) return false;
|
||||||
if (!this.detail.promotionMap) return false;
|
const keysArr = Object.keys(this.detail.promotionMap);
|
||||||
let keysArr = Object.keys(this.detail.promotionMap);
|
|
||||||
if (keysArr.length === 0) return false;
|
if (keysArr.length === 0) return false;
|
||||||
|
|
||||||
for (let i = 0; i < keysArr.length; i++) {
|
for (let i = 0; i < keysArr.length; i++) {
|
||||||
let key = keysArr[i].split("-")[0];
|
const key = keysArr[i].split("-")[0];
|
||||||
if (key === "COUPON") {
|
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 {
|
} else {
|
||||||
this.promotionMap[key] = this.detail.promotionMap[keysArr[i]];
|
this.promotionMap[key] = this.detail.promotionMap[keysArr[i]];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* 卡密商品(E_COUPON / 电子卡券)— 买家端公共常量与工具
|
* 卡密商品(E_COUPON / 电子卡券)— 买家端公共常量与工具
|
||||||
*
|
*
|
||||||
* 与 VIRTUAL_GOODS(核销型虚拟)区分:E_COUPON 仅立即购买(BUY_NOW),
|
* 与 VIRTUAL_GOODS(核销型虚拟)区分:E_COUPON 常规促销走 BUY_NOW,
|
||||||
* 支付后自动发卡,订单终态 COMPLETED;可售库存优先读 poolStock。
|
* 特殊活动走 PINTUAN / POINTS / KANJIA;禁止 CART / VIRTUAL。
|
||||||
* 需求:lilishop/docs/requirements/card-key-goods-v4.md
|
* 需求:lilishop/docs/requirements/card-key-goods-v4.md · M-02
|
||||||
*
|
*
|
||||||
* @author Mike
|
* @author Mike
|
||||||
* @date 2026-07-31
|
* @date 2026-07-31
|
||||||
@@ -11,9 +11,15 @@
|
|||||||
export const E_COUPON_GOODS_TYPE = "E_COUPON";
|
export const E_COUPON_GOODS_TYPE = "E_COUPON";
|
||||||
export const E_COUPON_ORDER_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;
|
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) {
|
export function isECoupon(goodsType) {
|
||||||
return goodsType === E_COUPON_GOODS_TYPE;
|
return goodsType === E_COUPON_GOODS_TYPE;
|
||||||
}
|
}
|
||||||
@@ -22,13 +28,9 @@ export function isECouponOrder(orderType) {
|
|||||||
return orderType === E_COUPON_ORDER_TYPE;
|
return orderType === E_COUPON_ORDER_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 可售库存:优先 poolStock */
|
/** 可售库存:E_COUPON 读 SKU quantity(由卡池 syncSkuStock 同步) */
|
||||||
export function getECouponStock(sku) {
|
export function getECouponStock(sku) {
|
||||||
if (!sku) return 0;
|
if (!sku) return 0;
|
||||||
const pool = sku.poolStock;
|
|
||||||
if (pool !== undefined && pool !== null) {
|
|
||||||
return Number(pool) || 0;
|
|
||||||
}
|
|
||||||
return Number(sku.quantity) || 0;
|
return Number(sku.quantity) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,3 +39,167 @@ export function getECouponMaxBuyNum(sku) {
|
|||||||
if (stock <= 0) return 1;
|
if (stock <= 0) return 1;
|
||||||
return Math.min(stock, E_COUPON_MAX_BUY_NUM);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ import Promotion from "@/components/goodsDetail/Promotion";
|
|||||||
import Search from "@/components/Search";
|
import Search from "@/components/Search";
|
||||||
import * as APICart from "@/api/cart";
|
import * as APICart from "@/api/cart";
|
||||||
import * as APIMember from "@/api/member";
|
import * as APIMember from "@/api/member";
|
||||||
|
import { isECoupon } from "@/constants/goodsType";
|
||||||
export default {
|
export default {
|
||||||
name: "Cart",
|
name: "Cart",
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
@@ -407,11 +408,20 @@ export default {
|
|||||||
},
|
},
|
||||||
// 跳转支付页面
|
// 跳转支付页面
|
||||||
pay() {
|
pay() {
|
||||||
if (this.checkedNum) {
|
if (!this.checkedNum) {
|
||||||
this.$router.push({ path: "/pay", query: { way: "CART" } });
|
|
||||||
} else {
|
|
||||||
Message.warning("请至少选择一件商品");
|
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) {
|
showCoupon(storeId, index) {
|
||||||
|
|||||||
@@ -132,23 +132,39 @@
|
|||||||
</template>
|
</template>
|
||||||
<div v-else style="color: #999; margin-left: 5px">未开发票</div>
|
<div v-else style="color: #999; margin-left: 5px">未开发票</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 电子卡券:卡密信息(PC 内嵌) -->
|
<!-- 卡密信息(E_COUPON 主单或满赠子单,以 cardKeyFulfillStatus 为准) -->
|
||||||
<div class="order-card ecoupon-card-keys" v-if="isECouponOrder">
|
<div class="order-card ecoupon-card-keys" v-if="hasCardKeySection">
|
||||||
<h3>卡密信息</h3>
|
<h3>卡密信息</h3>
|
||||||
|
<div
|
||||||
|
v-for="line in cardKeyFulfillLines"
|
||||||
|
:key="line.key"
|
||||||
|
class="ecoupon-fulfill-block"
|
||||||
|
>
|
||||||
|
<div v-if="cardKeyFulfillLines.length > 1" class="ecoupon-fulfill-title">
|
||||||
|
{{ line.goodsName || (line.isGift ? "满赠电子卡券" : "电子卡券") }}
|
||||||
|
</div>
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="!ecouponCardKeyDelivered"
|
v-if="line.status !== 'DELIVERED'"
|
||||||
type="info"
|
:type="cardKeyFulfillAlertType(line.status)"
|
||||||
show-icon
|
show-icon
|
||||||
:closable="false"
|
:closable="false"
|
||||||
title="卡密尚未发放,请稍后刷新或联系商家"
|
:title="resolveCardKeyFulfillMessage(line)"
|
||||||
|
class="mb_10"
|
||||||
/>
|
/>
|
||||||
<template v-else-if="ecouponCardKeyRows.length">
|
<template v-else-if="line.cardKeys.length">
|
||||||
<div class="ecoupon-card-keys-mobile">
|
<div class="ecoupon-card-keys-mobile">
|
||||||
<el-button type="primary" @click="cardKeyDialogVisible = true">查看卡密</el-button>
|
<el-button type="primary" @click="cardKeyDialogVisible = true">查看卡密</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="ecoupon-card-keys-pc">
|
<div class="ecoupon-card-keys-pc">
|
||||||
<el-table border :data="ecouponCardKeyRows" style="width: 100%">
|
<el-table border :data="lineCardKeyRows(line)" style="width: 100%">
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
v-if="cardKeyFulfillLines.length > 1"
|
||||||
|
prop="goodsName"
|
||||||
|
label="商品"
|
||||||
|
min-width="120"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
<el-table-column prop="cardNo" 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="cardSecret" label="卡密" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="allocatedTime" label="发卡时间" width="170" />
|
<el-table-column prop="allocatedTime" label="发卡时间" width="170" />
|
||||||
@@ -158,10 +174,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-button class="mt_10" size="small" @click="copyAllCardKeys">复制全部卡密</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<el-button
|
||||||
|
v-if="ecouponCardKeyRows.length"
|
||||||
|
class="mt_10 ecoupon-card-keys-pc"
|
||||||
|
size="small"
|
||||||
|
@click="copyAllCardKeys"
|
||||||
|
>
|
||||||
|
复制全部卡密
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 订单商品 -->
|
<!-- 订单商品 -->
|
||||||
<div class="goods">
|
<div class="goods">
|
||||||
@@ -378,7 +402,14 @@ import {
|
|||||||
getPackage
|
getPackage
|
||||||
} from "@/api/order.js";
|
} from "@/api/order.js";
|
||||||
import { afterSaleReason, receiptDetail } from "@/api/member";
|
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)。
|
* 订单详情:E_COUPON 展示 orderItems[].cardKeys,屏蔽售后/物流(FR-B-03 / FR-B-04)。
|
||||||
@@ -415,20 +446,37 @@ export default {
|
|||||||
isNonPhysicalOrder() {
|
isNonPhysicalOrder() {
|
||||||
return this.isVirtualOrder || this.isECouponOrder;
|
return this.isVirtualOrder || this.isECouponOrder;
|
||||||
},
|
},
|
||||||
ecouponCardKeyDelivered() {
|
/** 是否展示卡密区块(含满赠 E_COUPON 子单 giftECouponOrders) */
|
||||||
return (this.order.orderItems || []).some((item) => item.cardKeyDelivered);
|
hasCardKeySection() {
|
||||||
|
return orderHasCardKeySection({
|
||||||
|
orderItems: this.order.orderItems,
|
||||||
|
giftSummaries: this.order.giftECouponOrders,
|
||||||
|
isECouponOrder: this.isECouponOrder,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cardKeyFulfillLines() {
|
||||||
|
return collectCardKeyFulfillLines(
|
||||||
|
this.order.orderItems,
|
||||||
|
this.order.giftECouponOrders
|
||||||
|
);
|
||||||
},
|
},
|
||||||
ecouponCardKeyRows() {
|
ecouponCardKeyRows() {
|
||||||
const rows = [];
|
return flattenOrderCardKeys(
|
||||||
(this.order.orderItems || []).forEach((item) => {
|
this.order.orderItems,
|
||||||
(item.cardKeys || []).forEach((ck) => {
|
false,
|
||||||
rows.push({ ...ck });
|
this.order.giftECouponOrders
|
||||||
});
|
);
|
||||||
});
|
|
||||||
return rows;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
cardKeyFulfillAlertType,
|
||||||
|
resolveCardKeyFulfillMessage,
|
||||||
|
lineCardKeyRows(line) {
|
||||||
|
return (line.cardKeys || []).map((ck) => ({
|
||||||
|
...ck,
|
||||||
|
goodsName: line.goodsName,
|
||||||
|
}));
|
||||||
|
},
|
||||||
copyCardKey(row) {
|
copyCardKey(row) {
|
||||||
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
|
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
|
||||||
this.copyText(text);
|
this.copyText(text);
|
||||||
@@ -767,6 +815,18 @@ table {
|
|||||||
display: none;
|
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 {
|
.ecoupon-card-keys-pc {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,8 +151,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 发票信息 -->
|
<!-- 发票信息(E_COUPON 虚拟型结算不开发票) -->
|
||||||
<div class="invoice">
|
<div class="invoice" v-if="!isECouponCheckout">
|
||||||
<div class="card-head mt_20 mb_20">
|
<div class="card-head mt_20 mb_20">
|
||||||
<span class="relative">发票信息<span class="inv-tips">
|
<span class="relative">发票信息<span class="inv-tips">
|
||||||
<el-icon><Warning /></el-icon>开企业抬头发票须填写纳税人识别号,以免影响报销
|
<el-icon><Warning /></el-icon>开企业抬头发票须填写纳税人识别号,以免影响报销
|
||||||
@@ -167,8 +167,8 @@
|
|||||||
<span @click="editInvoice">编辑</span>
|
<span @click="editInvoice">编辑</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 优惠券(E_COUPON 不参与促销 M-01) -->
|
<!-- 优惠券 -->
|
||||||
<div class="invoice" v-if="!isECouponCheckout">
|
<div class="invoice">
|
||||||
<div class="card-head mt_20 mb_20">
|
<div class="card-head mt_20 mb_20">
|
||||||
<span class="relative">优惠券</span>
|
<span class="relative">优惠券</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- 礼品卡 -->
|
<!-- 礼品卡 -->
|
||||||
<div class="invoice pay-gcc-module" v-if="!isECouponCheckout">
|
<div class="invoice pay-gcc-module">
|
||||||
<div class="pay-gcc-head">
|
<div class="pay-gcc-head">
|
||||||
<div class="pay-gcc-title">
|
<div class="pay-gcc-title">
|
||||||
使用礼品卡
|
使用礼品卡
|
||||||
@@ -284,13 +284,13 @@
|
|||||||
<!-- 底部支付栏 -->
|
<!-- 底部支付栏 -->
|
||||||
<div class="order-footer">
|
<div class="order-footer">
|
||||||
<div class="pay ml_20" @click="pay">提交订单</div>
|
<div class="pay ml_20" @click="pay">提交订单</div>
|
||||||
<div class="pay-address" v-if="addressList.length && selectedDeliverMethod === 'LOGISTICS'">
|
<div class="pay-address" v-if="!isVirtualLikeCheckout && addressList.length && selectedDeliverMethod === 'LOGISTICS'">
|
||||||
配送至:{{ $filters.unitAddress(selectedAddress.consigneeAddressPath) }}
|
配送至:{{ $filters.unitAddress(selectedAddress.consigneeAddressPath) }}
|
||||||
{{ selectedAddress.detail }} 收货人:{{
|
{{ selectedAddress.detail }} 收货人:{{
|
||||||
selectedAddress.name
|
selectedAddress.name
|
||||||
}} {{ selectedAddress.mobile }}
|
}} {{ selectedAddress.mobile }}
|
||||||
</div>
|
</div>
|
||||||
<div class="pay-address" v-if="addressList.length && selectedDeliverMethod === 'SELF_PICK_UP'">
|
<div class="pay-address" v-if="!isVirtualLikeCheckout && addressList.length && selectedDeliverMethod === 'SELF_PICK_UP'">
|
||||||
自提地点:{{selectedStoreAddress.address}} 联系方式:{{ selectedStoreAddress.mobile }}
|
自提地点:{{selectedStoreAddress.address}} 联系方式:{{ selectedStoreAddress.mobile }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -329,7 +329,7 @@ import {
|
|||||||
} from "@/api/cart";
|
} from "@/api/cart";
|
||||||
import { getStoreAddress } from "@/api/shopentry.js"
|
import { getStoreAddress } from "@/api/shopentry.js"
|
||||||
import { canUseCouponList } from "@/api/member.js";
|
import { canUseCouponList } from "@/api/member.js";
|
||||||
import { isECoupon } from "@/constants/goodsType";
|
import { isECoupon, shouldSkipPaymentPage } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Pay",
|
name: "Pay",
|
||||||
@@ -379,7 +379,7 @@ export default {
|
|||||||
);
|
);
|
||||||
return Number.isFinite(n) ? n : 0;
|
return Number.isFinite(n) ? n : 0;
|
||||||
},
|
},
|
||||||
/** 电子卡券结算:免地址、隐藏优惠券/礼品卡/发票(M-01 / O-11) */
|
/** 电子卡券结算:免地址、隐藏发票;优惠券/礼品卡/促销明细可用(M-02) */
|
||||||
isECouponCheckout() {
|
isECouponCheckout() {
|
||||||
return isECoupon(this.goodsType);
|
return isECoupon(this.goodsType);
|
||||||
},
|
},
|
||||||
@@ -855,15 +855,15 @@ export default {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
Spin.hide();
|
Spin.hide();
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
if (params.way === "POINTS") {
|
const way = params.way;
|
||||||
// 积分支付不需要跳转支付页面
|
if (shouldSkipPaymentPage(res.result, way)) {
|
||||||
this.$router.push("/payDone");
|
this.$router.push("/payDone");
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: "/payment",
|
path: "/payment",
|
||||||
query: { orderType: "TRADE", sn: res.result.sn },
|
query: { orderType: "TRADE", sn: res.result.sn },
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} else if (res.message) {
|
} else if (res.message) {
|
||||||
Message.warning(res.message);
|
Message.warning(res.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ export default {
|
|||||||
this.isStart = true
|
this.isStart = true
|
||||||
this.support = this.payDetail.support
|
this.support = this.payDetail.support
|
||||||
this.walletValue = this.payDetail.walletValue
|
this.walletValue = this.payDetail.walletValue
|
||||||
|
const price = Number(this.payDetail.price);
|
||||||
|
if (!Number.isNaN(price) && price <= 0) {
|
||||||
|
this.$router.replace('/payDone');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.$router.push({name: 'MyOrder'});
|
this.$router.push({name: 'MyOrder'});
|
||||||
|
|||||||
@@ -2,18 +2,14 @@
|
|||||||
<div style="background:#fff;">
|
<div style="background:#fff;">
|
||||||
<BaseHeader></BaseHeader>
|
<BaseHeader></BaseHeader>
|
||||||
<Search style="border-bottom:2px solid red;"></Search>
|
<Search style="border-bottom:2px solid red;"></Search>
|
||||||
<!-- <drawer></drawer> -->
|
|
||||||
<div class="base-width cate-container">
|
<div class="base-width cate-container">
|
||||||
<el-breadcrumb>
|
<el-breadcrumb>
|
||||||
<el-breadcrumb-item to="/">首页</el-breadcrumb-item>
|
<el-breadcrumb-item to="/">首页</el-breadcrumb-item>
|
||||||
<el-breadcrumb-item>{{ goodsMsg.pointsGoodsCategoryName }}</el-breadcrumb-item>
|
<el-breadcrumb-item>{{ goodsMsg.pointsGoodsCategoryName }}</el-breadcrumb-item>
|
||||||
</el-breadcrumb>
|
</el-breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<!-- 商品信息展示 -->
|
|
||||||
<div class="item-detail-show">
|
<div class="item-detail-show">
|
||||||
<!-- 详情左侧展示数据、图片,收藏、举报 -->
|
|
||||||
<div class="item-detail-left">
|
<div class="item-detail-left">
|
||||||
<!-- 大图、放大镜 -->
|
|
||||||
<div class="item-detail-big-img">
|
<div class="item-detail-big-img">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="goodsSku.thumbnail"
|
v-if="goodsSku.thumbnail"
|
||||||
@@ -29,18 +25,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧商品信息、活动信息、操作展示 -->
|
|
||||||
<div class="item-detail-right">
|
<div class="item-detail-right">
|
||||||
<div class="item-detail-title">
|
<div class="item-detail-title">
|
||||||
|
<div class="item-detail-title-row">
|
||||||
<p>{{ goodsSku.goodsName }}</p>
|
<p>{{ goodsSku.goodsName }}</p>
|
||||||
|
<el-tag v-if="isECouponSku" class="goods-type-tag" size="small">电子卡券</el-tag>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sell-point">
|
<div class="sell-point">
|
||||||
{{ goodsSku.sellingPoint }}
|
{{ goodsSku.sellingPoint }}
|
||||||
</div>
|
</div>
|
||||||
<!-- 商品详细 -->
|
|
||||||
<div class="item-detail-price-row">
|
<div class="item-detail-price-row">
|
||||||
<div class="item-price-left">
|
<div class="item-price-left">
|
||||||
<!-- 商品原价 -->
|
|
||||||
<div class="item-price-row">
|
<div class="item-price-row">
|
||||||
<p>
|
<p>
|
||||||
<span class="item-price-title">积 分</span>
|
<span class="item-price-title">积 分</span>
|
||||||
@@ -55,11 +51,19 @@
|
|||||||
<p>数量</p>
|
<p>数量</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-select-row">
|
<div class="item-select-row">
|
||||||
<el-input-number :min="1" :disabled="goodsSku.quantity === 0" v-model="count"></el-input-number>
|
<el-input-number
|
||||||
<span class="inventory"> 库存{{goodsSku.quantity}}</span>
|
:min="1"
|
||||||
|
:max="quantityMax"
|
||||||
|
:disabled="isOutOfStock"
|
||||||
|
v-model="count"
|
||||||
|
/>
|
||||||
|
<span class="inventory"> 库存{{ displayStock }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-select" v-if="goodsSku.goodsType !== 'VIRTUAL_GOODS' && goodsSku.weight !== 0">
|
<div
|
||||||
|
class="item-select"
|
||||||
|
v-if="!isECouponSku && goodsSku.goodsType !== 'VIRTUAL_GOODS' && goodsSku.weight !== 0"
|
||||||
|
>
|
||||||
<div class="item-select-title">
|
<div class="item-select-title">
|
||||||
<p>重量</p>
|
<p>重量</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,12 +72,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="add-buy-car">
|
<div class="add-buy-car">
|
||||||
<el-button type="danger" :loading="loading" :disabled="goodsSku.quantity === 0" @click="pointBuy">积分购买</el-button>
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="isOutOfStock"
|
||||||
|
@click="pointBuy"
|
||||||
|
>积分购买</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 商品详情 -->
|
|
||||||
<div class="base-width item-intro" ref="itemIntroGoods">
|
<div class="base-width item-intro" ref="itemIntroGoods">
|
||||||
<div>商品介绍</div>
|
<div>商品介绍</div>
|
||||||
<div v-html="goodsSku.intro" class="mt_10 ml_10" v-if="goodsSku.intro"></div>
|
<div v-html="goodsSku.intro" class="mt_10 ml_10" v-if="goodsSku.intro"></div>
|
||||||
@@ -86,11 +94,19 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import Search from '@/components/Search';
|
import Search from "@/components/Search";
|
||||||
import { addCartGoods } from '@/api/cart.js';
|
import { addCartGoods } from "@/api/cart.js";
|
||||||
import { pointGoodsDetail } from '@/api/promotion';
|
import { pointGoodsDetail } from "@/api/promotion";
|
||||||
|
import {
|
||||||
|
isECoupon,
|
||||||
|
getECouponStock,
|
||||||
|
getECouponMaxBuyNum,
|
||||||
|
E_COUPON_CART_POINTS,
|
||||||
|
} from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PointGoodsDetail',
|
name: "PointGoodsDetail",
|
||||||
|
components: { Search },
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
next();
|
next();
|
||||||
@@ -99,74 +115,106 @@ export default {
|
|||||||
this.getGoodsDetail();
|
this.getGoodsDetail();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('scroll', this.handleScroll)
|
window.addEventListener("scroll", this.handleScroll);
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
goodsMsg: {}, // 商品信息
|
goodsMsg: {},
|
||||||
goodsSku: {}, // 商品sku
|
goodsSku: {},
|
||||||
isLoading: false, // 加载状态
|
isLoading: false,
|
||||||
categoryBar: [], // 分类
|
onceFlag: true,
|
||||||
onceFlag: true, // 只调用一次
|
count: 1,
|
||||||
count: 1, // 购买商品数量
|
loading: false,
|
||||||
loading: false // 提交加载状态
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
isECouponSku() {
|
||||||
|
return isECoupon(this.goodsSku.goodsType);
|
||||||
|
},
|
||||||
|
displayStock() {
|
||||||
|
if (this.isECouponSku) {
|
||||||
|
return getECouponStock(this.goodsSku);
|
||||||
|
}
|
||||||
|
return Number(this.goodsSku.quantity) || 0;
|
||||||
|
},
|
||||||
|
quantityMax() {
|
||||||
|
if (this.isECouponSku) {
|
||||||
|
return getECouponMaxBuyNum(this.goodsSku);
|
||||||
|
}
|
||||||
|
const qty = Number(this.goodsSku.quantity) || 0;
|
||||||
|
return qty > 0 ? qty : 1;
|
||||||
|
},
|
||||||
|
isOutOfStock() {
|
||||||
|
return this.displayStock <= 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 获取积分商品详情
|
|
||||||
getGoodsDetail() {
|
getGoodsDetail() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
pointGoodsDetail(this.$route.query.id).then((res) => {
|
pointGoodsDetail(this.$route.query.id)
|
||||||
|
.then((res) => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.goodsMsg = res.result;
|
this.goodsMsg = res.result;
|
||||||
this.goodsSku = res.result.goodsSku
|
this.goodsSku = res.result.goodsSku || {};
|
||||||
|
this.syncCountWithQuantity();
|
||||||
} else {
|
} else {
|
||||||
Message.error(res.message)
|
Message.error(res.message);
|
||||||
this.$router.push('/')
|
this.$router.push("/");
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
})
|
||||||
this.$router.push('/')
|
.catch(() => {
|
||||||
|
this.$router.push("/");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
syncCountWithQuantity() {
|
||||||
|
const max = this.quantityMax;
|
||||||
|
if (max <= 0) {
|
||||||
|
this.count = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.count > max) this.count = max;
|
||||||
|
if (this.count < 1) this.count = 1;
|
||||||
|
},
|
||||||
pointBuy() {
|
pointBuy() {
|
||||||
const params = {
|
const params = {
|
||||||
num: this.count,
|
num: this.count,
|
||||||
skuId: this.goodsMsg.skuId,
|
skuId: this.goodsMsg.skuId,
|
||||||
cartType: 'POINTS'
|
cartType: E_COUPON_CART_POINTS,
|
||||||
};
|
};
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
addCartGoods(params).then(res => {
|
addCartGoods(params)
|
||||||
|
.then((res) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.$router.push({path: '/pay', query: {way: params.cartType}});
|
this.$router.push({ path: "/pay", query: { way: params.cartType } });
|
||||||
} else {
|
} else {
|
||||||
Message.warning(res.message);
|
Message.warning(res.message);
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
})
|
||||||
|
.catch(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleScroll () { // 监听页面滚动
|
handleScroll() {
|
||||||
if (this.onceFlag) {
|
if (this.onceFlag) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.changeHeight()
|
this.changeHeight();
|
||||||
});
|
});
|
||||||
this.onceFlag = false
|
this.onceFlag = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
changeHeight () { // 设置商品详情高度
|
changeHeight() {
|
||||||
let goodsDetailCon = document.querySelector('.item-intro')
|
const goodsDetailCon = document.querySelector(".item-intro");
|
||||||
|
if (!goodsDetailCon || !this.$refs.itemIntroGoods) return;
|
||||||
let heightCss = window.getComputedStyle(goodsDetailCon).height;
|
let heightCss = window.getComputedStyle(goodsDetailCon).height;
|
||||||
heightCss = parseInt(heightCss.substr(0, heightCss.length - 2)) + 89;
|
heightCss = parseInt(heightCss.substr(0, heightCss.length - 2), 10) + 89;
|
||||||
this.$refs.itemIntroGoods.style.height = heightCss + 'px';
|
this.$refs.itemIntroGoods.style.height = heightCss + "px";
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
components: {
|
|
||||||
Search,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.base-width {
|
.base-width {
|
||||||
width: 1200px;
|
width: 1200px;
|
||||||
@@ -180,7 +228,6 @@ export default {
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
// 商品图片,价格等
|
|
||||||
.item-detail-show {
|
.item-detail-show {
|
||||||
width: 1200px;
|
width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -192,7 +239,6 @@ export default {
|
|||||||
width: 350px;
|
width: 350px;
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-detail-big-img {
|
.item-detail-big-img {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
height: 350px;
|
height: 350px;
|
||||||
@@ -202,12 +248,10 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-detail-img-row {
|
.item-detail-img-row {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-detail-img-small {
|
.item-detail-img-small {
|
||||||
width: 68px;
|
width: 68px;
|
||||||
height: 68px;
|
height: 68px;
|
||||||
@@ -219,20 +263,27 @@ export default {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*商品选购详情*/
|
|
||||||
.item-detail-right {
|
.item-detail-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
.item-detail-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
.item-detail-title p {
|
.item-detail-title p {
|
||||||
@include content_color($light_content_color);
|
@include content_color($light_content_color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.goods-type-tag {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-detail-express {
|
.item-detail-express {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 2px 3px;
|
padding: 2px 3px;
|
||||||
@@ -240,15 +291,6 @@ export default {
|
|||||||
background-color: $theme_color;
|
background-color: $theme_color;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*商品标签*/
|
|
||||||
.item-detail-tag {
|
|
||||||
padding: 8px 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: $theme_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*价格详情等*/
|
|
||||||
.item-detail-price-row {
|
.item-detail-price-row {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -256,39 +298,28 @@ export default {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: url("../../assets/images/goodsDetail/price-bg.png");
|
background: url("../../assets/images/goodsDetail/price-bg.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-price-left {
|
.item-price-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-price-title {
|
.item-price-title {
|
||||||
color: #999999;
|
color: #999999;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-price-row {
|
.item-price-row {
|
||||||
margin: 5px 0px;
|
margin: 5px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-price {
|
.item-price {
|
||||||
color: $theme_color;
|
color: $theme_color;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.item-price-old {
|
|
||||||
color: gray;
|
|
||||||
text-decoration: line-through;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
.add-buy-car-box {
|
.add-buy-car-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
border-top: 1px dotted $border_color;
|
border-top: 1px dotted $border_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-buy-car {
|
.add-buy-car {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
> * {
|
> * {
|
||||||
@@ -300,54 +331,20 @@ export default {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-select-title {
|
.item-select-title {
|
||||||
@include content_color($light_content_color);
|
@include content_color($light_content_color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-select-column {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-select-row {
|
.item-select-row {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-select-box {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
padding: 5px;
|
|
||||||
margin-right: 8px;
|
|
||||||
@include background_color($light_background_color);
|
|
||||||
border: 0.5px solid $border_color;
|
|
||||||
cursor: pointer;
|
|
||||||
@include content_color($light_content_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-select-box:hover {
|
|
||||||
border: 0.5px solid $theme_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-select-box-active {
|
|
||||||
border: 0.5px solid $theme_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-select-intro p {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
.sell-point {
|
.sell-point {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: red;
|
color: red;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
// 商品详情
|
|
||||||
.item-intro {
|
.item-intro {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
> div:nth-child(1) {
|
> div:nth-child(1) {
|
||||||
|
|||||||
@@ -45,7 +45,15 @@
|
|||||||
<div class="wap-sku">
|
<div class="wap-sku">
|
||||||
{{ item.goodsUnit }}
|
{{ item.goodsUnit }}
|
||||||
<el-tag
|
<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'"
|
:type="item.salesModel === 'RETAIL' ? 'info' : 'primary'"
|
||||||
>
|
>
|
||||||
{{ item.salesModel === "RETAIL" ? "零售型" : "批发型" }}
|
{{ item.salesModel === "RETAIL" ? "零售型" : "批发型" }}
|
||||||
@@ -53,6 +61,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="wap-content-desc-bottom">
|
<div class="wap-content-desc-bottom">
|
||||||
<div>¥{{ $filters.unitPrice(item.price) }}</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,6 +86,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import * as API_Goods from "@/api/goods";
|
import * as API_Goods from "@/api/goods";
|
||||||
|
import { goodsTypeLabel, goodsTypeTagType } from "@/constants/goodsType";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
selectedWay: {
|
selectedWay: {
|
||||||
@@ -129,6 +141,8 @@ export default {
|
|||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
goodsTypeLabel,
|
||||||
|
goodsTypeTagType,
|
||||||
onSearchGoods() {
|
onSearchGoods() {
|
||||||
this.goodsData = [];
|
this.goodsData = [];
|
||||||
this.goodsParams.pageNumber = 1;
|
this.goodsParams.pageNumber = 1;
|
||||||
@@ -270,7 +284,9 @@ export default {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.goods-dialog-cascader-popper {
|
.pool-stock-hint {
|
||||||
z-index: 10001 !important;
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
export const CARD_KEY_STATUS_TEXT = {
|
export const CARD_KEY_STATUS_TEXT = {
|
||||||
UNUSED: "未使用",
|
UNUSED: "未使用",
|
||||||
ALLOCATED: "已分配",
|
ALLOCATED: "已分配",
|
||||||
|
RESERVED: "已预占",
|
||||||
VOIDED: "已作废",
|
VOIDED: "已作废",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
35
manager/src/constants/goodsType.js
Normal file
35
manager/src/constants/goodsType.js
Normal 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;
|
||||||
|
}
|
||||||
@@ -105,6 +105,7 @@
|
|||||||
import { saveKanJiaActivityGoods } from "@/api/promotion";
|
import { saveKanJiaActivityGoods } from "@/api/promotion";
|
||||||
import { regular } from "@/utils";
|
import { regular } from "@/utils";
|
||||||
import skuSelect from "@/components/lili-dialog";
|
import skuSelect from "@/components/lili-dialog";
|
||||||
|
import { isECoupon } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "add-kanjia-activity-goods",
|
name: "add-kanjia-activity-goods",
|
||||||
@@ -175,6 +176,7 @@ export default {
|
|||||||
|
|
||||||
let checkResult = true;
|
let checkResult = true;
|
||||||
this.form.promotionGoodsList.forEach((res) => {
|
this.form.promotionGoodsList.forEach((res) => {
|
||||||
|
const eCoupon = isECoupon(res.goodsType);
|
||||||
if (res.stock <= 0 || res.stock > res.quantity) {
|
if (res.stock <= 0 || res.stock > res.quantity) {
|
||||||
checkResult = false;
|
checkResult = false;
|
||||||
this.$Message.error("活动库存不能为0且不能超过商品库存");
|
this.$Message.error("活动库存不能为0且不能超过商品库存");
|
||||||
@@ -186,9 +188,16 @@ export default {
|
|||||||
this.$Message.error("结算价格金额格式不正确");
|
this.$Message.error("结算价格金额格式不正确");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.settlementPrice < 0 || res.settlementPrice > res.price) {
|
if (
|
||||||
|
(eCoupon ? res.settlementPrice < 0 : res.settlementPrice <= 0) ||
|
||||||
|
res.settlementPrice > res.price
|
||||||
|
) {
|
||||||
checkResult = false;
|
checkResult = false;
|
||||||
this.$Message.error("结算价格金额不能为0且不能超过商品价格");
|
this.$Message.error(
|
||||||
|
eCoupon
|
||||||
|
? "结算价格不能小于0且不能超过商品价格"
|
||||||
|
: "结算价格金额不能为0且不能超过商品价格"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!regular.money.test(res.highestPrice)) {
|
if (!regular.money.test(res.highestPrice)) {
|
||||||
@@ -206,9 +215,9 @@ export default {
|
|||||||
this.$Message.error("最低可砍金额格式错误");
|
this.$Message.error("最低可砍金额格式错误");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.lowestPrice <= 0 || res.lowestPrice > res.price) {
|
if (res.lowestPrice < 0 || (!eCoupon && res.lowestPrice <= 0) || res.lowestPrice > res.price) {
|
||||||
checkResult = false;
|
checkResult = false;
|
||||||
this.$Message.error("最低可砍金额不能为0");
|
this.$Message.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (parseInt(res.lowestPrice) > parseInt(res.highestPrice)) {
|
if (parseInt(res.lowestPrice) > parseInt(res.highestPrice)) {
|
||||||
@@ -277,6 +286,7 @@ export default {
|
|||||||
storeId: e.storeId,
|
storeId: e.storeId,
|
||||||
storeName: e.storeName,
|
storeName: e.storeName,
|
||||||
skuId: e.id,
|
skuId: e.id,
|
||||||
|
goodsType: e.goodsType,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.form.promotionGoodsList = list;
|
this.form.promotionGoodsList = list;
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getKanJiaActivityGoodsById, editKanJiaActivityGoods } from "@/api/promotion";
|
import { getKanJiaActivityGoodsById, editKanJiaActivityGoods } from "@/api/promotion";
|
||||||
import { regular } from "@/utils";
|
import { regular } from "@/utils";
|
||||||
|
import { isECoupon } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "editKanjiaActivityGoods",
|
name: "editKanjiaActivityGoods",
|
||||||
@@ -200,6 +201,8 @@ export default {
|
|||||||
params.endTime = this.$filters.unixToDate(endDate.getTime() / 1000);
|
params.endTime = this.$filters.unixToDate(endDate.getTime() / 1000);
|
||||||
delete params.rangeTime;
|
delete params.rangeTime;
|
||||||
|
|
||||||
|
const eCoupon = isECoupon(params.goodsType || params.goodsSku?.goodsType);
|
||||||
|
|
||||||
if (params.stock <= 0 || params.stock > params.goodsSku.quantity) {
|
if (params.stock <= 0 || params.stock > params.goodsSku.quantity) {
|
||||||
this.$Message.error("活动库存不能为0且不能超过商品库存");
|
this.$Message.error("活动库存不能为0且不能超过商品库存");
|
||||||
return;
|
return;
|
||||||
@@ -208,8 +211,15 @@ export default {
|
|||||||
this.$Message.error("结算价格金额格式不正确");
|
this.$Message.error("结算价格金额格式不正确");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (params.settlementPrice < 0 || params.settlementPrice > params.price) {
|
if (
|
||||||
this.$Message.error("结算价格金额不能为0且不能超过商品价格");
|
(eCoupon ? params.settlementPrice < 0 : params.settlementPrice <= 0) ||
|
||||||
|
params.settlementPrice > params.price
|
||||||
|
) {
|
||||||
|
this.$Message.error(
|
||||||
|
eCoupon
|
||||||
|
? "结算价格不能小于0且不能超过商品价格"
|
||||||
|
: "结算价格金额不能为0且不能超过商品价格"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!regular.money.test(params.highestPrice)) {
|
if (!regular.money.test(params.highestPrice)) {
|
||||||
@@ -224,8 +234,12 @@ export default {
|
|||||||
this.$Message.error("最低可砍金额格式错误");
|
this.$Message.error("最低可砍金额格式错误");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (params.lowestPrice <= 0 || params.lowestPrice > params.price) {
|
if (
|
||||||
this.$Message.error("最低可砍金额不能为0");
|
params.lowestPrice < 0 ||
|
||||||
|
(!eCoupon && params.lowestPrice <= 0) ||
|
||||||
|
params.lowestPrice > params.price
|
||||||
|
) {
|
||||||
|
this.$Message.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (params.lowestPrice > params.highestPrice) {
|
if (params.lowestPrice > params.highestPrice) {
|
||||||
|
|||||||
@@ -23,6 +23,11 @@
|
|||||||
>
|
>
|
||||||
<el-table-column type="selection" width="60" align="center" />
|
<el-table-column type="selection" width="60" align="center" />
|
||||||
<el-table-column prop="goodsName" label="商品名称" min-width="120" show-overflow-tooltip />
|
<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">
|
<el-table-column label="SKU编码" min-width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row">{{ row.skuId }}</span>
|
<span v-if="row">{{ row.skuId }}</span>
|
||||||
@@ -117,6 +122,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { addPointsGoods, getPointsGoodsCategoryList } from "@/api/promotion";
|
import { addPointsGoods, getPointsGoodsCategoryList } from "@/api/promotion";
|
||||||
import skuSelect from "@/components/lili-dialog";
|
import skuSelect from "@/components/lili-dialog";
|
||||||
|
import { isECoupon, goodsTypeLabel } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "addPoinsGoods",
|
name: "addPoinsGoods",
|
||||||
@@ -160,6 +166,7 @@ export default {
|
|||||||
await this.getCategory();
|
await this.getCategory();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
goodsTypeLabel,
|
||||||
async getCategory() {
|
async getCategory() {
|
||||||
let res = await getPointsGoodsCategoryList();
|
let res = await getPointsGoodsCategoryList();
|
||||||
this.categoryList = res.result.records;
|
this.categoryList = res.result.records;
|
||||||
@@ -172,6 +179,13 @@ export default {
|
|||||||
this.$Modal.warning({ title: "提示", content: "请选择指定商品" });
|
this.$Modal.warning({ title: "提示", content: "请选择指定商品" });
|
||||||
return;
|
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.startTime = this.form.rangeTime[0];
|
||||||
this.form.endTime = this.form.rangeTime[1];
|
this.form.endTime = this.form.rangeTime[1];
|
||||||
const start = this.$filters.unixToDate(this.form.startTime / 1000);
|
const start = this.$filters.unixToDate(this.form.startTime / 1000);
|
||||||
@@ -251,6 +265,7 @@ export default {
|
|||||||
quantity: e.quantity || "",
|
quantity: e.quantity || "",
|
||||||
storeName: e.storeName || "",
|
storeName: e.storeName || "",
|
||||||
price: e.price || "",
|
price: e.price || "",
|
||||||
|
goodsType: e.goodsType,
|
||||||
};
|
};
|
||||||
list.push(obj);
|
list.push(obj);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,18 +11,21 @@
|
|||||||
export const CARD_KEY_STATUS = {
|
export const CARD_KEY_STATUS = {
|
||||||
UNUSED: "UNUSED",
|
UNUSED: "UNUSED",
|
||||||
ALLOCATED: "ALLOCATED",
|
ALLOCATED: "ALLOCATED",
|
||||||
|
RESERVED: "RESERVED",
|
||||||
VOIDED: "VOIDED",
|
VOIDED: "VOIDED",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CARD_KEY_STATUS_TEXT = {
|
export const CARD_KEY_STATUS_TEXT = {
|
||||||
UNUSED: "未使用",
|
UNUSED: "未使用",
|
||||||
ALLOCATED: "已分配",
|
ALLOCATED: "已分配",
|
||||||
|
RESERVED: "已预占",
|
||||||
VOIDED: "已作废",
|
VOIDED: "已作废",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CARD_KEY_STATUS_TAG = {
|
export const CARD_KEY_STATUS_TAG = {
|
||||||
UNUSED: "success",
|
UNUSED: "success",
|
||||||
ALLOCATED: "info",
|
ALLOCATED: "info",
|
||||||
|
RESERVED: "warning",
|
||||||
VOIDED: "danger",
|
VOIDED: "danger",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,6 +33,7 @@ export const CARD_KEY_STATUS_TAG = {
|
|||||||
export const CARD_KEY_STATUS_TABS = [
|
export const CARD_KEY_STATUS_TABS = [
|
||||||
{ key: "", label: "全部" },
|
{ key: "", label: "全部" },
|
||||||
{ key: CARD_KEY_STATUS.UNUSED, label: "未使用" },
|
{ key: CARD_KEY_STATUS.UNUSED, label: "未使用" },
|
||||||
|
{ key: CARD_KEY_STATUS.RESERVED, label: "已预占" },
|
||||||
{ key: CARD_KEY_STATUS.ALLOCATED, label: "已分配" },
|
{ key: CARD_KEY_STATUS.ALLOCATED, label: "已分配" },
|
||||||
{ key: CARD_KEY_STATUS.VOIDED, label: "已作废" },
|
{ key: CARD_KEY_STATUS.VOIDED, label: "已作废" },
|
||||||
];
|
];
|
||||||
|
|||||||
152
seller/src/constants/goodsType.js
Normal file
152
seller/src/constants/goodsType.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* 卡密商品(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 "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** E_COUPON 活动库存提示 */
|
||||||
|
export function eCouponStockHint(goodsType) {
|
||||||
|
if (!isECoupon(goodsType)) return "";
|
||||||
|
return "库存来自卡池同步(quantity)";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 活动价下限:E_COUPON 允许 0 元 */
|
||||||
|
export function promotionPriceMin(goodsType) {
|
||||||
|
return isECoupon(goodsType) ? 0 : 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 活动价下限:E_COUPON 允许 0 元 */
|
||||||
|
export function promotionPriceMin(goodsType) {
|
||||||
|
return isECoupon(goodsType) ? 0 : 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 订单是否含已交付卡密(含满赠子单) */
|
||||||
|
export function orderHasCardKeyContent(orderItems, giftSummaries) {
|
||||||
|
return flattenCardKeyFulfillLines(collectCardKeyFulfillLines(orderItems, giftSummaries)).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -116,6 +116,7 @@
|
|||||||
>
|
>
|
||||||
作废
|
作废
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<span v-else-if="row && row.status === 'RESERVED'" class="text-muted">预占中</span>
|
||||||
<span v-else>—</span>
|
<span v-else>—</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -257,10 +258,14 @@ export default {
|
|||||||
const withCount = (label, count) =>
|
const withCount = (label, count) =>
|
||||||
count != null && this.stats ? `${label}(${count})` : label;
|
count != null && this.stats ? `${label}(${count})` : label;
|
||||||
const total =
|
const total =
|
||||||
(s.unusedCount || 0) + (s.allocatedCount || 0) + (s.voidedCount || 0);
|
(s.unusedCount || 0) +
|
||||||
|
(s.reservedCount || 0) +
|
||||||
|
(s.allocatedCount || 0) +
|
||||||
|
(s.voidedCount || 0);
|
||||||
return [
|
return [
|
||||||
{ title: withCount("全部", total), value: "ALL" },
|
{ title: withCount("全部", total), value: "ALL" },
|
||||||
{ title: withCount("未使用", s.unusedCount || 0), value: "UNUSED" },
|
{ title: withCount("未使用", s.unusedCount || 0), value: "UNUSED" },
|
||||||
|
{ title: withCount("已预占", s.reservedCount || 0), value: "RESERVED" },
|
||||||
{ title: withCount("已分配", s.allocatedCount || 0), value: "ALLOCATED" },
|
{ title: withCount("已分配", s.allocatedCount || 0), value: "ALLOCATED" },
|
||||||
{ title: withCount("已作废", s.voidedCount || 0), value: "VOIDED" },
|
{ title: withCount("已作废", s.voidedCount || 0), value: "VOIDED" },
|
||||||
];
|
];
|
||||||
@@ -482,4 +487,9 @@ export default {
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -346,9 +346,9 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="col.slot === 'quantity' && isECouponGoods"
|
v-else-if="col.slot === 'quantity' && isECouponGoods"
|
||||||
:model-value="resolvePoolStockDisplay(row)"
|
:model-value="row.quantity ?? 0"
|
||||||
disabled
|
disabled
|
||||||
placeholder="卡池可用数"
|
placeholder="库存"
|
||||||
>
|
>
|
||||||
<template #append>{{ baseInfoForm.goodsUnit || "" }}</template>
|
<template #append>{{ baseInfoForm.goodsUnit || "" }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
@@ -561,7 +561,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
/** 卡密商品:库存只读展示 poolStock,提交时 quantity 传 0(FR-S-01 / P-02) */
|
/** 卡密商品:库存只读展示 quantity,提交时 quantity 传 0(FR-S-01 / P-02) */
|
||||||
isECouponGoods() {
|
isECouponGoods() {
|
||||||
return this.baseInfoForm.goodsType === "E_COUPON";
|
return this.baseInfoForm.goodsType === "E_COUPON";
|
||||||
},
|
},
|
||||||
@@ -783,17 +783,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** E_COUPON 库存列只读展示,无卡密时显示 0 */
|
/** E_COUPON 提交固定传 0,真实库存由卡池 syncSkuStock 同步至 quantity */
|
||||||
resolvePoolStockDisplay(row) {
|
|
||||||
if (row.poolStock != null && row.poolStock !== "") {
|
|
||||||
return row.poolStock;
|
|
||||||
}
|
|
||||||
if (row.quantity != null && row.quantity !== "") {
|
|
||||||
return row.quantity;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
/** E_COUPON 提交固定传 0,真实库存由卡池同步 */
|
|
||||||
resolveSubmitQuantity(sku) {
|
resolveSubmitQuantity(sku) {
|
||||||
if (this.isECouponGoods) {
|
if (this.isECouponGoods) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1338,7 +1328,6 @@ export default {
|
|||||||
price: e.price,
|
price: e.price,
|
||||||
// cost: e.cost,
|
// cost: e.cost,
|
||||||
quantity: e.quantity,
|
quantity: e.quantity,
|
||||||
poolStock: e.poolStock,
|
|
||||||
// alertQuantity: e.alertQuantity,
|
// alertQuantity: e.alertQuantity,
|
||||||
weight: e.weight,
|
weight: e.weight,
|
||||||
};
|
};
|
||||||
@@ -1809,9 +1798,8 @@ export default {
|
|||||||
id: existingCombination.id || "",
|
id: existingCombination.id || "",
|
||||||
sn: existingCombination.sn || "",
|
sn: existingCombination.sn || "",
|
||||||
quantity: this.isECouponGoods
|
quantity: this.isECouponGoods
|
||||||
? this.resolvePoolStockDisplay(existingCombination)
|
? (existingCombination.quantity ?? 0)
|
||||||
: (existingCombination.quantity || ""),
|
: (existingCombination.quantity || ""),
|
||||||
poolStock: existingCombination.poolStock,
|
|
||||||
cost: existingCombination.cost || "",
|
cost: existingCombination.cost || "",
|
||||||
price: existingCombination.price || "",
|
price: existingCombination.price || "",
|
||||||
weight: existingCombination.weight || ""
|
weight: existingCombination.weight || ""
|
||||||
@@ -1870,9 +1858,8 @@ export default {
|
|||||||
id: existingCombination.id || "",
|
id: existingCombination.id || "",
|
||||||
sn: existingCombination.sn || "",
|
sn: existingCombination.sn || "",
|
||||||
quantity: this.isECouponGoods
|
quantity: this.isECouponGoods
|
||||||
? this.resolvePoolStockDisplay(existingCombination)
|
? (existingCombination.quantity ?? 0)
|
||||||
: (existingCombination.quantity || ""),
|
: (existingCombination.quantity || ""),
|
||||||
poolStock: existingCombination.poolStock,
|
|
||||||
cost: existingCombination.cost || "",
|
cost: existingCombination.cost || "",
|
||||||
price: existingCombination.price || "",
|
price: existingCombination.price || "",
|
||||||
weight: existingCombination.weight || ""
|
weight: existingCombination.weight || ""
|
||||||
@@ -1939,7 +1926,7 @@ export default {
|
|||||||
}
|
}
|
||||||
pushData.push(
|
pushData.push(
|
||||||
{
|
{
|
||||||
title: this.isECouponGoods ? "卡池可用数" : "库存",
|
title: "库存",
|
||||||
slot: "quantity",
|
slot: "quantity",
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@@ -1975,9 +1962,8 @@ export default {
|
|||||||
id: existingCombination.id || "",
|
id: existingCombination.id || "",
|
||||||
sn: existingCombination.sn || "",
|
sn: existingCombination.sn || "",
|
||||||
quantity: this.isECouponGoods
|
quantity: this.isECouponGoods
|
||||||
? this.resolvePoolStockDisplay(existingCombination)
|
? (existingCombination.quantity ?? 0)
|
||||||
: (existingCombination.quantity || ""),
|
: (existingCombination.quantity || ""),
|
||||||
poolStock: existingCombination.poolStock,
|
|
||||||
cost: existingCombination.cost || "",
|
cost: existingCombination.cost || "",
|
||||||
price: existingCombination.price || (this.baseInfoForm.salesModel === 'WHOLESALE' && this.wholesaleData.length > 0 ? this.wholesaleData[0].price : ""),
|
price: existingCombination.price || (this.baseInfoForm.salesModel === 'WHOLESALE' && this.wholesaleData.length > 0 ? this.wholesaleData[0].price : ""),
|
||||||
weight: existingCombination.weight || ""
|
weight: existingCombination.weight || ""
|
||||||
|
|||||||
@@ -41,10 +41,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="wap-content-desc">
|
<div class="wap-content-desc">
|
||||||
<div class="wap-content-desc-title">{{ item.goodsName }}</div>
|
<div class="wap-content-desc-title">{{ item.goodsName }}</div>
|
||||||
|
|
||||||
<div class="wap-sku">
|
<div class="wap-sku">
|
||||||
{{ item.goodsUnit }}
|
{{ item.goodsUnit }}
|
||||||
<el-tag
|
<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'"
|
:type="item.salesModel === 'RETAIL' ? 'info' : 'primary'"
|
||||||
>
|
>
|
||||||
{{ item.salesModel === "RETAIL" ? "零售型" : "批发型" }}
|
{{ item.salesModel === "RETAIL" ? "零售型" : "批发型" }}
|
||||||
@@ -52,6 +61,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="wap-content-desc-bottom">
|
<div class="wap-content-desc-bottom">
|
||||||
<div>¥{{ $filters.unitPrice(item.price) }}</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -73,6 +85,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import * as API_Goods from "@/api/goods";
|
import * as API_Goods from "@/api/goods";
|
||||||
|
import { goodsTypeLabel, goodsTypeTagType } from "@/constants/goodsType";
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
selectedWay: {
|
selectedWay: {
|
||||||
@@ -127,6 +140,8 @@ export default {
|
|||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
goodsTypeLabel,
|
||||||
|
goodsTypeTagType,
|
||||||
onSearchGoods() {
|
onSearchGoods() {
|
||||||
this.goodsData = [];
|
this.goodsData = [];
|
||||||
this.goodsParams.pageNumber = 1;
|
this.goodsParams.pageNumber = 1;
|
||||||
@@ -265,6 +280,11 @@ export default {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
.pool-stock-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.goods-dialog-cascader-popper {
|
.goods-dialog-cascader-popper {
|
||||||
|
|||||||
@@ -257,25 +257,40 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- E_COUPON:卡密来自订单详情 orderItems[].cardKeys(S-09,无独立 API-S-06) -->
|
<!-- 卡密信息(E_COUPON 主单或满赠子单,以 cardKeyFulfillStatus 为准) -->
|
||||||
<div v-if="isECouponOrder" class="ecoupon-card-keys mt_10">
|
<div v-if="hasCardKeySection" class="ecoupon-card-keys mt_10">
|
||||||
<h4>卡密信息</h4>
|
<h4>卡密信息</h4>
|
||||||
|
<div
|
||||||
|
v-for="line in cardKeyFulfillLines"
|
||||||
|
:key="line.key"
|
||||||
|
class="ecoupon-fulfill-block"
|
||||||
|
>
|
||||||
|
<div v-if="cardKeyFulfillLines.length > 1" class="ecoupon-fulfill-title mb_10">
|
||||||
|
{{ line.goodsName || (line.isGift ? "满赠电子卡券" : "电子卡券") }}
|
||||||
|
</div>
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="!ecouponCardKeyDelivered"
|
v-if="line.status !== 'DELIVERED'"
|
||||||
type="info"
|
:type="cardKeyFulfillAlertType(line.status)"
|
||||||
show-icon
|
show-icon
|
||||||
:closable="false"
|
:closable="false"
|
||||||
title="卡密尚未发放"
|
:title="resolveCardKeyFulfillMessage(line)"
|
||||||
class="mb_10"
|
class="mb_10"
|
||||||
/>
|
/>
|
||||||
<el-table
|
<el-table
|
||||||
v-else-if="ecouponCardKeyRows.length"
|
v-else-if="line.cardKeys.length"
|
||||||
border
|
border
|
||||||
:data="ecouponCardKeyRows"
|
:data="lineCardKeyRows(line)"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
class="mb_10"
|
||||||
>
|
>
|
||||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
<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
|
||||||
|
v-if="cardKeyFulfillLines.length > 1"
|
||||||
|
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="cardNo" label="卡号" min-width="140" show-overflow-tooltip />
|
||||||
<el-table-column prop="cardSecret" label="卡密" min-width="120" 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 prop="allocatedTime" label="发卡时间" width="170" />
|
||||||
@@ -291,6 +306,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="goods-total">
|
<div class="goods-total">
|
||||||
<ul>
|
<ul>
|
||||||
@@ -731,6 +747,13 @@ import multipleMap from "@/views/my-components/map/multiple-map";
|
|||||||
import vueQr from "vue-qr";
|
import vueQr from "vue-qr";
|
||||||
import { printElement } from "@/utils/print";
|
import { printElement } from "@/utils/print";
|
||||||
import { formatCardKeyStatus } from "@/constants/cardKey";
|
import { formatCardKeyStatus } from "@/constants/cardKey";
|
||||||
|
import {
|
||||||
|
collectCardKeyFulfillLines,
|
||||||
|
orderHasCardKeySection,
|
||||||
|
flattenOrderCardKeys,
|
||||||
|
cardKeyFulfillAlertType,
|
||||||
|
resolveCardKeyFulfillMessage,
|
||||||
|
} from "@/constants/goodsType";
|
||||||
export default {
|
export default {
|
||||||
name: "orderDetail",
|
name: "orderDetail",
|
||||||
components: {
|
components: {
|
||||||
@@ -863,20 +886,18 @@ export default {
|
|||||||
isNonPhysicalOrder() {
|
isNonPhysicalOrder() {
|
||||||
return this.isVirtualOrder || this.isECouponOrder;
|
return this.isVirtualOrder || this.isECouponOrder;
|
||||||
},
|
},
|
||||||
ecouponCardKeyDelivered() {
|
hasCardKeySection() {
|
||||||
return (this.data || []).some((item) => item.cardKeyDelivered);
|
return orderHasCardKeySection({
|
||||||
|
orderItems: this.data,
|
||||||
|
giftSummaries: this.orderInfo.giftECouponOrders,
|
||||||
|
isECouponOrder: this.isECouponOrder,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cardKeyFulfillLines() {
|
||||||
|
return collectCardKeyFulfillLines(this.data, this.orderInfo.giftECouponOrders);
|
||||||
},
|
},
|
||||||
ecouponCardKeyRows() {
|
ecouponCardKeyRows() {
|
||||||
const rows = [];
|
return flattenOrderCardKeys(this.data, true, this.orderInfo.giftECouponOrders);
|
||||||
(this.data || []).forEach((item) => {
|
|
||||||
(item.cardKeys || []).forEach((ck) => {
|
|
||||||
rows.push({
|
|
||||||
...ck,
|
|
||||||
goodsName: item.goodsName,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return rows;
|
|
||||||
},
|
},
|
||||||
canPartDelivery() {
|
canPartDelivery() {
|
||||||
if (!this.allowOperation.ship) return false;
|
if (!this.allowOperation.ship) return false;
|
||||||
@@ -910,6 +931,14 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatCardKeyStatus,
|
formatCardKeyStatus,
|
||||||
|
cardKeyFulfillAlertType,
|
||||||
|
resolveCardKeyFulfillMessage,
|
||||||
|
lineCardKeyRows(line) {
|
||||||
|
return (line.cardKeys || []).map((ck) => ({
|
||||||
|
...ck,
|
||||||
|
goodsName: line.goodsName,
|
||||||
|
}));
|
||||||
|
},
|
||||||
copyCardKey(row) {
|
copyCardKey(row) {
|
||||||
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
|
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
|||||||
@@ -33,7 +33,12 @@
|
|||||||
<el-table-column prop="originalPrice" label="原价" width="90" align="center" />
|
<el-table-column prop="originalPrice" label="原价" width="90" align="center" />
|
||||||
<el-table-column label="活动价" width="150" align="center">
|
<el-table-column label="活动价" width="150" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number v-model="row.price" :min="0.01" :precision="2" size="small" />
|
<el-input-number
|
||||||
|
v-model="row.price"
|
||||||
|
:min="promotionPriceMin(row.goodsType)"
|
||||||
|
:precision="2"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -50,6 +55,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getFlashDiscountDetail, saveFlashDiscount, editFlashDiscount } from "@/api/promotion.js";
|
import { getFlashDiscountDetail, saveFlashDiscount, editFlashDiscount } from "@/api/promotion.js";
|
||||||
|
import { promotionPriceMin } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -75,6 +81,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
promotionPriceMin,
|
||||||
openGoods() {
|
openGoods() {
|
||||||
this.$refs.liliDialog.open("goods");
|
this.$refs.liliDialog.open("goods");
|
||||||
},
|
},
|
||||||
@@ -88,6 +95,7 @@ export default {
|
|||||||
originalPrice: g.price,
|
originalPrice: g.price,
|
||||||
price: g.price,
|
price: g.price,
|
||||||
quantity: g.quantity,
|
quantity: g.quantity,
|
||||||
|
goodsType: g.goodsType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -113,6 +113,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="form.giftFlag" label="赠品" prop="giftId">
|
<el-form-item v-if="form.giftFlag" label="赠品" prop="giftId">
|
||||||
|
<p class="describe">电子卡券赠品支付后自动发卡;卡池不足时仅取消赠品子单,不影响主订单。</p>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.giftId"
|
v-model="form.giftId"
|
||||||
filterable
|
filterable
|
||||||
@@ -127,7 +128,7 @@
|
|||||||
v-for="item in giftList"
|
v-for="item in giftList"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.goodsName"
|
:label="giftOptionLabel(item)"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -241,6 +242,7 @@ import {
|
|||||||
import { getGoodsSkuListDataSeller } from "@/api/goods";
|
import { getGoodsSkuListDataSeller } from "@/api/goods";
|
||||||
import { regular } from "@/utils";
|
import { regular } from "@/utils";
|
||||||
import vueQr from "vue-qr";
|
import vueQr from "vue-qr";
|
||||||
|
import { goodsTypeLabel } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "add-full-discount",
|
name: "add-full-discount",
|
||||||
@@ -313,6 +315,16 @@ export default {
|
|||||||
this.getGiftList();
|
this.getGiftList();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
giftOptionLabel(item) {
|
||||||
|
if (!item) return "";
|
||||||
|
const typeLabel =
|
||||||
|
item.goodsType === "E_COUPON" ? ` [${goodsTypeLabel(item.goodsType)}]` : "";
|
||||||
|
const stock =
|
||||||
|
item.goodsType === "E_COUPON" && item.quantity != null
|
||||||
|
? ` · 库存${item.quantity}`
|
||||||
|
: "";
|
||||||
|
return `${item.goodsName || ""}${typeLabel}${stock}`;
|
||||||
|
},
|
||||||
closeCurrentPage() {
|
closeCurrentPage() {
|
||||||
this.$router.back();
|
this.$router.back();
|
||||||
},
|
},
|
||||||
@@ -442,6 +454,7 @@ export default {
|
|||||||
thumbnail: e.thumbnail,
|
thumbnail: e.thumbnail,
|
||||||
skuId: e.id,
|
skuId: e.id,
|
||||||
goodsId: e.goodsId,
|
goodsId: e.goodsId,
|
||||||
|
goodsType: e.goodsType,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.form.promotionGoodsList = list;
|
this.form.promotionGoodsList = list;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-if="row"
|
v-if="row"
|
||||||
v-model="row.price"
|
v-model="row.price"
|
||||||
:min="0.01"
|
:min="promotionPriceMin(row.goodsType)"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -133,6 +133,7 @@
|
|||||||
import { getPintuanGoodsList, getPintuanDetail, editPintuan } from "@/api/promotion.js";
|
import { getPintuanGoodsList, getPintuanDetail, editPintuan } from "@/api/promotion.js";
|
||||||
import liliDialog from "@/views/lili-dialog";
|
import liliDialog from "@/views/lili-dialog";
|
||||||
import vueQr from "vue-qr";
|
import vueQr from "vue-qr";
|
||||||
|
import { promotionPriceMin } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { liliDialog, vueQr },
|
components: { liliDialog, vueQr },
|
||||||
@@ -158,6 +159,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
promotionPriceMin,
|
||||||
promotionStatusText(status) {
|
promotionStatusText(status) {
|
||||||
const map = {
|
const map = {
|
||||||
NEW: "未开始",
|
NEW: "未开始",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<el-input-number
|
<el-input-number
|
||||||
v-if="row"
|
v-if="row"
|
||||||
v-model="row.price"
|
v-model="row.price"
|
||||||
:min="0.01"
|
:min="promotionPriceMin(row.goodsType)"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:disabled="row.promotionApplyStatus === 'PASS'"
|
:disabled="row.promotionApplyStatus === 'PASS'"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
@@ -185,6 +185,7 @@ import {
|
|||||||
delSeckillGoods,
|
delSeckillGoods,
|
||||||
} from "@/api/promotion.js";
|
} from "@/api/promotion.js";
|
||||||
import liliDialog from "@/views/lili-dialog";
|
import liliDialog from "@/views/lili-dialog";
|
||||||
|
import { promotionPriceMin } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -215,6 +216,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
promotionPriceMin,
|
||||||
seckillStatusText(status) {
|
seckillStatusText(status) {
|
||||||
const map = {
|
const map = {
|
||||||
NEW: "未开始",
|
NEW: "未开始",
|
||||||
@@ -349,6 +351,7 @@ export default {
|
|||||||
storeName: item.storeName,
|
storeName: item.storeName,
|
||||||
skuId: item.id,
|
skuId: item.id,
|
||||||
timeLine,
|
timeLine,
|
||||||
|
goodsType: item.goodsType,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.goodsList[this.tabIndex].list = list;
|
this.goodsList[this.tabIndex].list = list;
|
||||||
|
|||||||
Reference in New Issue
Block a user