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/api/goods.js b/buyer/src/api/goods.js index 76fe0d03..1cba631b 100644 --- a/buyer/src/api/goods.js +++ b/buyer/src/api/goods.js @@ -33,12 +33,13 @@ export function goodsDetail (goodsId) { }); } -// id获取商品Sku详情 +// id获取商品Sku详情(支持未登录浏览,登录后可选携带 token) export function goodsSkuDetail (params) { return request({ url: `/buyer/goods/goods/sku/${params.goodsId}/${params.skuId}`, method: Method.GET, - needToken: true, + needToken: false, + optionalToken: true, params }); } diff --git a/buyer/src/api/pay.js b/buyer/src/api/pay.js index 0b0d2e02..12246a6e 100644 --- a/buyer/src/api/pay.js +++ b/buyer/src/api/pay.js @@ -24,12 +24,13 @@ export function tradeDetail (params) { * @param payClient 调起方式 可用值:APP,NATIVE,JSAPI,H5 * @param sn 订单编号 */ -export function pay (params) { +export function pay (params, config = {}) { return request({ url: `/buyer/payment/cashier/pay/${params.paymentMethod}/${params.paymentClient}`, needToken: true, method: Method.GET, - params + params, + skipMessage: !!config.skipMessage, }); } diff --git a/buyer/src/api/shopentry.js b/buyer/src/api/shopentry.js index 05bdf3d1..0e5fb90e 100644 --- a/buyer/src/api/shopentry.js +++ b/buyer/src/api/shopentry.js @@ -46,7 +46,8 @@ export function applyThird (params) { export function getDetailById (id) { return request({ url: `/buyer/store/store/get/detail/${id}`, - needToken: true, + needToken: false, + optionalToken: true, method: Method.GET }) } diff --git a/buyer/src/components/goodsDetail/ShowGoods.vue b/buyer/src/components/goodsDetail/ShowGoods.vue index 74cc4f6c..2e74b747 100644 --- a/buyer/src/components/goodsDetail/ShowGoods.vue +++ b/buyer/src/components/goodsDetail/ShowGoods.vue @@ -44,7 +44,14 @@
{{ skuDetail.goodsName }} + 电子卡券 + + @@ -85,7 +92,7 @@
-
+
价     格 @@ -158,6 +165,36 @@
+ +
+

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

+
+ +
+

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

+
+ +
+

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

+

@@ -222,7 +259,6 @@

-
@@ -238,14 +274,16 @@ :precision="0" @blur="changeCount" > -   库存{{ skuDetail.quantity }} +   库存{{ displayStock }}
@@ -279,19 +317,35 @@
加入购物车 + 拼团购买 立即购买 + @@ -320,6 +374,18 @@ import { } from "@/api/member.js"; import { addCartGoods } from "@/api/cart.js"; import playIcon from "@/assets/iconfont/play.svg"; +/** 卡密商品(E_COUPON)判定与库存工具,见 @/constants/goodsType */ +import { + isECoupon, + getECouponStock, + getECouponMaxBuyNum, + createEmptyPromotionMap, + resolveECouponCartType, + eCouponAfterSaleHint, + E_COUPON_CART_BUY_NOW, + E_COUPON_CART_PINTUAN, + E_COUPON_CART_POINTS, +} from "@/constants/goodsType"; export default { name: "ShowGoods", @@ -362,15 +428,11 @@ export default { specList: [], }, // sku详情 goodsSpecList: this.detail.specs, // 商品spec - promotionMap: { - // 活动状态 - SECKILL: null, - FULL_DISCOUNT: null, - COUPON: [], - }, // 促销活动 + promotionMap: createEmptyPromotionMap(), formatList: [], // 选择商品品类的数组 loading: false, // 立即购买loading loading1: false, // 加入购物车loading + loadingPintuan: false, isCollected: false, // 是否收藏 }; }, @@ -397,15 +459,32 @@ export default { }) : []; }, + /** 卡密商品:禁加购,仅立即购买;库存读 quantity(M-02 起支持促销) */ + isECouponGoods() { + return isECoupon(this.skuDetail.goodsType); + }, + displayStock() { + if (this.isECouponGoods) { + return getECouponStock(this.skuDetail); + } + return Number(this.skuDetail.quantity) || 0; + }, quantityMax() { + if (this.isECouponGoods) { + return getECouponMaxBuyNum(this.skuDetail); + } const qty = Number(this.skuDetail.quantity) || 0; return qty > 0 ? qty : 1; }, isOutOfStock() { + if (this.isECouponGoods) { + return getECouponStock(this.skuDetail) <= 0; + } return (Number(this.skuDetail.quantity) || 0) <= 0; }, }, methods: { + eCouponAfterSaleHint, // 初始化video initVideo(){ if(!this.goodsVideo ){ @@ -425,17 +504,20 @@ export default { syncCountWithQuantity() { - const qty = Number(this.skuDetail.quantity) || 0; + const qty = this.isECouponGoods + ? getECouponStock(this.skuDetail) + : Number(this.skuDetail.quantity) || 0; if (qty <= 0) { this.count = 1; return; } - if (this.wholesaleList && this.wholesaleList.length > 0) { + if (!this.isECouponGoods && this.wholesaleList && this.wholesaleList.length > 0) { this.count = Math.min(Math.max(this.wholesaleList[0].num, 1), qty); return; } - if (this.count > qty) { - this.count = qty; + const max = this.isECouponGoods ? getECouponMaxBuyNum(this.skuDetail) : qty; + if (this.count > max) { + this.count = max; } else if (this.count < 1) { this.count = 1; } @@ -471,6 +553,11 @@ export default { }, addShoppingCartBtn() { + // FR-B-01:E_COUPON 禁止加购物车(后端亦返回 CARD_KEY_E_COUPON_CART_FORBIDDEN) + if (this.isECouponGoods) { + Message.warning("电子卡券请使用立即购买"); + return; + } // 添加购物车 const params = { num: this.count, @@ -500,20 +587,32 @@ export default { }); }, 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 = { num: this.count, skuId: this.skuDetail.id, cartType: "BUY_NOW", }; - // 虚拟商品购买 if (this.skuDetail.goodsType === "VIRTUAL_GOODS") { params.cartType = "VIRTUAL"; + } else if (this.isECouponGoods) { + params.cartType = resolveECouponCartType({ + routeWay: this.$route.query.way, + action, + }); } - this.loading1 = true; + this[loadingKey] = true; addCartGoods(params) .then((res) => { - this.loading1 = false; + this[loadingKey] = false; if (res.success) { this.$router.push({ path: "/pay", @@ -524,7 +623,7 @@ export default { } }) .catch(() => { - this.loading1 = false; + this[loadingKey] = false; }); }, async collect() { @@ -602,16 +701,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]]; } @@ -630,8 +733,8 @@ export default { }, }, mounted() { - // 用户登录才会判断是否收藏 - if (this.Cookies.getItem("userInfo")) { + // 用户登录才会判断是否收藏(须同时有 token,避免残留 userInfo 触发 401 跳转登录) + if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) { isCollection("GOODS", this.skuDetail.id).then((res) => { if (res.success && res.result) { this.isCollected = true; @@ -958,6 +1061,22 @@ export default { border-top: 1px dotted $border_color; } +.e-coupon-after-sale-notice { + width: fit-content; + max-width: 420px; + flex: 0 0 auto; + padding: 4px 10px; + + :deep(.el-alert__content) { + padding: 0; + } + + :deep(.el-alert__title) { + font-size: 12px; + line-height: 1.5; + } +} + .add-buy-car-row { margin-top: 25px; diff --git a/buyer/src/constants/goodsType.js b/buyer/src/constants/goodsType.js new file mode 100644 index 00000000..70f27872 --- /dev/null +++ b/buyer/src/constants/goodsType.js @@ -0,0 +1,208 @@ +/** + * 卡密商品(E_COUPON / 电子卡券)— 买家端公共常量与工具 + * + * 与 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 + */ +export const E_COUPON_GOODS_TYPE = "E_COUPON"; +export const E_COUPON_ORDER_TYPE = "E_COUPON"; + +/** 单次购买数量上限(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; +} + +export function isECouponOrder(orderType) { + return orderType === E_COUPON_ORDER_TYPE; +} + +/** 详情页提示:卡密交付后不支持买家售后(FR-B-04) */ +export const E_COUPON_AFTER_SALE_HINT = + "卡密交付后不支持买家售后,请确认商品信息后再购买。"; + +export function eCouponAfterSaleHint() { + return E_COUPON_AFTER_SALE_HINT; +} + +/** 可售库存:E_COUPON 读 SKU quantity(由卡池 syncSkuStock 同步) */ +export function getECouponStock(sku) { + if (!sku) return 0; + return Number(sku.quantity) || 0; +} + +export function getECouponMaxBuyNum(sku) { + const stock = getECouponStock(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, + }; +} + +/** 汇总当前订单 orderItems 中的卡密履约行(不含满赠子单;赠品卡密在赠品子单详情单独查看) */ +export function collectCardKeyFulfillLines(orderItems = []) { + const lines = []; + (orderItems || []).forEach((item) => { + if (!isCardKeyFulfillApplicable(item)) return; + lines.push(normalizeCardKeyFulfillLine(item)); + }); + return lines; +} + +export function orderHasCardKeySection({ orderItems, isECouponOrder } = {}) { + if (isECouponOrder) 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; +} + +/** 当前订单是否含已交付卡密(仅 orderItems,不含满赠子单) */ +export function orderHasCardKeyContent(orderItems) { + return flattenCardKeyFulfillLines(collectCardKeyFulfillLines(orderItems)).length > 0; +} + +/** 扁平化当前订单 orderItems 卡密 */ +export function flattenOrderCardKeys(orderItems, withGoodsName = false) { + const lines = collectCardKeyFulfillLines(orderItems); + 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/main.js b/buyer/src/main.js index ebfcc214..7c284225 100644 --- a/buyer/src/main.js +++ b/buyer/src/main.js @@ -13,6 +13,7 @@ import storage from "@/plugins/storage"; import { fetchAndApplyTheme } from "@/utils/theme"; import { getThemeSetting } from "@/api/common.js"; import config from "@/config"; +import { openLink } from "@/utils/buyerLogin"; const { aMapSecurityJsCode, inputMaxLength } = config; @@ -36,12 +37,7 @@ app.config.globalProperties.Cookies = storage; app.config.globalProperties.$inputMaxLength = inputMaxLength; app.config.globalProperties.linkTo = function (url) { - if (!url) return; - if (url.substr(0, 1) === "/") { - window.open(location.origin + url, "_blank"); - } else { - window.open(url, "_blank"); - } + openLink(url, router); }; app.config.globalProperties.connectCs = function ( 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/GoodsDetail.vue b/buyer/src/pages/GoodsDetail.vue index ecaf8060..cf281726 100644 --- a/buyer/src/pages/GoodsDetail.vue +++ b/buyer/src/pages/GoodsDetail.vue @@ -140,8 +140,8 @@ export default { if (!this.goodsMsg.data.intro) { this.goodsMsg.data.intro = '' } - // 判断是否收藏 - if (this.Cookies.getItem("userInfo")) { + // 判断是否收藏(须同时有 token,避免未登录/ token 失效时误跳登录页) + if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) { isStoreCollection("STORE", this.goodsMsg.data.storeId).then((res) => { if (res.success && res.result) { this.storeCollected = true; diff --git a/buyer/src/pages/GoodsList.vue b/buyer/src/pages/GoodsList.vue index 6cd061a3..8644a133 100644 --- a/buyer/src/pages/GoodsList.vue +++ b/buyer/src/pages/GoodsList.vue @@ -80,9 +80,16 @@ > 自营 + + + 电子卡券 + 虚拟 @@ -118,6 +125,7 @@ import { Message } from "@/utils/message"; import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'; import GoodsClassNav from "@/components/nav/GoodsClassNav"; import * as apiGoods from "@/api/goods"; +import { isBuyerLoggedIn, redirectToLogin } from "@/utils/buyerLogin"; export default { name: "GoodsList", beforeRouteEnter(to, from, next) { @@ -194,12 +202,15 @@ export default { this.getGoodsList(); }, goGoodsDetail(skuId, goodsId) { - // 跳转商品详情 - let routeUrl = this.$router.resolve({ + const location = { path: "/goodsDetail", query: { skuId, goodsId }, - }); - window.open(routeUrl.href, "_blank"); + }; + if (!isBuyerLoggedIn()) { + redirectToLogin(this.$router, location); + return; + } + window.open(this.$router.resolve(location).href, "_blank"); }, goShopPage(id) { if (!id) { @@ -281,13 +292,24 @@ export default { } .goods-show-tag { - height: 18px; - width: 32px; - line-height: 14px; - white-space: nowrap; - text-align: center; + display: inline-flex; align-items: center; - padding: 0 3px; + justify-content: center; + height: 18px; + min-width: 32px; + width: auto; + line-height: 1; + white-space: nowrap; + padding: 0 4px; + box-sizing: border-box; + + :deep(.el-tag__content) { + line-height: 1; + } +} + +.goods-show-tag-ecoupon { + padding: 0 5px; } .goods-show-tag-self { diff --git a/buyer/src/pages/Login.vue b/buyer/src/pages/Login.vue index 98cb7762..da9db604 100644 --- a/buyer/src/pages/Login.vue +++ b/buyer/src/pages/Login.vue @@ -197,6 +197,7 @@ export default { mobile: "", }, verifyStatus: false, // 是否图片验证通过 + codeSent: false, // 是否已成功发送短信验证码 ruleInline: { // 验证规则 username: [{required: true, message: "请输入用户名"}], @@ -237,7 +238,15 @@ export default { if (this.type) { this.$refs.verify?.init(); } else { - let data = JSON.parse(JSON.stringify(this.formSms)); + if (!this.verifyStatus) { + Message.warning("请先完成安全验证"); + return; + } + if (!this.codeSent) { + Message.warning("请先获取短信验证码"); + return; + } + const data = JSON.parse(JSON.stringify(this.formSms)); apiLogin.smsLogin(data).then((res) => { this.hideVerify(); if (res.success) { @@ -262,7 +271,7 @@ export default { } else { Message.error(res.message); } - }); + }).catch(() => {}); } } }); @@ -285,6 +294,7 @@ export default { sendSms(params).then((res) => { if (res.success) { Message.success("验证码发送成功"); + this.codeSent = true; let that = this; this.interval = setInterval(() => { that.time--; @@ -292,6 +302,7 @@ export default { that.time = 60; that.codeMsg = "重新发送"; that.verifyStatus = false; + that.codeSent = false; clearInterval(that.interval); } else { that.codeMsg = that.time; @@ -300,7 +311,7 @@ export default { } else { Message.warning(res.message); } - }); + }).catch(() => {}); } }, verifyChange(con) { @@ -453,11 +464,19 @@ export default { this.$refs.formSms.resetFields(); } this.verifyStatus = false; + this.codeSent = false; this.hideVerify(); clearInterval(this.interval); this.codeMsg = "发送验证码"; this.time = 60; }, + "formSms.mobile"() { + this.codeSent = false; + this.verifyStatus = false; + clearInterval(this.interval); + this.codeMsg = "发送验证码"; + this.time = 60; + }, }, }; diff --git a/buyer/src/pages/home/orderCenter/MyOrder.vue b/buyer/src/pages/home/orderCenter/MyOrder.vue index 79b5f296..96e456ba 100644 --- a/buyer/src/pages/home/orderCenter/MyOrder.vue +++ b/buyer/src/pages/home/orderCenter/MyOrder.vue @@ -72,8 +72,8 @@ 取消订单 去支付 确认收货 - - + 申请售后
@@ -141,8 +141,8 @@ 取消订单 去支付 确认收货 - - + 申请售后
diff --git a/buyer/src/pages/home/orderCenter/OrderDetail.vue b/buyer/src/pages/home/orderCenter/OrderDetail.vue index 7389b04a..1ce29e45 100644 --- a/buyer/src/pages/home/orderCenter/OrderDetail.vue +++ b/buyer/src/pages/home/orderCenter/OrderDetail.vue @@ -30,7 +30,7 @@ >取消订单 查看物流 -

+

核验码:{{ order.order.verificationCode }}

@@ -51,7 +51,7 @@ >
-
+

收货人信息

收货人:{{ order.order.consigneeName }}

手机号码:{{ $filters.secrecyMobile( order.order.consigneeMobile ) }}

@@ -70,7 +70,7 @@

支付方式:{{ order.paymentMethodValue }}

付款状态:{{ order.payStatusValue }}

-
+

配送信息

配送方式:{{ order.deliveryMethodValue }}

配送状态:{{ order.deliverStatusValue }}

@@ -132,6 +132,51 @@
未开发票
+ +
+

卡密信息

+
+ + +
+ + 复制全部卡密 + +
+
@@ -144,8 +189,8 @@ 货号 单价 数量 - 退款状态 - 实际退款金额 + 退款状态 + 实际退款金额 小计 操作 @@ -170,8 +215,8 @@ {{ goods.id }} {{ $filters.unitPrice(goods.goodsPrice, "¥") }} {{ goods.num }} - {{refundPriceList(goods.isRefund)}} - {{ $filters.unitPrice(goods.refundPrice, "¥") }} + {{refundPriceList(goods.isRefund)}} + {{ $filters.unitPrice(goods.refundPrice, "¥") }} {{ $filters.unitPrice((goods.goodsPrice * goods.num), "¥") }} 商品总价:{{ $filters.unitPrice(order.order.goodsPrice, "¥") }}
-
+
运费:+{{ $filters.unitPrice(order.order.freightPrice, "¥") }}
@@ -319,6 +365,19 @@ 取消
+ + + +
+

卡号:{{ row.cardNo }}

+

卡密:{{ row.cardSecret }}

+ 复制 +
+ +
@@ -333,6 +392,19 @@ import { getPackage } from "@/api/order.js"; import { afterSaleReason, receiptDetail } from "@/api/member"; +import { + isECouponOrder as checkECouponOrder, + collectCardKeyFulfillLines, + orderHasCardKeySection, + flattenOrderCardKeys, + cardKeyFulfillAlertType, + resolveCardKeyFulfillMessage, +} from "@/constants/goodsType"; + +/** + * 订单详情:E_COUPON 展示 orderItems[].cardKeys,屏蔽售后/物流(FR-B-03 / FR-B-04)。 + * 移动端已完成订单用 Dialog 查看卡密(原型 P-09 / D-01)。 + */ export default { name: "order-detail", data() { @@ -340,6 +412,7 @@ export default { order: {}, // 订单详情数据 progressList: [], // 订单流程 logistics: "", // 物流数据 + cardKeyDialogVisible: false, cancelParams: { // 取消售后参数 orderSn: "", @@ -352,7 +425,63 @@ export default { logisticsModal: false, }; }, + computed: { + /** 电子卡券订单:无物流/核验码,卡密数据来自 API-B-01 orderItems[].cardKeys */ + isECouponOrder() { + return checkECouponOrder(this.order?.order?.orderType); + }, + isVirtualOrder() { + return this.order?.order?.orderType === "VIRTUAL"; + }, + isNonPhysicalOrder() { + return this.isVirtualOrder || this.isECouponOrder; + }, + /** 是否展示卡密区块(仅当前订单 orderItems) */ + hasCardKeySection() { + return orderHasCardKeySection({ + orderItems: this.order.orderItems, + isECouponOrder: this.isECouponOrder, + }); + }, + cardKeyFulfillLines() { + return collectCardKeyFulfillLines(this.order.orderItems); + }, + ecouponCardKeyRows() { + return flattenOrderCardKeys(this.order.orderItems, false); + }, + }, methods: { + cardKeyFulfillAlertType, + resolveCardKeyFulfillMessage, + lineCardKeyRows(line) { + return line.cardKeys || []; + }, + copyCardKey(row) { + const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`; + this.copyText(text); + }, + copyAllCardKeys() { + const text = this.ecouponCardKeyRows + .map((row, i) => `${i + 1}. 卡号:${row.cardNo || ""} 卡密:${row.cardSecret || ""}`) + .join("\n"); + this.copyText(text || ""); + }, + copyText(text) { + if (!text) return; + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(() => { + Message.success("已复制到剪贴板"); + }); + } else { + const ta = document.createElement("textarea"); + ta.value = text; + document.body.appendChild(ta); + ta.select(); + document.execCommand("copy"); + document.body.removeChild(ta); + Message.success("已复制到剪贴板"); + } + }, isVatSpecialReceipt (receipt) { if (!receipt) return false; const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : ""; @@ -660,6 +789,38 @@ table { color: $theme_color; } } + +.ecoupon-card-keys-mobile { + display: none; +} + +.ecoupon-fulfill-block + .ecoupon-fulfill-block { + margin-top: 16px; + padding-top: 16px; + border-top: 1px dashed #eee; +} + +.ecoupon-card-keys-pc { + display: block; +} + +.card-key-mobile-item { + padding: 12px 0; + border-bottom: 1px solid #eee; + p { + margin: 4px 0; + word-break: break-all; + } +} + +@media (max-width: 768px) { + .ecoupon-card-keys-mobile { + display: block; + } + .ecoupon-card-keys-pc { + display: none; + } +} /** 订单进度条 */ .progress { margin: 15px 0; diff --git a/buyer/src/pages/payment/Pay.vue b/buyer/src/pages/payment/Pay.vue index 81cf558e..757d8722 100644 --- a/buyer/src/pages/payment/Pay.vue +++ b/buyer/src/pages/payment/Pay.vue @@ -18,7 +18,8 @@
-
+ +
收货人信息 管理收货人地址 @@ -94,7 +95,7 @@
-
+
配送方式
@@ -150,8 +151,8 @@
- -
+ +
发票信息 开企业抬头发票须填写纳税人识别号,以免影响报销 @@ -283,13 +284,13 @@
-
+
收货信息:
{{ orderInfo.order.consigneeName }} @@ -165,7 +165,7 @@
--> -
+
配送方式:
{{ orderInfo.deliveryMethodValue }} @@ -217,6 +217,41 @@ + + +
+

卡密信息

+ + + + + + + + + + + + + +
+
  • @@ -280,7 +315,7 @@ {{item.promotionName}}:| ¥{{ unitPrice(item.discountPrice) }}
  • --> -
  • +
  • 运费: {{ unitPrice(orderInfo.order.freightPrice, "¥") @@ -440,6 +475,7 @@ import vueQr from "vue-qr"; import { printElement } from "@/utils/print"; import { ElMessage, ElMessageBox } from "element-plus"; import { unitPrice, clientTypeWay } from "@/utils/filters"; +import { formatCardKeyStatus } from "@/constants/cardKey"; export default { name: "orderList", components: { @@ -447,6 +483,32 @@ export default { multipleMap, "vue-qr": vueQr, }, + computed: { + /** 电子卡券订单:无物流区,卡密来自 orderItems[].cardKeys */ + isECouponOrder() { + const t = this.orderInfo?.order?.orderType; + return t === "E_COUPON" || this.$route.query.orderType === "E_COUPON"; + }, + isVirtualOrder() { + const t = this.orderInfo?.order?.orderType; + return t === "VIRTUAL" || this.$route.query.orderType === "VIRTUAL"; + }, + isNonPhysicalOrder() { + return this.isVirtualOrder || this.isECouponOrder; + }, + ecouponCardKeyDelivered() { + return (this.data || []).some((item) => item.cardKeyDelivered); + }, + ecouponCardKeyRows() { + const rows = []; + (this.data || []).forEach((item) => { + (item.cardKeys || []).forEach((ck) => { + rows.push({ ...ck }); + }); + }); + return rows; + }, + }, data () { return { typeList: [], @@ -546,6 +608,23 @@ export default { methods: { unitPrice, clientTypeWay, + formatCardKeyStatus, + copyCardKey(row) { + const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`; + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(() => { + ElMessage.success("已复制到剪贴板"); + }); + } else { + const ta = document.createElement("textarea"); + ta.value = text; + document.body.appendChild(ta); + ta.select(); + document.execCommand("copy"); + document.body.removeChild(ta); + ElMessage.success("已复制到剪贴板"); + } + }, getPromotionText(row) { let resultText = ""; if (row && row.promotionType) { diff --git a/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue b/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue index e064b141..bb2ccbb0 100644 --- a/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue +++ b/manager/src/views/promotions/kanjia/kanjia-activity-add-goods.vue @@ -105,6 +105,7 @@ import { saveKanJiaActivityGoods } from "@/api/promotion"; import { regular } from "@/utils"; import skuSelect from "@/components/lili-dialog"; +import { isECoupon } from "@/constants/goodsType"; export default { name: "add-kanjia-activity-goods", @@ -175,6 +176,7 @@ export default { let checkResult = true; this.form.promotionGoodsList.forEach((res) => { + const eCoupon = isECoupon(res.goodsType); if (res.stock <= 0 || res.stock > res.quantity) { checkResult = false; this.$Message.error("活动库存不能为0且不能超过商品库存"); @@ -186,9 +188,16 @@ export default { this.$Message.error("结算价格金额格式不正确"); return; } - if (res.settlementPrice < 0 || res.settlementPrice > res.price) { + if ( + (eCoupon ? res.settlementPrice < 0 : res.settlementPrice <= 0) || + res.settlementPrice > res.price + ) { checkResult = false; - this.$Message.error("结算价格金额不能为0且不能超过商品价格"); + this.$Message.error( + eCoupon + ? "结算价格不能小于0且不能超过商品价格" + : "结算价格金额不能为0且不能超过商品价格" + ); return; } if (!regular.money.test(res.highestPrice)) { @@ -206,9 +215,9 @@ export default { this.$Message.error("最低可砍金额格式错误"); return; } - if (res.lowestPrice <= 0 || res.lowestPrice > res.price) { + if (res.lowestPrice < 0 || (!eCoupon && res.lowestPrice <= 0) || res.lowestPrice > res.price) { checkResult = false; - this.$Message.error("最低可砍金额不能为0"); + this.$Message.error(eCoupon ? "最低可砍金额不能小于0" : "最低可砍金额不能为0"); return; } if (parseInt(res.lowestPrice) > parseInt(res.highestPrice)) { @@ -277,6 +286,7 @@ export default { storeId: e.storeId, storeName: e.storeName, skuId: e.id, + goodsType: e.goodsType, }); }); this.form.promotionGoodsList = list; diff --git a/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue b/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue index 43ece4f4..ad432847 100644 --- a/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue +++ b/manager/src/views/promotions/kanjia/kanjia-activity-edit-goods.vue @@ -90,6 +90,7 @@ + + diff --git a/seller/src/views/goods/goods-seller/goods.vue b/seller/src/views/goods/goods-seller/goods.vue index f564ae40..56aa8cdc 100644 --- a/seller/src/views/goods/goods-seller/goods.vue +++ b/seller/src/views/goods/goods-seller/goods.vue @@ -53,6 +53,8 @@ > + + @@ -148,7 +150,7 @@ - + @@ -196,6 +203,7 @@ import { getGoodsListData, getGoodsNumerData, + getQueryGoodsIdGoodsList, upGoods, lowGoods, } from "@/api/goods"; @@ -252,7 +260,34 @@ export default { goodsTypeText(v) { if (v === "PHYSICAL_GOODS") return "实物商品"; if (v === "VIRTUAL_GOODS") return "虚拟商品"; - return "电子卡券"; + if (v === "E_COUPON") return "电子卡券"; // 卡密商品 + return v || "—"; + }, + /** 跳转卡池管理;单 SKU 商品默认取第一个 SKU(card-key-pool 需 skuId) */ + async goCardKeyPool(row) { + let skuId = row.skuId; + if (!skuId) { + try { + const res = await getQueryGoodsIdGoodsList(row.id); + if (res.success && res.result?.length) { + skuId = res.result[0].id; + } else { + this.$message.warning("该商品暂无 SKU,请先完善商品规格"); + return; + } + } catch { + this.$message.error("获取商品规格失败"); + return; + } + } + this.$router.push({ + path: "/card-key-pool", + query: { + skuId, + goodsId: row.id, + goodsName: row.goodsName, + }, + }); }, marketEnableText(v) { if (v === "DOWN") return "下架"; diff --git a/seller/src/views/goods/goods-seller/goodsOperation.vue b/seller/src/views/goods/goods-seller/goodsOperation.vue index df7a6bb0..8bb67f9d 100644 --- a/seller/src/views/goods/goods-seller/goodsOperation.vue +++ b/seller/src/views/goods/goods-seller/goodsOperation.vue @@ -1,58 +1,31 @@ diff --git a/seller/src/views/goods/goods-seller/goodsOperationSec.vue b/seller/src/views/goods/goods-seller/goodsOperationSec.vue index ec94a0ae..bd110fd1 100644 --- a/seller/src/views/goods/goods-seller/goodsOperationSec.vue +++ b/seller/src/views/goods/goods-seller/goodsOperationSec.vue @@ -8,12 +8,37 @@

    基本信息

    - - {{ - baseInfoForm.categoryName[0] - }} - > {{ baseInfoForm.categoryName[1] }} - > {{ baseInfoForm.categoryName[2] }} + + + +
    + {{ item.label }} + {{ item.desc }} +
    +
    +
    +
    {{ currentGoodsTypeDesc }}
    +
    + + @@ -44,7 +69,7 @@ @@ -52,7 +77,7 @@ 批发型 - 虚拟型 + {{ isECouponGoods ? "电子卡券" : "虚拟型" }} + + +
    -
    +

    商品物流信息

    @@ -486,7 +519,6 @@