mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-09-21 04:12:03 +08:00
Merge branch 'master' into salesman
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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ export function goodsDetail (goodsId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// id获取商品Sku详情
|
// id获取商品Sku详情(支持未登录浏览,登录后可选携带 token)
|
||||||
export function goodsSkuDetail (params) {
|
export function goodsSkuDetail (params) {
|
||||||
return request({
|
return request({
|
||||||
url: `/buyer/goods/goods/sku/${params.goodsId}/${params.skuId}`,
|
url: `/buyer/goods/goods/sku/${params.goodsId}/${params.skuId}`,
|
||||||
method: Method.GET,
|
method: Method.GET,
|
||||||
needToken: true,
|
needToken: false,
|
||||||
|
optionalToken: true,
|
||||||
params
|
params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ export function tradeDetail (params) {
|
|||||||
* @param payClient 调起方式 可用值:APP,NATIVE,JSAPI,H5
|
* @param payClient 调起方式 可用值:APP,NATIVE,JSAPI,H5
|
||||||
* @param sn 订单编号
|
* @param sn 订单编号
|
||||||
*/
|
*/
|
||||||
export function pay (params) {
|
export function pay (params, config = {}) {
|
||||||
return request({
|
return request({
|
||||||
url: `/buyer/payment/cashier/pay/${params.paymentMethod}/${params.paymentClient}`,
|
url: `/buyer/payment/cashier/pay/${params.paymentMethod}/${params.paymentClient}`,
|
||||||
needToken: true,
|
needToken: true,
|
||||||
method: Method.GET,
|
method: Method.GET,
|
||||||
params
|
params,
|
||||||
|
skipMessage: !!config.skipMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ export function applyThird (params) {
|
|||||||
export function getDetailById (id) {
|
export function getDetailById (id) {
|
||||||
return request({
|
return request({
|
||||||
url: `/buyer/store/store/get/detail/${id}`,
|
url: `/buyer/store/store/get/detail/${id}`,
|
||||||
needToken: true,
|
needToken: false,
|
||||||
|
optionalToken: true,
|
||||||
method: Method.GET
|
method: Method.GET
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,14 @@
|
|||||||
<div class="item-detail-title-row">
|
<div class="item-detail-title-row">
|
||||||
<span class="item-detail-name">{{ skuDetail.goodsName }}</span>
|
<span class="item-detail-name">{{ skuDetail.goodsName }}</span>
|
||||||
<el-tag
|
<el-tag
|
||||||
v-if="skuDetail.goodsType !== 'VIRTUAL_GOODS'"
|
v-if="isECouponGoods"
|
||||||
|
class="goods-type-tag"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
电子卡券
|
||||||
|
</el-tag>
|
||||||
|
<el-tag
|
||||||
|
v-else-if="skuDetail.goodsType !== 'VIRTUAL_GOODS'"
|
||||||
class="goods-type-tag"
|
class="goods-type-tag"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
@@ -85,7 +92,7 @@
|
|||||||
<!-- 商品原价 -->
|
<!-- 商品原价 -->
|
||||||
<div class="item-price-row" v-else>
|
<div class="item-price-row" v-else>
|
||||||
<!-- 批发价格 -->
|
<!-- 批发价格 -->
|
||||||
<div v-if="wholesaleNum && wholesaleNum.length">
|
<div v-if="!isECouponGoods && wholesaleNum && wholesaleNum.length">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="item-price-title">
|
<div class="item-price-title">
|
||||||
价 格
|
价 格
|
||||||
@@ -158,6 +165,36 @@
|
|||||||
</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="promotionMap['FULL_DISCOUNT']">
|
<div class="item-price-row" v-if="promotionMap['FULL_DISCOUNT']">
|
||||||
<p>
|
<p>
|
||||||
@@ -222,7 +259,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
<div class="add-buy-car-box">
|
<div class="add-buy-car-box">
|
||||||
<div class="item-select">
|
<div class="item-select">
|
||||||
<div class="item-select-title">
|
<div class="item-select-title">
|
||||||
@@ -238,14 +274,16 @@
|
|||||||
:precision="0"
|
:precision="0"
|
||||||
@blur="changeCount"
|
@blur="changeCount"
|
||||||
></el-input-number>
|
></el-input-number>
|
||||||
<span class="inventory"> 库存{{ skuDetail.quantity }}</span>
|
<span class="inventory"> 库存{{ displayStock }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="item-select"
|
class="item-select"
|
||||||
v-if="
|
v-if="
|
||||||
skuDetail.goodsType !== 'VIRTUAL_GOODS' && skuDetail.weight !== 0
|
!isECouponGoods &&
|
||||||
|
skuDetail.goodsType !== 'VIRTUAL_GOODS' &&
|
||||||
|
skuDetail.weight !== 0
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="item-select-title">
|
<div class="item-select-title">
|
||||||
@@ -279,19 +317,35 @@
|
|||||||
<div class="add-buy-car">
|
<div class="add-buy-car">
|
||||||
<el-button
|
<el-button
|
||||||
class="goods-action-btn"
|
class="goods-action-btn"
|
||||||
v-if="skuDetail.goodsType !== 'VIRTUAL_GOODS'"
|
v-if="!isECouponGoods && skuDetail.goodsType !== 'VIRTUAL_GOODS'"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="skuDetail.quantity === 0"
|
:disabled="isOutOfStock"
|
||||||
@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"
|
||||||
:disabled="skuDetail.quantity === 0"
|
:disabled="isOutOfStock"
|
||||||
@click="buyNow"
|
@click="buyNow"
|
||||||
>立即购买</el-button
|
>立即购买</el-button
|
||||||
>
|
>
|
||||||
|
<el-alert
|
||||||
|
v-if="isECouponGoods"
|
||||||
|
class="e-coupon-after-sale-notice"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
:title="eCouponAfterSaleHint()"
|
||||||
|
/>
|
||||||
<el-tooltip content="观看视频" v-if="skuDetail.goodsVideo">
|
<el-tooltip content="观看视频" v-if="skuDetail.goodsVideo">
|
||||||
<img class="view-video" @click="showGoodsVideo = true" :src="playIcon" alt="">
|
<img class="view-video" @click="showGoodsVideo = true" :src="playIcon" alt="">
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@@ -320,6 +374,18 @@ import {
|
|||||||
} from "@/api/member.js";
|
} from "@/api/member.js";
|
||||||
import { addCartGoods } from "@/api/cart.js";
|
import { addCartGoods } from "@/api/cart.js";
|
||||||
import playIcon from "@/assets/iconfont/play.svg";
|
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 {
|
export default {
|
||||||
name: "ShowGoods",
|
name: "ShowGoods",
|
||||||
@@ -362,15 +428,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, // 是否收藏
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -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() {
|
quantityMax() {
|
||||||
|
if (this.isECouponGoods) {
|
||||||
|
return getECouponMaxBuyNum(this.skuDetail);
|
||||||
|
}
|
||||||
const qty = Number(this.skuDetail.quantity) || 0;
|
const qty = Number(this.skuDetail.quantity) || 0;
|
||||||
return qty > 0 ? qty : 1;
|
return qty > 0 ? qty : 1;
|
||||||
},
|
},
|
||||||
isOutOfStock() {
|
isOutOfStock() {
|
||||||
|
if (this.isECouponGoods) {
|
||||||
|
return getECouponStock(this.skuDetail) <= 0;
|
||||||
|
}
|
||||||
return (Number(this.skuDetail.quantity) || 0) <= 0;
|
return (Number(this.skuDetail.quantity) || 0) <= 0;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
eCouponAfterSaleHint,
|
||||||
// 初始化video
|
// 初始化video
|
||||||
initVideo(){
|
initVideo(){
|
||||||
if(!this.goodsVideo ){
|
if(!this.goodsVideo ){
|
||||||
@@ -425,17 +504,20 @@ export default {
|
|||||||
|
|
||||||
|
|
||||||
syncCountWithQuantity() {
|
syncCountWithQuantity() {
|
||||||
const qty = Number(this.skuDetail.quantity) || 0;
|
const qty = this.isECouponGoods
|
||||||
|
? getECouponStock(this.skuDetail)
|
||||||
|
: Number(this.skuDetail.quantity) || 0;
|
||||||
if (qty <= 0) {
|
if (qty <= 0) {
|
||||||
this.count = 1;
|
this.count = 1;
|
||||||
return;
|
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);
|
this.count = Math.min(Math.max(this.wholesaleList[0].num, 1), qty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.count > qty) {
|
const max = this.isECouponGoods ? getECouponMaxBuyNum(this.skuDetail) : qty;
|
||||||
this.count = qty;
|
if (this.count > max) {
|
||||||
|
this.count = max;
|
||||||
} else if (this.count < 1) {
|
} else if (this.count < 1) {
|
||||||
this.count = 1;
|
this.count = 1;
|
||||||
}
|
}
|
||||||
@@ -471,6 +553,11 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addShoppingCartBtn() {
|
addShoppingCartBtn() {
|
||||||
|
// FR-B-01:E_COUPON 禁止加购物车(后端亦返回 CARD_KEY_E_COUPON_CART_FORBIDDEN)
|
||||||
|
if (this.isECouponGoods) {
|
||||||
|
Message.warning("电子卡券请使用立即购买");
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 添加购物车
|
// 添加购物车
|
||||||
const params = {
|
const params = {
|
||||||
num: this.count,
|
num: this.count,
|
||||||
@@ -500,20 +587,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",
|
||||||
};
|
};
|
||||||
// 虚拟商品购买
|
|
||||||
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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.loading1 = true;
|
this[loadingKey] = 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",
|
||||||
@@ -524,7 +623,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.loading1 = false;
|
this[loadingKey] = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async collect() {
|
async collect() {
|
||||||
@@ -602,16 +701,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]];
|
||||||
}
|
}
|
||||||
@@ -630,8 +733,8 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 用户登录才会判断是否收藏
|
// 用户登录才会判断是否收藏(须同时有 token,避免残留 userInfo 触发 401 跳转登录)
|
||||||
if (this.Cookies.getItem("userInfo")) {
|
if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) {
|
||||||
isCollection("GOODS", this.skuDetail.id).then((res) => {
|
isCollection("GOODS", this.skuDetail.id).then((res) => {
|
||||||
if (res.success && res.result) {
|
if (res.success && res.result) {
|
||||||
this.isCollected = true;
|
this.isCollected = true;
|
||||||
@@ -958,6 +1061,22 @@ export default {
|
|||||||
border-top: 1px dotted $border_color;
|
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 {
|
.add-buy-car-row {
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
|
|
||||||
|
|||||||
208
buyer/src/constants/goodsType.js
Normal file
208
buyer/src/constants/goodsType.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import storage from "@/plugins/storage";
|
|||||||
import { fetchAndApplyTheme } from "@/utils/theme";
|
import { fetchAndApplyTheme } from "@/utils/theme";
|
||||||
import { getThemeSetting } from "@/api/common.js";
|
import { getThemeSetting } from "@/api/common.js";
|
||||||
import config from "@/config";
|
import config from "@/config";
|
||||||
|
import { openLink } from "@/utils/buyerLogin";
|
||||||
|
|
||||||
const { aMapSecurityJsCode, inputMaxLength } = config;
|
const { aMapSecurityJsCode, inputMaxLength } = config;
|
||||||
|
|
||||||
@@ -36,12 +37,7 @@ app.config.globalProperties.Cookies = storage;
|
|||||||
app.config.globalProperties.$inputMaxLength = inputMaxLength;
|
app.config.globalProperties.$inputMaxLength = inputMaxLength;
|
||||||
|
|
||||||
app.config.globalProperties.linkTo = function (url) {
|
app.config.globalProperties.linkTo = function (url) {
|
||||||
if (!url) return;
|
openLink(url, router);
|
||||||
if (url.substr(0, 1) === "/") {
|
|
||||||
window.open(location.origin + url, "_blank");
|
|
||||||
} else {
|
|
||||||
window.open(url, "_blank");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
app.config.globalProperties.connectCs = function (
|
app.config.globalProperties.connectCs = function (
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ export default {
|
|||||||
if (!this.goodsMsg.data.intro) {
|
if (!this.goodsMsg.data.intro) {
|
||||||
this.goodsMsg.data.intro = ''
|
this.goodsMsg.data.intro = ''
|
||||||
}
|
}
|
||||||
// 判断是否收藏
|
// 判断是否收藏(须同时有 token,避免未登录/ token 失效时误跳登录页)
|
||||||
if (this.Cookies.getItem("userInfo")) {
|
if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) {
|
||||||
isStoreCollection("STORE", this.goodsMsg.data.storeId).then((res) => {
|
isStoreCollection("STORE", this.goodsMsg.data.storeId).then((res) => {
|
||||||
if (res.success && res.result) {
|
if (res.success && res.result) {
|
||||||
this.storeCollected = true;
|
this.storeCollected = true;
|
||||||
|
|||||||
@@ -80,9 +80,16 @@
|
|||||||
>
|
>
|
||||||
自营
|
自营
|
||||||
</el-tag>
|
</el-tag>
|
||||||
|
<!-- E_COUPON 卡密商品类型标识 -->
|
||||||
|
<el-tag
|
||||||
|
class="goods-show-tag goods-show-tag-physical goods-show-tag-ecoupon"
|
||||||
|
v-if="item.goodsType === 'E_COUPON'"
|
||||||
|
>
|
||||||
|
电子卡券
|
||||||
|
</el-tag>
|
||||||
<el-tag
|
<el-tag
|
||||||
class="goods-show-tag goods-show-tag-physical"
|
class="goods-show-tag goods-show-tag-physical"
|
||||||
v-if="item.goodsType === 'VIRTUAL_GOODS'"
|
v-else-if="item.goodsType === 'VIRTUAL_GOODS'"
|
||||||
>
|
>
|
||||||
虚拟
|
虚拟
|
||||||
</el-tag>
|
</el-tag>
|
||||||
@@ -118,6 +125,7 @@ import { Message } from "@/utils/message";
|
|||||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||||
import GoodsClassNav from "@/components/nav/GoodsClassNav";
|
import GoodsClassNav from "@/components/nav/GoodsClassNav";
|
||||||
import * as apiGoods from "@/api/goods";
|
import * as apiGoods from "@/api/goods";
|
||||||
|
import { isBuyerLoggedIn, redirectToLogin } from "@/utils/buyerLogin";
|
||||||
export default {
|
export default {
|
||||||
name: "GoodsList",
|
name: "GoodsList",
|
||||||
beforeRouteEnter(to, from, next) {
|
beforeRouteEnter(to, from, next) {
|
||||||
@@ -194,12 +202,15 @@ export default {
|
|||||||
this.getGoodsList();
|
this.getGoodsList();
|
||||||
},
|
},
|
||||||
goGoodsDetail(skuId, goodsId) {
|
goGoodsDetail(skuId, goodsId) {
|
||||||
// 跳转商品详情
|
const location = {
|
||||||
let routeUrl = this.$router.resolve({
|
|
||||||
path: "/goodsDetail",
|
path: "/goodsDetail",
|
||||||
query: { skuId, goodsId },
|
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) {
|
goShopPage(id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@@ -281,13 +292,24 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.goods-show-tag {
|
.goods-show-tag {
|
||||||
height: 18px;
|
display: inline-flex;
|
||||||
width: 32px;
|
|
||||||
line-height: 14px;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
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 {
|
.goods-show-tag-self {
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ export default {
|
|||||||
mobile: "",
|
mobile: "",
|
||||||
},
|
},
|
||||||
verifyStatus: false, // 是否图片验证通过
|
verifyStatus: false, // 是否图片验证通过
|
||||||
|
codeSent: false, // 是否已成功发送短信验证码
|
||||||
ruleInline: {
|
ruleInline: {
|
||||||
// 验证规则
|
// 验证规则
|
||||||
username: [{required: true, message: "请输入用户名"}],
|
username: [{required: true, message: "请输入用户名"}],
|
||||||
@@ -237,7 +238,15 @@ export default {
|
|||||||
if (this.type) {
|
if (this.type) {
|
||||||
this.$refs.verify?.init();
|
this.$refs.verify?.init();
|
||||||
} else {
|
} 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) => {
|
apiLogin.smsLogin(data).then((res) => {
|
||||||
this.hideVerify();
|
this.hideVerify();
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
@@ -262,7 +271,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
Message.error(res.message);
|
Message.error(res.message);
|
||||||
}
|
}
|
||||||
});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -285,6 +294,7 @@ export default {
|
|||||||
sendSms(params).then((res) => {
|
sendSms(params).then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
Message.success("验证码发送成功");
|
Message.success("验证码发送成功");
|
||||||
|
this.codeSent = true;
|
||||||
let that = this;
|
let that = this;
|
||||||
this.interval = setInterval(() => {
|
this.interval = setInterval(() => {
|
||||||
that.time--;
|
that.time--;
|
||||||
@@ -292,6 +302,7 @@ export default {
|
|||||||
that.time = 60;
|
that.time = 60;
|
||||||
that.codeMsg = "重新发送";
|
that.codeMsg = "重新发送";
|
||||||
that.verifyStatus = false;
|
that.verifyStatus = false;
|
||||||
|
that.codeSent = false;
|
||||||
clearInterval(that.interval);
|
clearInterval(that.interval);
|
||||||
} else {
|
} else {
|
||||||
that.codeMsg = that.time;
|
that.codeMsg = that.time;
|
||||||
@@ -300,7 +311,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
Message.warning(res.message);
|
Message.warning(res.message);
|
||||||
}
|
}
|
||||||
});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
verifyChange(con) {
|
verifyChange(con) {
|
||||||
@@ -453,11 +464,19 @@ export default {
|
|||||||
this.$refs.formSms.resetFields();
|
this.$refs.formSms.resetFields();
|
||||||
}
|
}
|
||||||
this.verifyStatus = false;
|
this.verifyStatus = false;
|
||||||
|
this.codeSent = false;
|
||||||
this.hideVerify();
|
this.hideVerify();
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
this.codeMsg = "发送验证码";
|
this.codeMsg = "发送验证码";
|
||||||
this.time = 60;
|
this.time = 60;
|
||||||
},
|
},
|
||||||
|
"formSms.mobile"() {
|
||||||
|
this.codeSent = false;
|
||||||
|
this.verifyStatus = false;
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.codeMsg = "发送验证码";
|
||||||
|
this.time = 60;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -72,8 +72,8 @@
|
|||||||
<el-button @click="handleCancelOrder(order.sn)" type="danger" v-if="order.allowOperationVO.cancel" size="small">取消订单</el-button>
|
<el-button @click="handleCancelOrder(order.sn)" type="danger" v-if="order.allowOperationVO.cancel" size="small">取消订单</el-button>
|
||||||
<el-button @click="goPay(order.sn)" size="small" type="success" v-if="order.allowOperationVO.pay">去支付</el-button>
|
<el-button @click="goPay(order.sn)" size="small" type="success" v-if="order.allowOperationVO.pay">去支付</el-button>
|
||||||
<el-button @click="received(order.sn)" size="small" type="primary" v-if="order.allowOperationVO.rog">确认收货</el-button>
|
<el-button @click="received(order.sn)" size="small" type="primary" v-if="order.allowOperationVO.rog">确认收货</el-button>
|
||||||
<!-- 售后 -->
|
<!-- 售后(E_COUPON 卡密已交付不支持售后 FR-B-04) -->
|
||||||
<el-button v-if="order.groupAfterSaleStatus && (order.groupAfterSaleStatus.includes('NOT_APPLIED')|| order.groupAfterSaleStatus.includes('PART_AFTER_SALE'))"
|
<el-button v-if="order.orderType !== 'E_COUPON' && order.groupAfterSaleStatus && (order.groupAfterSaleStatus.includes('NOT_APPLIED')|| order.groupAfterSaleStatus.includes('PART_AFTER_SALE'))"
|
||||||
@click="applyAfterSale(order.orderItems)" size="small">申请售后</el-button>
|
@click="applyAfterSale(order.orderItems)" size="small">申请售后</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,8 +141,8 @@
|
|||||||
<el-button @click="handleCancelOrder(order.sn)" type="danger" v-if="order.allowOperationVO.cancel" size="small">取消订单</el-button>
|
<el-button @click="handleCancelOrder(order.sn)" type="danger" v-if="order.allowOperationVO.cancel" size="small">取消订单</el-button>
|
||||||
<el-button @click="goPay(order.sn)" size="small" type="success" v-if="order.allowOperationVO.pay">去支付</el-button>
|
<el-button @click="goPay(order.sn)" size="small" type="success" v-if="order.allowOperationVO.pay">去支付</el-button>
|
||||||
<el-button @click="received(order.sn)" size="small" type="primary" v-if="order.allowOperationVO.rog">确认收货</el-button>
|
<el-button @click="received(order.sn)" size="small" type="primary" v-if="order.allowOperationVO.rog">确认收货</el-button>
|
||||||
<!-- 售后 -->
|
<!-- 售后(E_COUPON 不支持买家售后 FR-B-04) -->
|
||||||
<el-button v-if="order.groupAfterSaleStatus && (order.groupAfterSaleStatus.includes('NOT_APPLIED')|| order.groupAfterSaleStatus.includes('PART_AFTER_SALE'))"
|
<el-button v-if="order.orderType !== 'E_COUPON' && order.groupAfterSaleStatus && (order.groupAfterSaleStatus.includes('NOT_APPLIED')|| order.groupAfterSaleStatus.includes('PART_AFTER_SALE'))"
|
||||||
@click="applyAfterSale(order.orderItems)" size="small">申请售后</el-button>
|
@click="applyAfterSale(order.orderItems)" size="small">申请售后</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
>取消订单</el-button>
|
>取消订单</el-button>
|
||||||
<el-button v-if="order.allowOperationVO.showLogistics || orderPackage.length > 0 || logistics" type="info" @click="logisticsList()" size="small">查看物流</el-button>
|
<el-button v-if="order.allowOperationVO.showLogistics || orderPackage.length > 0 || logistics" type="info" @click="logisticsList()" size="small">查看物流</el-button>
|
||||||
</el-card>
|
</el-card>
|
||||||
<p class="verificationCode" v-if="order.order.verificationCode">
|
<p class="verificationCode" v-if="order.order.verificationCode && !isECouponOrder">
|
||||||
核验码:<span>{{ order.order.verificationCode }}</span>
|
核验码:<span>{{ order.order.verificationCode }}</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="order-card">
|
<div class="order-card">
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
></el-step>
|
></el-step>
|
||||||
</el-steps>
|
</el-steps>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-card" v-if="order.order.deliveryMethod === 'LOGISTICS' && order.order.orderType !== 'VIRTUAL'">
|
<div class="order-card" v-if="order.order.deliveryMethod === 'LOGISTICS' && !isNonPhysicalOrder">
|
||||||
<h3>收货人信息</h3>
|
<h3>收货人信息</h3>
|
||||||
<p>收货人:{{ order.order.consigneeName }}</p>
|
<p>收货人:{{ order.order.consigneeName }}</p>
|
||||||
<p>手机号码:{{ $filters.secrecyMobile( order.order.consigneeMobile ) }}</p>
|
<p>手机号码:{{ $filters.secrecyMobile( order.order.consigneeMobile ) }}</p>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<p>支付方式:{{ order.paymentMethodValue }}</p>
|
<p>支付方式:{{ order.paymentMethodValue }}</p>
|
||||||
<p>付款状态:{{ order.payStatusValue }}</p>
|
<p>付款状态:{{ order.payStatusValue }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="order-card" v-if="!order.order.verificationCode && order.order.orderType !== 'VIRTUAL'">
|
<div class="order-card" v-if="!order.order.verificationCode && !isNonPhysicalOrder">
|
||||||
<h3>配送信息</h3>
|
<h3>配送信息</h3>
|
||||||
<p>配送方式:{{ order.deliveryMethodValue }}</p>
|
<p>配送方式:{{ order.deliveryMethodValue }}</p>
|
||||||
<p v-if="order.order.deliveryMethod === 'LOGISTICS'">配送状态:{{ order.deliverStatusValue }}</p>
|
<p v-if="order.order.deliveryMethod === 'LOGISTICS'">配送状态:{{ order.deliverStatusValue }}</p>
|
||||||
@@ -132,6 +132,51 @@
|
|||||||
</template>
|
</template>
|
||||||
<div v-else style="color: #999; margin-left: 5px">未开发票</div>
|
<div v-else style="color: #999; margin-left: 5px">未开发票</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 卡密信息(当前订单 orderItems;满赠赠品卡密在赠品子单详情查看) -->
|
||||||
|
<div class="order-card ecoupon-card-keys" v-if="hasCardKeySection">
|
||||||
|
<h3>卡密信息</h3>
|
||||||
|
<div
|
||||||
|
v-for="line in cardKeyFulfillLines"
|
||||||
|
:key="line.key"
|
||||||
|
class="ecoupon-fulfill-block"
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
v-if="line.status !== 'DELIVERED'"
|
||||||
|
:type="cardKeyFulfillAlertType(line.status)"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
:title="resolveCardKeyFulfillMessage(line)"
|
||||||
|
class="mb_10"
|
||||||
|
/>
|
||||||
|
<template v-else-if="line.cardKeys.length">
|
||||||
|
<div class="ecoupon-card-keys-mobile">
|
||||||
|
<el-button type="primary" @click="cardKeyDialogVisible = true">查看卡密</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="ecoupon-card-keys-pc">
|
||||||
|
<el-table border :data="lineCardKeyRows(line)" style="width: 100%">
|
||||||
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
|
<el-table-column prop="cardNo" label="卡号" min-width="140" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="cardSecret" label="卡密" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="allocatedTime" label="发卡时间" width="170" />
|
||||||
|
<el-table-column label="操作" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button v-if="row" link type="primary" @click="copyCardKey(row)">复制</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</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">
|
||||||
<div class="shop-name">
|
<div class="shop-name">
|
||||||
@@ -144,8 +189,8 @@
|
|||||||
<th width="15%">货号</th>
|
<th width="15%">货号</th>
|
||||||
<th width="10%">单价</th>
|
<th width="10%">单价</th>
|
||||||
<th width="5%">数量</th>
|
<th width="5%">数量</th>
|
||||||
<th width="10%">退款状态</th>
|
<th width="10%" v-if="!isECouponOrder">退款状态</th>
|
||||||
<th width="10%">实际退款金额</th>
|
<th width="10%" v-if="!isECouponOrder">实际退款金额</th>
|
||||||
<th width="10%">小计</th>
|
<th width="10%">小计</th>
|
||||||
<th width="10%">操作</th>
|
<th width="10%">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -170,8 +215,8 @@
|
|||||||
<td>{{ goods.id }}</td>
|
<td>{{ goods.id }}</td>
|
||||||
<td>{{ $filters.unitPrice(goods.goodsPrice, "¥") }}</td>
|
<td>{{ $filters.unitPrice(goods.goodsPrice, "¥") }}</td>
|
||||||
<td>{{ goods.num }}</td>
|
<td>{{ goods.num }}</td>
|
||||||
<td>{{refundPriceList(goods.isRefund)}}</td>
|
<td v-if="!isECouponOrder">{{refundPriceList(goods.isRefund)}}</td>
|
||||||
<td>{{ $filters.unitPrice(goods.refundPrice, "¥") }}</td>
|
<td v-if="!isECouponOrder">{{ $filters.unitPrice(goods.refundPrice, "¥") }}</td>
|
||||||
<td>{{ $filters.unitPrice((goods.goodsPrice * goods.num), "¥") }}</td>
|
<td>{{ $filters.unitPrice((goods.goodsPrice * goods.num), "¥") }}</td>
|
||||||
<td class="order-item-actions">
|
<td class="order-item-actions">
|
||||||
<el-button
|
<el-button
|
||||||
@@ -192,8 +237,9 @@
|
|||||||
>
|
>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="
|
v-if="
|
||||||
goods.afterSaleStatus.includes('NOT_APPLIED') ||
|
!isECouponOrder &&
|
||||||
goods.afterSaleStatus.includes('PART_AFTER_SALE')
|
(goods.afterSaleStatus.includes('NOT_APPLIED') ||
|
||||||
|
goods.afterSaleStatus.includes('PART_AFTER_SALE'))
|
||||||
"
|
"
|
||||||
@click="applyAfterSale(goods.sn)"
|
@click="applyAfterSale(goods.sn)"
|
||||||
type="default"
|
type="default"
|
||||||
@@ -213,7 +259,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<span>商品总价:</span><span>{{ $filters.unitPrice(order.order.goodsPrice, "¥") }}</span><br />
|
<span>商品总价:</span><span>{{ $filters.unitPrice(order.order.goodsPrice, "¥") }}</span><br />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="order.order.orderType !== 'VIRTUAL'">
|
<div v-if="!isECouponOrder">
|
||||||
<span>运费:</span><span>+{{ $filters.unitPrice(order.order.freightPrice, "¥") }}</span><br />
|
<span>运费:</span><span>+{{ $filters.unitPrice(order.order.freightPrice, "¥") }}</span><br />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="order.order.priceDetailDTO.couponPrice">
|
<div v-if="order.order.priceDetailDTO.couponPrice">
|
||||||
@@ -319,6 +365,19 @@
|
|||||||
<el-button @click="logisticsModal = false">取消</el-button>
|
<el-button @click="logisticsModal = false">取消</el-button>
|
||||||
</div></template>
|
</div></template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 移动端查看卡密 -->
|
||||||
|
<el-dialog v-model="cardKeyDialogVisible" title="卡密信息" width="92%" class="card-key-mobile-dialog">
|
||||||
|
<div v-for="(row, idx) in ecouponCardKeyRows" :key="idx" class="card-key-mobile-item">
|
||||||
|
<p><strong>卡号:</strong>{{ row.cardNo }}</p>
|
||||||
|
<p><strong>卡密:</strong>{{ row.cardSecret }}</p>
|
||||||
|
<el-button size="small" link type="primary" @click="copyCardKey(row)">复制</el-button>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="copyAllCardKeys">复制全部</el-button>
|
||||||
|
<el-button type="primary" @click="cardKeyDialogVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</UserCenterLayout>
|
</UserCenterLayout>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -333,6 +392,19 @@ 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,
|
||||||
|
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 {
|
export default {
|
||||||
name: "order-detail",
|
name: "order-detail",
|
||||||
data() {
|
data() {
|
||||||
@@ -340,6 +412,7 @@ export default {
|
|||||||
order: {}, // 订单详情数据
|
order: {}, // 订单详情数据
|
||||||
progressList: [], // 订单流程
|
progressList: [], // 订单流程
|
||||||
logistics: "", // 物流数据
|
logistics: "", // 物流数据
|
||||||
|
cardKeyDialogVisible: false,
|
||||||
cancelParams: {
|
cancelParams: {
|
||||||
// 取消售后参数
|
// 取消售后参数
|
||||||
orderSn: "",
|
orderSn: "",
|
||||||
@@ -352,7 +425,63 @@ export default {
|
|||||||
logisticsModal: false,
|
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: {
|
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) {
|
isVatSpecialReceipt (receipt) {
|
||||||
if (!receipt) return false;
|
if (!receipt) return false;
|
||||||
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
|
const rt = receipt.receiptType != null ? String(receipt.receiptType).trim() : "";
|
||||||
@@ -660,6 +789,38 @@ table {
|
|||||||
color: $theme_color;
|
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 {
|
.progress {
|
||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
<el-divider />
|
<el-divider />
|
||||||
<div class="content width_1200_auto">
|
<div class="content width_1200_auto">
|
||||||
<!-- 收货地址 -->
|
<!-- 收货地址 -->
|
||||||
<div class="address" v-if="selectedDeliverMethod === 'LOGISTICS' && goodsType !== 'VIRTUAL_GOODS'">
|
<!-- 收货地址(E_COUPON / VIRTUAL 免地址,见 isVirtualLikeCheckout) -->
|
||||||
|
<div class="address" v-if="selectedDeliverMethod === 'LOGISTICS' && !isVirtualLikeCheckout">
|
||||||
<div class="card-head">
|
<div class="card-head">
|
||||||
<span>收货人信息</span>
|
<span>收货人信息</span>
|
||||||
<span @click="goAddressManage">管理收货人地址</span>
|
<span @click="goAddressManage">管理收货人地址</span>
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-content" v-if="goodsType !== 'VIRTUAL_GOODS'">
|
<div class="goods-content" v-if="!isVirtualLikeCheckout">
|
||||||
<div class="card-head mt_20 mb_20">
|
<div class="card-head mt_20 mb_20">
|
||||||
<span>配送方式</span>
|
<span>配送方式</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,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>开企业抬头发票须填写纳税人识别号,以免影响报销
|
||||||
@@ -283,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>
|
||||||
@@ -328,6 +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, shouldSkipPaymentPage } from "@/constants/goodsType";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Pay",
|
name: "Pay",
|
||||||
@@ -377,6 +379,14 @@ export default {
|
|||||||
);
|
);
|
||||||
return Number.isFinite(n) ? n : 0;
|
return Number.isFinite(n) ? n : 0;
|
||||||
},
|
},
|
||||||
|
/** 电子卡券结算:免地址、隐藏发票;优惠券/礼品卡/促销明细可用(M-02) */
|
||||||
|
isECouponCheckout() {
|
||||||
|
return isECoupon(this.goodsType);
|
||||||
|
},
|
||||||
|
/** 虚拟型结算(核销虚拟 + 电子卡券):均不需要收货地址 */
|
||||||
|
isVirtualLikeCheckout() {
|
||||||
|
return this.goodsType === "VIRTUAL_GOODS" || this.isECouponCheckout;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -845,19 +855,24 @@ 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({
|
|
||||||
path: "/payment",
|
|
||||||
query: { orderType: "TRADE", sn: res.result.sn },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
this.$router.push({
|
||||||
|
path: "/payment",
|
||||||
|
query: { orderType: "TRADE", sn: res.result.sn },
|
||||||
|
});
|
||||||
|
} else if (res.message) {
|
||||||
|
Message.warning(res.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((err) => {
|
||||||
Spin.hide();
|
Spin.hide();
|
||||||
|
if (err?.message && !err?.data?.message) {
|
||||||
|
Message.error(err.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 优惠券可用范围
|
// 优惠券可用范围
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import {tradeDetail, pay} from '@/api/pay.js';
|
import { tradeDetail, pay } from '@/api/pay.js';
|
||||||
import { Message, Modal } from "@/utils/message";
|
import { Message, Modal } from "@/utils/message";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -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'});
|
||||||
@@ -102,14 +106,21 @@ export default {
|
|||||||
title: '支付确认',
|
title: '支付确认',
|
||||||
content: '确认使用余额支付吗?',
|
content: '确认使用余额支付吗?',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
pay(params).then(res => {
|
return pay(params)
|
||||||
if (res.success) {
|
.then((res) => {
|
||||||
Message.success(res.message)
|
if (res.success) {
|
||||||
this.$router.push('/payDone');
|
Message.success(res.message || '支付成功');
|
||||||
} else {
|
this.$router.push('/payDone');
|
||||||
Message.warning(res.message)
|
} else {
|
||||||
}
|
Message.warning(res.message || '支付失败');
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// 支付失败兜底:业务错误(如 CARD_KEY_STORE_SELL_FORBIDDEN)通常已由 axios 拦截器提示
|
||||||
|
if (err?.message && !err?.data?.message) {
|
||||||
|
Message.error(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,19 +11,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="pay-way">{{params.paymentMethod === 'ALIPAY' ? '支付宝支付' : '微信支付'}}</div>
|
<div class="pay-way">{{ payWayName }}支付</div>
|
||||||
<div class="qrcode">
|
<div class="qrcode">
|
||||||
<div style="width:200px;height:200px;border:1px solid #eee;">
|
<div class="qrcode-box">
|
||||||
<vue-qr :text="qrcode" :margin="0" colorDark="#000" colorLight="#fff" :size="200"></vue-qr>
|
<div v-if="payLoading" class="qrcode-status">正在生成支付二维码...</div>
|
||||||
|
<div v-else-if="payError" class="qrcode-status qrcode-status--error">
|
||||||
|
<p>{{ payError }}</p>
|
||||||
|
<p class="qrcode-status-tip">您可点击「重新支付」重试,或返回选择其他支付方式</p>
|
||||||
|
</div>
|
||||||
|
<vue-qr
|
||||||
|
v-else
|
||||||
|
:text="qrcode"
|
||||||
|
:margin="0"
|
||||||
|
colorDark="#000"
|
||||||
|
colorLight="#fff"
|
||||||
|
:size="200"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="intro">
|
<div v-if="!payError" class="intro">
|
||||||
<el-icon><Iphone /></el-icon> 请使用{{params.paymentMethod === 'ALIPAY' ? '支付宝' : '微信'}}扫码付款
|
<el-icon><Iphone /></el-icon> 请使用{{ payWayName }}扫码付款
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-div">
|
<div class="btn-div">
|
||||||
<p class="mb_10">支付成功后自动跳转,如未跳转请点击按钮手动跳转。。。</p>
|
<p class="mb_10">支付成功后自动跳转,如未跳转请点击按钮手动跳转。。。</p>
|
||||||
<div>
|
<div>
|
||||||
<el-button @click="handlePay">重新支付</el-button>
|
<el-button :loading="payLoading" @click="handlePay">重新支付</el-button>
|
||||||
<el-button type="success" @click="$router.push('/payDone')">支付成功</el-button>
|
<el-button type="success" @click="$router.push('/payDone')">支付成功</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,47 +53,79 @@ export default {
|
|||||||
components: { vueQr, Iphone },
|
components: { vueQr, Iphone },
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
qrcode: '', // 二维码
|
qrcode: '',
|
||||||
params: this.$route.query, // 参数
|
params: this.$route.query,
|
||||||
interval: null, // 定时器
|
interval: null,
|
||||||
num: 0 // 商品数
|
num: 0,
|
||||||
|
payLoading: false,
|
||||||
|
payError: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
computed: {
|
||||||
// 获取支付二维码
|
payWayName () {
|
||||||
handlePay () {
|
return this.params.paymentMethod === 'ALIPAY' ? '支付宝' : '微信';
|
||||||
const params = this.$route.query;
|
|
||||||
pay(params).then(res => {
|
|
||||||
if (res.success) {
|
|
||||||
this.qrcode = res.result;
|
|
||||||
this.num = 0;
|
|
||||||
this.interval = setInterval(this.callback, 5000);
|
|
||||||
} else {
|
|
||||||
Message.error(res.message)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
callback () { // 支付回调接口
|
},
|
||||||
this.num++;
|
methods: {
|
||||||
if (this.num >= 7) {
|
formatPayError (rawMessage) {
|
||||||
|
const message = (rawMessage || '').trim();
|
||||||
|
if (!message || message === '支付业务异常,请稍后重试') {
|
||||||
|
return `${this.payWayName}支付暂不可用,请稍后重试或选择其他支付方式`;
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
clearPayPoll () {
|
||||||
|
if (this.interval) {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
this.interval = null;
|
this.interval = null;
|
||||||
}
|
}
|
||||||
let params = JSON.parse(JSON.stringify(this.$route.query));
|
},
|
||||||
|
handlePay () {
|
||||||
|
this.clearPayPoll();
|
||||||
|
const params = this.$route.query;
|
||||||
|
this.payLoading = true;
|
||||||
|
this.payError = '';
|
||||||
|
this.qrcode = '';
|
||||||
|
pay(params, { skipMessage: true })
|
||||||
|
.then((res) => {
|
||||||
|
this.payLoading = false;
|
||||||
|
if (res.success && res.result) {
|
||||||
|
this.qrcode = res.result;
|
||||||
|
this.num = 0;
|
||||||
|
this.interval = setInterval(this.callback, 5000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.payError = this.formatPayError(res.message);
|
||||||
|
Message.error(this.payError);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.payLoading = false;
|
||||||
|
this.payError = this.formatPayError(err?.message || err?.data?.message);
|
||||||
|
Message.error(this.payError);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
callback () {
|
||||||
|
this.num++;
|
||||||
|
if (this.num >= 7) {
|
||||||
|
this.clearPayPoll();
|
||||||
|
}
|
||||||
|
const params = JSON.parse(JSON.stringify(this.$route.query));
|
||||||
delete params.paymentMethod;
|
delete params.paymentMethod;
|
||||||
delete params.paymentClient;
|
delete params.paymentClient;
|
||||||
payCallback(params).then(res => {
|
payCallback(params).then((res) => {
|
||||||
if (res.result) {
|
if (res.result) {
|
||||||
clearInterval(this.interval);
|
this.clearPayPoll();
|
||||||
this.interval = null;
|
this.$router.push({ path: '/payDone', query: { orderType: this.$route.query.orderType } });
|
||||||
this.$router.push({path: '/payDone', query: {orderType: this.$route.query.orderType}});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.handlePay();
|
this.handlePay();
|
||||||
}
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
this.clearPayPoll();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -125,6 +169,33 @@ export default {
|
|||||||
margin: 30px 0 0 70px;
|
margin: 30px 0 0 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qrcode-box {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-status {
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-status--error {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-status-tip {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
background-color: #ff7674;
|
background-color: #ff7674;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -143,6 +214,9 @@ export default {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409eff;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.price {
|
.price {
|
||||||
@@ -153,4 +227,7 @@ export default {
|
|||||||
.head-right {
|
.head-right {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.mb_10 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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,22 +25,22 @@
|
|||||||
</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">
|
||||||
<p>{{ goodsSku.goodsName }}</p>
|
<div class="item-detail-title-row">
|
||||||
|
<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>
|
||||||
<span class="item-price">{{goodsMsg.points}}</span>
|
<span class="item-price">{{ goodsMsg.points }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,25 +51,37 @@
|
|||||||
<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>
|
||||||
<div class="item-select-row">
|
<div class="item-select-row">
|
||||||
<span class="inventory"> {{goodsSku.weight}}kg</span>
|
<span class="inventory"> {{ goodsSku.weight }}kg</span>
|
||||||
</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,101 +94,140 @@
|
|||||||
|
|
||||||
<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",
|
||||||
beforeRouteEnter (to, from, next) {
|
components: { Search },
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
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 // 提交加载状态
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
computed: {
|
||||||
// 获取积分商品详情
|
isECouponSku() {
|
||||||
getGoodsDetail () {
|
return isECoupon(this.goodsSku.goodsType);
|
||||||
this.isLoading = true;
|
|
||||||
pointGoodsDetail(this.$route.query.id).then((res) => {
|
|
||||||
this.isLoading = false;
|
|
||||||
if (res.success) {
|
|
||||||
this.goodsMsg = res.result;
|
|
||||||
this.goodsSku = res.result.goodsSku
|
|
||||||
} else {
|
|
||||||
Message.error(res.message)
|
|
||||||
this.$router.push('/')
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
this.$router.push('/')
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
pointBuy () {
|
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: {
|
||||||
|
getGoodsDetail() {
|
||||||
|
this.isLoading = true;
|
||||||
|
pointGoodsDetail(this.$route.query.id)
|
||||||
|
.then((res) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.goodsMsg = res.result;
|
||||||
|
this.goodsSku = res.result.goodsSku || {};
|
||||||
|
this.syncCountWithQuantity();
|
||||||
|
} else {
|
||||||
|
Message.error(res.message);
|
||||||
|
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() {
|
||||||
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)
|
||||||
this.loading = false;
|
.then((res) => {
|
||||||
if (res.success) {
|
this.loading = false;
|
||||||
this.$router.push({path: '/pay', query: {way: params.cartType}});
|
if (res.success) {
|
||||||
} else {
|
this.$router.push({ path: "/pay", query: { way: params.cartType } });
|
||||||
Message.warning(res.message);
|
} else {
|
||||||
}
|
Message.warning(res.message);
|
||||||
}).catch(() => {
|
}
|
||||||
this.loading = false;
|
})
|
||||||
});
|
.catch(() => {
|
||||||
|
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;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.cate-container{
|
.cate-container {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
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,42 +298,31 @@ 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;
|
||||||
>*{
|
> * {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,57 +331,23 @@ 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) {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
@click="goGoodsDetail(item.id)"
|
@click="goGoodsDetail(item.id)"
|
||||||
>
|
>
|
||||||
<div class="goods-show-img">
|
<div class="goods-show-img">
|
||||||
<img :src="item.goodsSku.thumbnail" />
|
<img :src="itemThumbnail(item)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-show-price">
|
<div class="goods-show-price">
|
||||||
<span>
|
<span>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-show-detail">
|
<div class="goods-show-detail">
|
||||||
<span>{{ item.goodsSku.goodsName }}</span>
|
<span>{{ itemGoodsName(item) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="goods-show-num">
|
<div class="goods-show-num">
|
||||||
已有<span>{{ item.commentNum || 0 }}</span>人评价
|
已有<span>{{ item.commentNum || 0 }}</span>人评价
|
||||||
@@ -70,6 +70,13 @@ export default {
|
|||||||
this.getCate()
|
this.getCate()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** 列表 API 返回 PointsGoods(thumbnail/goodsName 在顶层);详情 API 才有 goodsSku */
|
||||||
|
itemThumbnail (item) {
|
||||||
|
return item?.goodsSku?.thumbnail || item?.thumbnail || ''
|
||||||
|
},
|
||||||
|
itemGoodsName (item) {
|
||||||
|
return item?.goodsSku?.goodsName || item?.goodsName || ''
|
||||||
|
},
|
||||||
getList () { // 获取列表
|
getList () { // 获取列表
|
||||||
pointGoods(this.params).then(res => {
|
pointGoods(this.params).then(res => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ service.interceptors.request.use(
|
|||||||
|
|
||||||
const accessToken = Storage.getItem("accessToken");
|
const accessToken = Storage.getItem("accessToken");
|
||||||
if (accessToken && (config.needToken || config.optionalToken)) {
|
if (accessToken && (config.needToken || config.optionalToken)) {
|
||||||
config.headers["accessToken"] = accessToken;
|
|
||||||
try {
|
try {
|
||||||
const jwtData = JSON.parse(
|
const jwtData = JSON.parse(
|
||||||
decodeURIComponent(
|
decodeURIComponent(
|
||||||
@@ -80,12 +79,23 @@ service.interceptors.request.use(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (jwtData.exp < Math.round(new Date() / 1000)) {
|
const expired = jwtData.exp < Math.round(new Date() / 1000);
|
||||||
refresh({ response: { config } });
|
if (expired) {
|
||||||
|
if (config.needToken) {
|
||||||
|
return refresh({ response: { config } });
|
||||||
|
}
|
||||||
|
// 公开接口:丢弃过期 token,匿名继续请求,避免浏览商品时被引导登录
|
||||||
|
Storage.removeItem("accessToken");
|
||||||
|
Storage.removeItem("refreshToken");
|
||||||
|
} else {
|
||||||
|
config.headers["accessToken"] = accessToken;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
Storage.removeItem("accessToken");
|
Storage.removeItem("accessToken");
|
||||||
Storage.removeItem("refreshToken");
|
Storage.removeItem("refreshToken");
|
||||||
|
if (config.needToken) {
|
||||||
|
return refresh({ response: { config } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
buyer/src/utils/buyerLogin.js
Normal file
59
buyer/src/utils/buyerLogin.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 买家端登录态与商品详情跳转校验
|
||||||
|
*
|
||||||
|
* @author Mike
|
||||||
|
* @date 2026-08-05
|
||||||
|
*/
|
||||||
|
import storage from "@/plugins/storage";
|
||||||
|
|
||||||
|
export function isBuyerLoggedIn() {
|
||||||
|
const rawUserInfo = storage.getItem("userInfo");
|
||||||
|
if (rawUserInfo) {
|
||||||
|
try {
|
||||||
|
return !!JSON.parse(rawUserInfo).username;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !!storage.getItem("accessToken");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseInternalLink(url) {
|
||||||
|
const [path, search = ""] = url.split("?");
|
||||||
|
const query = {};
|
||||||
|
if (search) {
|
||||||
|
new URLSearchParams(search).forEach((value, key) => {
|
||||||
|
query[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { path, query };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGoodsDetailPath(path) {
|
||||||
|
return path === "/goodsDetail";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function redirectToLogin(router, location) {
|
||||||
|
router.push({
|
||||||
|
path: "/login",
|
||||||
|
query: {
|
||||||
|
rePath: location.path,
|
||||||
|
query: JSON.stringify(location.query || {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 内部链接跳转;访问商品详情前校验登录(首页装修、列表等共用) */
|
||||||
|
export function openLink(url, router) {
|
||||||
|
if (!url) return;
|
||||||
|
if (url.charAt(0) !== "/") {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const location = parseInternalLink(url);
|
||||||
|
if (!isBuyerLoggedIn() && isGoodsDetailPath(location.path)) {
|
||||||
|
redirectToLogin(router, location);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.open(router.resolve(url).href, "_blank");
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
19
manager/src/constants/cardKey.js
Normal file
19
manager/src/constants/cardKey.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 卡密商品(E_COUPON)— 平台端订单展示用常量
|
||||||
|
*
|
||||||
|
* 平台不管理卡池明文(S-04);本模块仅用于订单详情卡密状态文案。
|
||||||
|
*
|
||||||
|
* @author Mike
|
||||||
|
* @date 2026-07-31
|
||||||
|
*/
|
||||||
|
/** 卡密状态文案(与后端 CardKeyStatusEnum 一致) */
|
||||||
|
export const CARD_KEY_STATUS_TEXT = {
|
||||||
|
UNUSED: "未使用",
|
||||||
|
ALLOCATED: "已分配",
|
||||||
|
RESERVED: "已预占",
|
||||||
|
VOIDED: "已作废",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function formatCardKeyStatus(status) {
|
||||||
|
return CARD_KEY_STATUS_TEXT[status] || status || "—";
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
@@ -53,6 +53,8 @@
|
|||||||
>
|
>
|
||||||
<el-option label="实物商品" value="PHYSICAL_GOODS" />
|
<el-option label="实物商品" value="PHYSICAL_GOODS" />
|
||||||
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
|
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
|
||||||
|
<!-- E_COUPON:卡密商品;平台无卡池管理权限(S-04) -->
|
||||||
|
<el-option label="电子卡券" value="E_COUPON" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="商品分组" prop="groupId">
|
<el-form-item label="商品分组" prop="groupId">
|
||||||
@@ -385,7 +387,8 @@ export default {
|
|||||||
goodsTypeText(v) {
|
goodsTypeText(v) {
|
||||||
if (v === "PHYSICAL_GOODS") return "实物商品";
|
if (v === "PHYSICAL_GOODS") return "实物商品";
|
||||||
if (v === "VIRTUAL_GOODS") return "虚拟商品";
|
if (v === "VIRTUAL_GOODS") return "虚拟商品";
|
||||||
return "电子卡券";
|
if (v === "E_COUPON") return "电子卡券"; // 卡密商品
|
||||||
|
return v || "—";
|
||||||
},
|
},
|
||||||
marketEnableText(v) {
|
marketEnableText(v) {
|
||||||
if (v === "DOWN") return "下架";
|
if (v === "DOWN") return "下架";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<el-button v-if="allowOperation.cancel" plain type="warning" @click="orderCancel">订单取消</el-button>
|
<el-button v-if="allowOperation.cancel" plain type="warning" @click="orderCancel">订单取消</el-button>
|
||||||
<el-button v-if="orderInfo.order.orderStatus === 'UNPAID'" type="primary" @click="confirmPrice">收款</el-button>
|
<el-button v-if="orderInfo.order.orderStatus === 'UNPAID'" type="primary" @click="confirmPrice">收款</el-button>
|
||||||
<el-button plain @click="orderLog">订单日志</el-button>
|
<el-button plain @click="orderLog">订单日志</el-button>
|
||||||
<el-button v-if="$route.query.orderType != 'VIRTUAL'" plain type="primary" style="float:right;" @click="printOrder">打印发货单</el-button>
|
<el-button v-if="!isNonPhysicalOrder" plain type="primary" style="float:right;" @click="printOrder">打印发货单</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card class="mt_10 clearfix">
|
<el-card class="mt_10 clearfix">
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 36%; float: left">
|
<div style="width: 36%; float: left">
|
||||||
<div class="div-item">
|
<div class="div-item" v-if="!isECouponOrder && orderInfo.order.deliveryMethod != 'SELF_PICK_UP'">
|
||||||
<div class="div-item-left">收货信息:</div>
|
<div class="div-item-left">收货信息:</div>
|
||||||
<div class="div-item-right">
|
<div class="div-item-right">
|
||||||
{{ orderInfo.order.consigneeName }}
|
{{ orderInfo.order.consigneeName }}
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<div class="div-item" v-if="$route.query.orderType != 'VIRTUAL'">
|
<div class="div-item" v-if="!isNonPhysicalOrder">
|
||||||
<div class="div-item-left">配送方式:</div>
|
<div class="div-item-left">配送方式:</div>
|
||||||
<div class="div-item-right">
|
<div class="div-item-right">
|
||||||
{{ orderInfo.deliveryMethodValue }}
|
{{ orderInfo.deliveryMethodValue }}
|
||||||
@@ -217,6 +217,41 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
|
<!-- E_COUPON:平台订单详情只读展示卡密,无卡池代管(S-04) -->
|
||||||
|
<div v-if="isECouponOrder" class="ecoupon-card-keys mt_10">
|
||||||
|
<h4>卡密信息</h4>
|
||||||
|
<el-alert
|
||||||
|
v-if="!ecouponCardKeyDelivered"
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
title="卡密尚未发放"
|
||||||
|
class="mb_10"
|
||||||
|
/>
|
||||||
|
<el-table
|
||||||
|
v-else-if="ecouponCardKeyRows.length"
|
||||||
|
border
|
||||||
|
:data="ecouponCardKeyRows"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
|
<el-table-column prop="cardNo" label="卡号" min-width="140" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="cardSecret" label="卡密" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="allocatedTime" label="发卡时间" width="170" />
|
||||||
|
<el-table-column label="状态" width="90" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row">{{ formatCardKeyStatus(row.status || 'ALLOCATED') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button v-if="row" link type="primary" @click="copyCardKey(row)">复制</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="goods-total">
|
<div class="goods-total">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@@ -280,7 +315,7 @@
|
|||||||
<span class="label" v-if="typeList.length == 1 && index == 0" style="font-size:10px !important;"><a @click="gotoHomes" style="display: inline-block;border-top: 1px dashed;border-bottom: 1px dashed;color:black;width:80px;">{{item.promotionName}}:</a><span class="op-split">|</span>
|
<span class="label" v-if="typeList.length == 1 && index == 0" style="font-size:10px !important;"><a @click="gotoHomes" style="display: inline-block;border-top: 1px dashed;border-bottom: 1px dashed;color:black;width:80px;">{{item.promotionName}}:</a><span class="op-split">|</span>
|
||||||
<span class="txt" v-if="typeList.length == 1 && index == 0" style="border-top: 1px dashed;border-bottom: 1px dashed;font-size:10px !important;">¥{{ unitPrice(item.discountPrice) }}</span>
|
<span class="txt" v-if="typeList.length == 1 && index == 0" style="border-top: 1px dashed;border-bottom: 1px dashed;font-size:10px !important;">¥{{ unitPrice(item.discountPrice) }}</span>
|
||||||
</li> -->
|
</li> -->
|
||||||
<li>
|
<li v-if="!isECouponOrder">
|
||||||
<span class="label">运费:</span>
|
<span class="label">运费:</span>
|
||||||
<span class="txt">{{
|
<span class="txt">{{
|
||||||
unitPrice(orderInfo.order.freightPrice, "¥")
|
unitPrice(orderInfo.order.freightPrice, "¥")
|
||||||
@@ -440,6 +475,7 @@ import vueQr from "vue-qr";
|
|||||||
import { printElement } from "@/utils/print";
|
import { printElement } from "@/utils/print";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { unitPrice, clientTypeWay } from "@/utils/filters";
|
import { unitPrice, clientTypeWay } from "@/utils/filters";
|
||||||
|
import { formatCardKeyStatus } from "@/constants/cardKey";
|
||||||
export default {
|
export default {
|
||||||
name: "orderList",
|
name: "orderList",
|
||||||
components: {
|
components: {
|
||||||
@@ -447,6 +483,32 @@ export default {
|
|||||||
multipleMap,
|
multipleMap,
|
||||||
"vue-qr": vueQr,
|
"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 () {
|
data () {
|
||||||
return {
|
return {
|
||||||
typeList: [],
|
typeList: [],
|
||||||
@@ -546,6 +608,23 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
unitPrice,
|
unitPrice,
|
||||||
clientTypeWay,
|
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) {
|
getPromotionText(row) {
|
||||||
let resultText = "";
|
let resultText = "";
|
||||||
if (row && row.promotionType) {
|
if (row && row.promotionType) {
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
65
seller/src/api/cardKey.js
Normal file
65
seller/src/api/cardKey.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 卡密商品(E_COUPON)— 商家端卡池 HTTP 封装
|
||||||
|
*
|
||||||
|
* Base Path:/store/goods/card-key(axios 已带 /store 前缀)
|
||||||
|
* 对应 card-key-goods-api.md API-S-01a ~ S-07;导出为同步文件流(S-08)。
|
||||||
|
*
|
||||||
|
* @author Mike
|
||||||
|
* @date 2026-07-31
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
getRequest,
|
||||||
|
postRequestWithNoForm,
|
||||||
|
putRequest,
|
||||||
|
uploadFileRequest,
|
||||||
|
} from "@/libs/axios";
|
||||||
|
import { downloadBlob } from "@/utils/downloadBlob";
|
||||||
|
|
||||||
|
/** API-S-01a 下载卡密导入模板(同步文件流) */
|
||||||
|
export const downloadImportTemplateBlob = () => {
|
||||||
|
return getRequest("/goods/card-key/import/template", {}, "blob");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadImportTemplate = async () => {
|
||||||
|
const blob = await downloadImportTemplateBlob();
|
||||||
|
downloadBlob(blob, "card-key-import-template.xlsx");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** API-S-01 批量导入卡密 */
|
||||||
|
export const importCardKey = (skuId, file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("skuId", skuId);
|
||||||
|
formData.append("file", file);
|
||||||
|
return uploadFileRequest("/goods/card-key/import", formData);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** API-S-02 单条新增卡密(后端 @RequestBody,须 application/json) */
|
||||||
|
export const addCardKey = (data) => {
|
||||||
|
return postRequestWithNoForm("/goods/card-key/add", data);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** API-S-03 卡池分页列表 */
|
||||||
|
export const getCardKeyList = (params) => {
|
||||||
|
return getRequest("/goods/card-key/list", params);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** API-S-04 作废卡密 */
|
||||||
|
export const voidCardKey = (id) => {
|
||||||
|
return putRequest(`/goods/card-key/void/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** API-S-05 卡池状态统计 */
|
||||||
|
export const getCardKeyStats = (skuId) => {
|
||||||
|
return getRequest(`/goods/card-key/stats/${skuId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** API-S-07 卡池导出(同步文件流) */
|
||||||
|
export const exportCardKeyBlob = (params) => {
|
||||||
|
return getRequest("/goods/card-key/export", params, "blob");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportCardKey = async (params, skuId) => {
|
||||||
|
const blob = await exportCardKeyBlob(params);
|
||||||
|
const ts = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
||||||
|
downloadBlob(blob, `card-key-${skuId || "export"}-${ts}.xlsx`);
|
||||||
|
};
|
||||||
45
seller/src/constants/cardKey.js
Normal file
45
seller/src/constants/cardKey.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 卡密商品(E_COUPON)— 商家端常量
|
||||||
|
*
|
||||||
|
* 卡池状态 UNUSED / ALLOCATED / VOIDED;Tab 与列表筛选、标签色映射。
|
||||||
|
* 需求:card-key-goods-api.md §2.1~2.3
|
||||||
|
*
|
||||||
|
* @author Mike
|
||||||
|
* @date 2026-07-31
|
||||||
|
*/
|
||||||
|
/** 卡密状态枚举(与后端 CardKeyStatusEnum 一致) */
|
||||||
|
export const CARD_KEY_STATUS = {
|
||||||
|
UNUSED: "UNUSED",
|
||||||
|
ALLOCATED: "ALLOCATED",
|
||||||
|
RESERVED: "RESERVED",
|
||||||
|
VOIDED: "VOIDED",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CARD_KEY_STATUS_TEXT = {
|
||||||
|
UNUSED: "未使用",
|
||||||
|
ALLOCATED: "已分配",
|
||||||
|
RESERVED: "已预占",
|
||||||
|
VOIDED: "已作废",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CARD_KEY_STATUS_TAG = {
|
||||||
|
UNUSED: "success",
|
||||||
|
ALLOCATED: "info",
|
||||||
|
RESERVED: "warning",
|
||||||
|
VOIDED: "danger",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Tab:全部 + 各状态 */
|
||||||
|
export const CARD_KEY_STATUS_TABS = [
|
||||||
|
{ key: "", label: "全部" },
|
||||||
|
{ key: CARD_KEY_STATUS.UNUSED, label: "未使用" },
|
||||||
|
{ key: CARD_KEY_STATUS.RESERVED, label: "已预占" },
|
||||||
|
{ key: CARD_KEY_STATUS.ALLOCATED, label: "已分配" },
|
||||||
|
{ key: CARD_KEY_STATUS.VOIDED, label: "已作废" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const E_COUPON_GOODS_TYPE = "E_COUPON";
|
||||||
|
|
||||||
|
export function formatCardKeyStatus(status) {
|
||||||
|
return CARD_KEY_STATUS_TEXT[status] || status || "—";
|
||||||
|
}
|
||||||
144
seller/src/constants/goodsType.js
Normal file
144
seller/src/constants/goodsType.js
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* 卡密商品(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -64,6 +64,13 @@ export const otherRouter = {
|
|||||||
name: "goods-draft-operation-edit",
|
name: "goods-draft-operation-edit",
|
||||||
component: () => import("@/views/goods/goods-seller/goodsOperation.vue")
|
component: () => import("@/views/goods/goods-seller/goodsOperation.vue")
|
||||||
},
|
},
|
||||||
|
/** 卡密商品(E_COUPON)卡池管理;query: skuId(必填), goodsId, goodsName */
|
||||||
|
{
|
||||||
|
path: "card-key-pool",
|
||||||
|
title: "卡池管理",
|
||||||
|
name: "card-key-pool",
|
||||||
|
component: () => import("@/views/goods/card-key/cardKeyPool.vue")
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "add-coupon",
|
path: "add-coupon",
|
||||||
title: "店铺优惠券",
|
title: "店铺优惠券",
|
||||||
|
|||||||
619
seller/src/views/goods/card-key/cardKeyPool.vue
Normal file
619
seller/src/views/goods/card-key/cardKeyPool.vue
Normal file
@@ -0,0 +1,619 @@
|
|||||||
|
<!-- 卡密商品 · 卡池管理(原型 P-04;API-S-01~S-07)
|
||||||
|
@author Mike
|
||||||
|
@date 2026-07-31
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="search card-key-pool">
|
||||||
|
<el-card>
|
||||||
|
<div v-if="goodsName || skuOptions.length" class="pool-header">
|
||||||
|
<el-button @click="goBack">返回</el-button>
|
||||||
|
<span v-if="goodsName" class="pool-meta">
|
||||||
|
<strong>商品:</strong>{{ goodsName }}
|
||||||
|
</span>
|
||||||
|
<div v-if="skuOptions.length" class="pool-sku-select">
|
||||||
|
<span class="pool-sku-label">规格 SKU</span>
|
||||||
|
<el-select
|
||||||
|
v-model="skuId"
|
||||||
|
:loading="skuLoading"
|
||||||
|
placeholder="请选择规格"
|
||||||
|
filterable
|
||||||
|
style="width: 320px"
|
||||||
|
@change="onSkuChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in skuOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="formatSkuOptionLabel(item)"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="!skuId"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
class="mb_10"
|
||||||
|
>
|
||||||
|
缺少 SKU 参数,请从商品列表「卡池管理」进入。
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<el-form
|
||||||
|
v-else
|
||||||
|
ref="searchFormRef"
|
||||||
|
:model="searchForm"
|
||||||
|
inline
|
||||||
|
label-width="70px"
|
||||||
|
class="search-form"
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
>
|
||||||
|
<el-form-item label="卡号" prop="cardNo">
|
||||||
|
<el-input
|
||||||
|
v-model="searchForm.cardNo"
|
||||||
|
placeholder="卡号模糊搜索"
|
||||||
|
clearable
|
||||||
|
style="width: 240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="导入时间" prop="importRange">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="importRange"
|
||||||
|
type="datetimerange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
style="width: 360px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
|
||||||
|
<el-button @click="handleReset">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card v-if="skuId">
|
||||||
|
<div class="goods-tab">
|
||||||
|
<el-tabs v-model="statusTab" @tab-click="onStatusTabClick">
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="tab in statusTabsWithCount"
|
||||||
|
:key="tab.value"
|
||||||
|
:label="tab.title"
|
||||||
|
:name="tab.value"
|
||||||
|
/>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="poolLocked"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
class="mb_10"
|
||||||
|
title="当前商品不可管理卡池(审核拒绝、商品已删除或店铺已关店)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="operation" style="margin: 10px 0">
|
||||||
|
<el-button type="primary" :disabled="poolLocked" @click="importModal = true">
|
||||||
|
批量导入
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="poolLocked" @click="openAddDialog">单条新增</el-button>
|
||||||
|
<el-button :loading="exportLoading" :disabled="poolLocked" @click="handleExport">
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="data" class="mt_10" style="width: 100%">
|
||||||
|
<el-table-column label="序号" width="60" align="center">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
{{ (searchForm.pageNumber - 1) * searchForm.pageSize + $index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<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 label="状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row" :type="statusTag(row.status)" size="small">
|
||||||
|
{{ formatStatus(row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="导入时间" width="170" />
|
||||||
|
<el-table-column prop="allocatedTime" label="发卡时间" width="170" />
|
||||||
|
<el-table-column prop="orderSn" label="订单号" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="操作" width="100" align="center" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
v-if="row && row.status === 'UNUSED'"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
:disabled="poolLocked"
|
||||||
|
@click="handleVoid(row)"
|
||||||
|
>
|
||||||
|
作废
|
||||||
|
</el-button>
|
||||||
|
<span v-else-if="row && row.status === 'RESERVED'" class="text-muted">预占中</span>
|
||||||
|
<span v-else>—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="mt_10" style="display: flex; justify-content: flex-end">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="searchForm.pageNumber"
|
||||||
|
v-model:page-size="searchForm.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
size="small"
|
||||||
|
@current-change="getList"
|
||||||
|
@size-change="onPageSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 批量导入 -->
|
||||||
|
<el-dialog v-model="importModal" title="批量导入卡密" width="520px" :close-on-click-modal="false">
|
||||||
|
<p class="import-tip">
|
||||||
|
Excel 模板:第 1 行表头,第 2 行起为数据;列 A 卡号、列 B 卡密;单次最多 10,000 行。
|
||||||
|
</p>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
class="mb_10"
|
||||||
|
:loading="templateLoading"
|
||||||
|
@click="handleDownloadTemplate"
|
||||||
|
>
|
||||||
|
下载导入模板
|
||||||
|
</el-button>
|
||||||
|
<el-upload drag :show-file-list="false" accept=".xlsx" :before-upload="handleImportUpload">
|
||||||
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
</el-upload>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="importModal = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 导入结果 -->
|
||||||
|
<el-dialog v-model="importResultVisible" title="导入结果" width="560px">
|
||||||
|
<p>成功 {{ importResult.successCount || 0 }} 条,失败 {{ importResult.failCount || 0 }} 条</p>
|
||||||
|
<el-table
|
||||||
|
v-if="importResult.failRows && importResult.failRows.length"
|
||||||
|
:data="importResult.failRows"
|
||||||
|
max-height="320"
|
||||||
|
border
|
||||||
|
>
|
||||||
|
<el-table-column label="行号" width="80" align="center">
|
||||||
|
<template #default="{ row: failItem }">
|
||||||
|
<span v-if="failItem">{{ failItem.row }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="卡号" min-width="120">
|
||||||
|
<template #default="{ row: failItem }">
|
||||||
|
<span v-if="failItem">{{ failItem.cardNo || "—" }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="reason" label="失败原因" min-width="200" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="importResultVisible = false">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 单条新增 -->
|
||||||
|
<el-dialog v-model="addVisible" title="单条新增卡密" width="480px" :close-on-click-modal="false">
|
||||||
|
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="80px">
|
||||||
|
<el-form-item label="卡号" prop="cardNo">
|
||||||
|
<el-input v-model="addForm.cardNo" placeholder="请输入卡号" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="卡密" prop="cardSecret">
|
||||||
|
<el-input v-model="addForm.cardSecret" placeholder="请输入卡密" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="addVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="addLoading" @click="submitAdd">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
importCardKey,
|
||||||
|
addCardKey,
|
||||||
|
getCardKeyList,
|
||||||
|
voidCardKey,
|
||||||
|
getCardKeyStats,
|
||||||
|
exportCardKey,
|
||||||
|
downloadImportTemplate,
|
||||||
|
} from "@/api/cardKey";
|
||||||
|
import { getQueryGoodsIdGoodsList } from "@/api/goods";
|
||||||
|
import {
|
||||||
|
formatCardKeyStatus,
|
||||||
|
CARD_KEY_STATUS_TAG,
|
||||||
|
} from "@/constants/cardKey";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商家卡池管理页:导入/新增/作废/导出,列表展示明文卡密(仅商家授权上下文)。
|
||||||
|
* 入口:商品列表「卡池管理」,路由 query 须带 skuId;审核拒绝/删 SKU/关店时 poolLocked。
|
||||||
|
*
|
||||||
|
* @author Mike
|
||||||
|
* @date 2026-07-31
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: "cardKeyPool",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
skuId: "",
|
||||||
|
goodsId: "",
|
||||||
|
goodsName: "",
|
||||||
|
skuOptions: [],
|
||||||
|
skuLoading: false,
|
||||||
|
statusTab: "ALL",
|
||||||
|
stats: null,
|
||||||
|
poolLocked: false,
|
||||||
|
loading: false,
|
||||||
|
exportLoading: false,
|
||||||
|
templateLoading: false,
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
importRange: [],
|
||||||
|
searchForm: {
|
||||||
|
cardNo: "",
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
importModal: false,
|
||||||
|
importResultVisible: false,
|
||||||
|
importResult: {},
|
||||||
|
addVisible: false,
|
||||||
|
addLoading: false,
|
||||||
|
addForm: {
|
||||||
|
cardNo: "",
|
||||||
|
cardSecret: "",
|
||||||
|
},
|
||||||
|
addRules: {
|
||||||
|
cardNo: [{ required: true, message: "卡号不能为空", trigger: "blur" }],
|
||||||
|
cardSecret: [{ required: true, message: "卡密不能为空", trigger: "blur" }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
statusTabsWithCount() {
|
||||||
|
const s = this.stats || {};
|
||||||
|
const withCount = (label, count) =>
|
||||||
|
count != null && this.stats ? `${label}(${count})` : label;
|
||||||
|
const total =
|
||||||
|
(s.unusedCount || 0) +
|
||||||
|
(s.reservedCount || 0) +
|
||||||
|
(s.allocatedCount || 0) +
|
||||||
|
(s.voidedCount || 0);
|
||||||
|
return [
|
||||||
|
{ title: withCount("全部", total), value: "ALL" },
|
||||||
|
{ title: withCount("未使用", s.unusedCount || 0), value: "UNUSED" },
|
||||||
|
{ title: withCount("已预占", s.reservedCount || 0), value: "RESERVED" },
|
||||||
|
{ title: withCount("已分配", s.allocatedCount || 0), value: "ALLOCATED" },
|
||||||
|
{ title: withCount("已作废", s.voidedCount || 0), value: "VOIDED" },
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"$route.query": {
|
||||||
|
handler() {
|
||||||
|
const prevSkuId = this.skuId;
|
||||||
|
this.initFromRoute();
|
||||||
|
if (this.skuId && this.skuId !== prevSkuId) {
|
||||||
|
this.resetPoolViewState();
|
||||||
|
this.loadStats();
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatStatus: formatCardKeyStatus,
|
||||||
|
statusTag(status) {
|
||||||
|
return CARD_KEY_STATUS_TAG[status] || "info";
|
||||||
|
},
|
||||||
|
goBack() {
|
||||||
|
this.$router.back();
|
||||||
|
},
|
||||||
|
initFromRoute() {
|
||||||
|
const q = this.$route.query;
|
||||||
|
this.skuId = q.skuId || "";
|
||||||
|
this.goodsId = q.goodsId || "";
|
||||||
|
this.goodsName = q.goodsName || "";
|
||||||
|
},
|
||||||
|
formatSkuOptionLabel(sku) {
|
||||||
|
if (!sku) return "";
|
||||||
|
const spec = sku.simpleSpecs || sku.sn || sku.id;
|
||||||
|
const qty =
|
||||||
|
sku.poolStock != null && sku.poolStock !== ""
|
||||||
|
? sku.poolStock
|
||||||
|
: sku.quantity != null
|
||||||
|
? sku.quantity
|
||||||
|
: "—";
|
||||||
|
return `${spec}(库存 ${qty})`;
|
||||||
|
},
|
||||||
|
resetPoolViewState() {
|
||||||
|
this.statusTab = "ALL";
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.searchForm.cardNo = "";
|
||||||
|
this.importRange = [];
|
||||||
|
this.poolLocked = false;
|
||||||
|
this.stats = null;
|
||||||
|
this.data = [];
|
||||||
|
this.total = 0;
|
||||||
|
},
|
||||||
|
/** 卡池变更后刷新 SKU 选择器中的可售库存(后端 syncSkuStock → quantity) */
|
||||||
|
refreshSkuOptionStock() {
|
||||||
|
if (!this.goodsId) return Promise.resolve();
|
||||||
|
return this.loadSkuOptions();
|
||||||
|
},
|
||||||
|
async loadSkuOptions() {
|
||||||
|
if (!this.goodsId) {
|
||||||
|
this.skuOptions = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.skuLoading = true;
|
||||||
|
try {
|
||||||
|
const res = await getQueryGoodsIdGoodsList(this.goodsId);
|
||||||
|
if (res.success) {
|
||||||
|
this.skuOptions = (res.result || []).filter((s) => !s.deleteFlag);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.skuOptions = [];
|
||||||
|
} finally {
|
||||||
|
this.skuLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSkuChange(newSkuId) {
|
||||||
|
if (!newSkuId) return;
|
||||||
|
this.skuId = newSkuId;
|
||||||
|
this.resetPoolViewState();
|
||||||
|
this.$router.replace({
|
||||||
|
path: this.$route.path,
|
||||||
|
query: {
|
||||||
|
...this.$route.query,
|
||||||
|
skuId: newSkuId,
|
||||||
|
goodsId: this.goodsId,
|
||||||
|
goodsName: this.goodsName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.loadStats();
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
onStatusTabClick(tab) {
|
||||||
|
this.statusTab = tab.paneName;
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
buildListParams() {
|
||||||
|
const params = {
|
||||||
|
skuId: this.skuId,
|
||||||
|
pageNumber: this.searchForm.pageNumber,
|
||||||
|
pageSize: this.searchForm.pageSize,
|
||||||
|
};
|
||||||
|
if (this.goodsId) params.goodsId = this.goodsId;
|
||||||
|
if (this.searchForm.cardNo) params.cardNo = this.searchForm.cardNo;
|
||||||
|
if (this.statusTab && this.statusTab !== "ALL") {
|
||||||
|
params.status = this.statusTab;
|
||||||
|
}
|
||||||
|
if (this.importRange && this.importRange.length === 2) {
|
||||||
|
params.createTimeStart = this.importRange[0];
|
||||||
|
params.createTimeEnd = this.importRange[1];
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
},
|
||||||
|
loadStats() {
|
||||||
|
if (!this.skuId) return;
|
||||||
|
getCardKeyStats(this.skuId).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.stats = res.result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
if (!this.skuId) return;
|
||||||
|
this.loading = true;
|
||||||
|
getCardKeyList(this.buildListParams())
|
||||||
|
.then((res) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.data = res.result.records || [];
|
||||||
|
this.total = res.result.total || 0;
|
||||||
|
this.poolLocked = false;
|
||||||
|
} else {
|
||||||
|
this.checkPoolLocked(res);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 审核拒绝 / SKU 删除 / 关店时禁用卡池操作(S-06 / S-07 / EC-21~23) */
|
||||||
|
checkPoolLocked(res) {
|
||||||
|
const lockedCodes = [
|
||||||
|
"CARD_KEY_GOODS_AUTH_REFUSE",
|
||||||
|
"CARD_KEY_SKU_DELETED",
|
||||||
|
"CARD_KEY_STORE_CLOSED",
|
||||||
|
];
|
||||||
|
if (res.code && lockedCodes.includes(String(res.code))) {
|
||||||
|
this.poolLocked = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
handleReset() {
|
||||||
|
this.searchForm.cardNo = "";
|
||||||
|
this.importRange = [];
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
onPageSizeChange() {
|
||||||
|
this.searchForm.pageNumber = 1;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
handleImportUpload(file) {
|
||||||
|
if (!/\.xlsx$/i.test(file.name)) {
|
||||||
|
this.$Message.error("请上传 .xlsx 文件");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
importCardKey(this.skuId, file).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.importModal = false;
|
||||||
|
this.importResult = res.result || {};
|
||||||
|
this.importResultVisible = true;
|
||||||
|
this.loadStats();
|
||||||
|
this.getList();
|
||||||
|
this.refreshSkuOptionStock();
|
||||||
|
} else {
|
||||||
|
this.checkPoolLocked(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleDownloadTemplate() {
|
||||||
|
this.templateLoading = true;
|
||||||
|
downloadImportTemplate()
|
||||||
|
.then(() => {
|
||||||
|
this.$Message.success("模板下载成功");
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
this.templateLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openAddDialog() {
|
||||||
|
this.addForm = { cardNo: "", cardSecret: "" };
|
||||||
|
this.addVisible = true;
|
||||||
|
},
|
||||||
|
submitAdd() {
|
||||||
|
this.$refs.addFormRef.validate((valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
this.addLoading = true;
|
||||||
|
addCardKey({
|
||||||
|
skuId: this.skuId,
|
||||||
|
cardNo: this.addForm.cardNo.trim(),
|
||||||
|
cardSecret: this.addForm.cardSecret.trim(),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
this.addLoading = false;
|
||||||
|
if (res.success) {
|
||||||
|
this.$Message.success("新增成功");
|
||||||
|
this.addVisible = false;
|
||||||
|
this.loadStats();
|
||||||
|
this.getList();
|
||||||
|
this.refreshSkuOptionStock();
|
||||||
|
} else {
|
||||||
|
this.checkPoolLocked(res);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.addLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleVoid(row) {
|
||||||
|
this.$Modal.confirm({
|
||||||
|
title: "确认作废",
|
||||||
|
content: `确定作废卡号「${row.cardNo}」?作废后不可恢复。`,
|
||||||
|
onOk: () => {
|
||||||
|
voidCardKey(row.id).then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
this.$Message.success("作废成功");
|
||||||
|
this.loadStats();
|
||||||
|
this.getList();
|
||||||
|
this.refreshSkuOptionStock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleExport() {
|
||||||
|
this.exportLoading = true;
|
||||||
|
const params = { ...this.buildListParams() };
|
||||||
|
delete params.pageNumber;
|
||||||
|
delete params.pageSize;
|
||||||
|
exportCardKey(params, this.skuId)
|
||||||
|
.then(() => {
|
||||||
|
this.$Message.success("导出成功");
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
this.exportLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async init() {
|
||||||
|
this.initFromRoute();
|
||||||
|
if (this.goodsId) {
|
||||||
|
await this.loadSkuOptions();
|
||||||
|
}
|
||||||
|
if (!this.skuId && this.skuOptions.length) {
|
||||||
|
this.onSkuChange(this.skuOptions[0].id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.skuId) {
|
||||||
|
this.loadStats();
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/styles/table-common.scss";
|
||||||
|
|
||||||
|
.pool-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pool-meta {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pool-sku-select {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pool-sku-label {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-tab {
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-tip {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -53,6 +53,8 @@
|
|||||||
>
|
>
|
||||||
<el-option label="实物商品" value="PHYSICAL_GOODS" />
|
<el-option label="实物商品" value="PHYSICAL_GOODS" />
|
||||||
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
|
<el-option label="虚拟商品" value="VIRTUAL_GOODS" />
|
||||||
|
<!-- E_COUPON:卡密商品,可跳转卡池管理 -->
|
||||||
|
<el-option label="电子卡券" value="E_COUPON" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@@ -148,7 +150,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="storeName" label="店铺名称" width="200" show-overflow-tooltip />
|
<el-table-column prop="storeName" label="店铺名称" width="200" show-overflow-tooltip />
|
||||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
<el-table-column label="操作" width="260" align="center" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<template v-if="row.marketEnable === 'DOWN'">
|
<template v-if="row.marketEnable === 'DOWN'">
|
||||||
<a class="link-text" @click="upper(row)">上架</a>
|
<a class="link-text" @click="upper(row)">上架</a>
|
||||||
@@ -160,6 +162,11 @@
|
|||||||
<span class="op-split">|</span>
|
<span class="op-split">|</span>
|
||||||
<a class="link-text" @click="editGoods(row)">编辑</a>
|
<a class="link-text" @click="editGoods(row)">编辑</a>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- E_COUPON 专属:卡池管理入口(原型 P-03) -->
|
||||||
|
<template v-if="row.goodsType === 'E_COUPON'">
|
||||||
|
<span class="op-split">|</span>
|
||||||
|
<a class="link-text" @click="goCardKeyPool(row)">卡池管理</a>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -196,6 +203,7 @@
|
|||||||
import {
|
import {
|
||||||
getGoodsListData,
|
getGoodsListData,
|
||||||
getGoodsNumerData,
|
getGoodsNumerData,
|
||||||
|
getQueryGoodsIdGoodsList,
|
||||||
upGoods,
|
upGoods,
|
||||||
lowGoods,
|
lowGoods,
|
||||||
} from "@/api/goods";
|
} from "@/api/goods";
|
||||||
@@ -252,7 +260,34 @@ export default {
|
|||||||
goodsTypeText(v) {
|
goodsTypeText(v) {
|
||||||
if (v === "PHYSICAL_GOODS") return "实物商品";
|
if (v === "PHYSICAL_GOODS") return "实物商品";
|
||||||
if (v === "VIRTUAL_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) {
|
marketEnableText(v) {
|
||||||
if (v === "DOWN") return "下架";
|
if (v === "DOWN") return "下架";
|
||||||
|
|||||||
@@ -1,58 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="goods-operation">
|
<div class="goods-operation">
|
||||||
<div class="step-list">
|
<!-- 填写商品详细信息(含商品类型、商品分类) -->
|
||||||
<el-steps :active="activestep" align-center style="height: 60px; margin-top: 10px">
|
<second-step ref="second" :firstData="firstData" v-if="activestep === 1"></second-step>
|
||||||
<el-step title="选择商品品类" />
|
<!-- 发布完成 -->
|
||||||
<el-step title="填写商品详情" />
|
<third-step ref="third" v-if="activestep === 2"></third-step>
|
||||||
<el-step title="商品发布成功" />
|
|
||||||
</el-steps>
|
|
||||||
</div>
|
|
||||||
<!-- 第一步 选择分类 -->
|
|
||||||
<first-step ref='first' v-show="activestep === 0" @change="getFirstData"></first-step>
|
|
||||||
<!-- 第二步 商品详细信息 -->
|
|
||||||
<second-step ref='second' :firstData="firstData" v-if="activestep === 1"></second-step>
|
|
||||||
<!-- 第三步 发布完成 -->
|
|
||||||
<third-step ref='third' v-if="activestep === 2"></third-step>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import firstStep from './goodsOperationFirst'
|
import secondStep from "./goodsOperationSec";
|
||||||
import secondStep from './goodsOperationSec'
|
import thirdStep from "./goodsOperationThird";
|
||||||
import thirdStep from './goodsOperationThird'
|
|
||||||
export default {
|
export default {
|
||||||
name: "addGoods",
|
name: "addGoods",
|
||||||
components: {
|
components: {
|
||||||
firstStep,
|
|
||||||
secondStep,
|
secondStep,
|
||||||
thirdStep
|
thirdStep,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
/** 当前激活步骤*/
|
/** 当前激活步骤:1 填写详情,2 发布成功 */
|
||||||
activestep: 0,
|
activestep: 1,
|
||||||
firstData: {}, // 第一步传递的数据
|
firstData: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
// 选择商品分类回调
|
|
||||||
getFirstData (item) {
|
|
||||||
this.firstData = item;
|
|
||||||
this.activestep = 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
// 编辑商品、模板、复制商品
|
window.scrollTo(0, 0);
|
||||||
if (this.$route.query.id || this.$route.query.draftId || this.$route.query.copyId) {
|
},
|
||||||
this.activestep = 1;
|
|
||||||
} else {
|
|
||||||
this.activestep = 0
|
|
||||||
this.$refs.first.selectGoodsType = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,233 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div></div>
|
||||||
<!-- 选择商品类型 -->
|
|
||||||
<el-dialog v-model="selectGoodsType" width="550px" :show-close="false">
|
|
||||||
<div class="goods-type-list">
|
|
||||||
<div
|
|
||||||
class="goods-type-item"
|
|
||||||
:class="{ 'active-goods-type': item.check }"
|
|
||||||
@click="handleClickGoodsType(item)"
|
|
||||||
v-for="(item, index) in goodsTypeWay"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<img :src="item.img" />
|
|
||||||
<div>
|
|
||||||
<h2>{{ item.title }}</h2>
|
|
||||||
<p>{{ item.desc }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<div class="goods-type-actions">
|
|
||||||
<el-button @click="cancelGoodsType">取消</el-button>
|
|
||||||
<el-button type="primary" @click="confirmGoodsType">确认</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
<!-- 商品分类 -->
|
|
||||||
<div class="content-goods-publish">
|
|
||||||
<div class="goods-category">
|
|
||||||
<ul v-if="categoryListLevel1.length > 0">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in categoryListLevel1"
|
|
||||||
:class="{ activeClass: category[0].name === item.name }"
|
|
||||||
@click="handleSelectCategory(item, index, 1)"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
<span>></span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul v-if="categoryListLevel2.length > 0">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in categoryListLevel2"
|
|
||||||
:class="{ activeClass: category[1].name === item.name }"
|
|
||||||
@click="handleSelectCategory(item, index, 2)"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
<span>></span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul v-if="categoryListLevel3.length > 0">
|
|
||||||
<li
|
|
||||||
v-for="(item, index) in categoryListLevel3"
|
|
||||||
:class="{ activeClass: category[2].name === item.name }"
|
|
||||||
@click="handleSelectCategory(item, index, 3)"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
<span>{{ item.name }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<p class="current-goods-category">
|
|
||||||
您当前选择的商品类别是:
|
|
||||||
<span>{{ category[0].name }}</span>
|
|
||||||
<span v-show="category[1].name">> {{ category[1].name }}</span>
|
|
||||||
<span v-show="category[2].name">> {{ category[2].name }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<!-- 底部按钮 -->
|
|
||||||
<div class="footer">
|
|
||||||
<div class="footer-btns">
|
|
||||||
<el-button type="primary" @click="openGoodsTypeDialog">商品类型</el-button>
|
|
||||||
<el-button type="primary" @click="next">下一步</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import * as API_GOODS from "@/api/goods";
|
/** 商品类型与分类已迁移至 goodsOperationSec.vue 基本信息区 */
|
||||||
import goodsType1Img from "@/assets/goodsType1.png";
|
|
||||||
import goodsType2Img from "@/assets/goodsType2.png";
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
name: "goodsOperationFirst",
|
||||||
return {
|
|
||||||
selectGoodsType: false, // 展示选择商品分类modal
|
|
||||||
goodsTypeWay: [
|
|
||||||
{
|
|
||||||
title: "实物商品",
|
|
||||||
img: goodsType1Img,
|
|
||||||
desc: "零售批发,物流配送",
|
|
||||||
type: "PHYSICAL_GOODS",
|
|
||||||
check: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "虚拟商品",
|
|
||||||
img: goodsType2Img,
|
|
||||||
desc: "虚拟核验,无需物流",
|
|
||||||
type: "VIRTUAL_GOODS",
|
|
||||||
check: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// 商品分类选择数组
|
|
||||||
category: [
|
|
||||||
{ name: "", id: "" },
|
|
||||||
{ name: "", id: "" },
|
|
||||||
{ name: "", id: "" },
|
|
||||||
],
|
|
||||||
// 商品类型
|
|
||||||
goodsType: "",
|
|
||||||
pendingGoodsType: "",
|
|
||||||
/** 1级分类列表*/
|
|
||||||
categoryListLevel1: [],
|
|
||||||
/** 2级分类列表*/
|
|
||||||
categoryListLevel2: [],
|
|
||||||
/** 3级分类列表*/
|
|
||||||
categoryListLevel3: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
selectGoodsType(val) {
|
|
||||||
if (val) {
|
|
||||||
this.syncGoodsTypeSelection();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
syncGoodsTypeSelection() {
|
|
||||||
this.pendingGoodsType = this.goodsType;
|
|
||||||
this.goodsTypeWay.forEach((item) => {
|
|
||||||
item.check = item.type === this.goodsType;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
openGoodsTypeDialog() {
|
|
||||||
this.selectGoodsType = true;
|
|
||||||
},
|
|
||||||
// 点击商品类型(仅临时选中,确认后才生效)
|
|
||||||
handleClickGoodsType(val) {
|
|
||||||
this.goodsTypeWay.forEach((item) => {
|
|
||||||
item.check = item.type === val.type;
|
|
||||||
});
|
|
||||||
this.pendingGoodsType = val.type;
|
|
||||||
},
|
|
||||||
cancelGoodsType() {
|
|
||||||
this.syncGoodsTypeSelection();
|
|
||||||
this.selectGoodsType = false;
|
|
||||||
},
|
|
||||||
confirmGoodsType() {
|
|
||||||
if (!this.pendingGoodsType) {
|
|
||||||
this.$Message.error("请选择商品类型");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.goodsType = this.pendingGoodsType;
|
|
||||||
this.selectGoodsType = false;
|
|
||||||
},
|
|
||||||
/** 选择商城商品分类 */
|
|
||||||
handleSelectCategory(row, index, level) {
|
|
||||||
if (level === 1) {
|
|
||||||
this.category.forEach((cate) => {
|
|
||||||
(cate.name = ""), (cate.id = "");
|
|
||||||
});
|
|
||||||
this.category[0].name = row.name;
|
|
||||||
this.category[0].id = row.id;
|
|
||||||
this.categoryListLevel2 = this.categoryListLevel1[index].children;
|
|
||||||
this.categoryListLevel3 = [];
|
|
||||||
} else if (level === 2) {
|
|
||||||
this.category[1].name = row.name;
|
|
||||||
this.category[1].id = row.id;
|
|
||||||
this.category[2].name = "";
|
|
||||||
this.category[2].id = "";
|
|
||||||
this.categoryListLevel3 = this.categoryListLevel2[index].children;
|
|
||||||
} else {
|
|
||||||
this.category[2].name = row.name;
|
|
||||||
this.category[2].id = row.id;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/** 查询下一级 商城商品分类*/
|
|
||||||
GET_NextLevelCategory(row) {
|
|
||||||
const _id = row && row.id !== 0 ? row.id : 0;
|
|
||||||
API_GOODS.getGoodsCategoryAll().then((res) => {
|
|
||||||
if (res.success && res.result) {
|
|
||||||
this.categoryListLevel1 = res.result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// 下一步
|
|
||||||
next() {
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
if (!this.goodsType) {
|
|
||||||
this.$Message.error("请选择商品类型");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.category[0].name) {
|
|
||||||
this.$Message.error("请选择商品分类");
|
|
||||||
return;
|
|
||||||
} else if (!this.category[2].name) {
|
|
||||||
this.$Message.error("必须选择到三级分类");
|
|
||||||
return;
|
|
||||||
} else if (this.category[2].name) {
|
|
||||||
let params = {
|
|
||||||
category: this.category,
|
|
||||||
goodsType: this.goodsType,
|
|
||||||
};
|
|
||||||
this.$emit("change", params);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.GET_NextLevelCategory();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import "./addGoods.scss";
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-btns {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.goods-type-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -8,12 +8,37 @@
|
|||||||
<div class="base-info-item">
|
<div class="base-info-item">
|
||||||
<h4>基本信息</h4>
|
<h4>基本信息</h4>
|
||||||
<div class="form-item-view">
|
<div class="form-item-view">
|
||||||
<el-form-item label="商品分类">
|
<el-form-item label="商品类型" prop="goodsType">
|
||||||
<span class="goods-category-name">{{
|
<el-select
|
||||||
baseInfoForm.categoryName[0]
|
v-model="baseInfoForm.goodsType"
|
||||||
}}</span>
|
placeholder="请选择商品类型"
|
||||||
<span> > {{ baseInfoForm.categoryName[1] }}</span>
|
style="width: 260px"
|
||||||
<span> > {{ baseInfoForm.categoryName[2] }}</span>
|
popper-class="goods-type-select-popper"
|
||||||
|
@change="handleGoodsTypeChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in goodsTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
<div class="goods-type-option">
|
||||||
|
<span class="goods-type-option-label">{{ item.label }}</span>
|
||||||
|
<span class="goods-type-option-desc">{{ item.desc }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
<div v-if="currentGoodsTypeDesc" class="form-tip">{{ currentGoodsTypeDesc }}</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="商品分类" prop="categoryPath">
|
||||||
|
<el-cascader
|
||||||
|
v-model="selectedCategory"
|
||||||
|
:options="categoryList"
|
||||||
|
placeholder="请选择商品分类"
|
||||||
|
clearable
|
||||||
|
style="width: 260px"
|
||||||
|
@change="handleCategoryChange"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="商品名称" prop="goodsName">
|
<el-form-item label="商品名称" prop="goodsName">
|
||||||
<el-input v-model="baseInfoForm.goodsName" clearable placeholder="商品名称" style="width: 260px" type="text" />
|
<el-input v-model="baseInfoForm.goodsName" clearable placeholder="商品名称" style="width: 260px" type="text" />
|
||||||
@@ -44,7 +69,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item class="form-item-view-el" label="销售模式" prop="salesModel">
|
<el-form-item class="form-item-view-el" label="销售模式" prop="salesModel">
|
||||||
<el-radio-group
|
<el-radio-group
|
||||||
v-if="baseInfoForm.goodsType != 'VIRTUAL_GOODS'"
|
v-if="!isVirtualLikeGoods"
|
||||||
v-model="baseInfoForm.salesModel"
|
v-model="baseInfoForm.salesModel"
|
||||||
@change="handleSalesModeChange"
|
@change="handleSalesModeChange"
|
||||||
>
|
>
|
||||||
@@ -52,7 +77,7 @@
|
|||||||
<el-radio-button value="WHOLESALE">批发型</el-radio-button>
|
<el-radio-button value="WHOLESALE">批发型</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<el-radio-group v-else v-model="baseInfoForm.salesModel">
|
<el-radio-group v-else v-model="baseInfoForm.salesModel">
|
||||||
<el-radio-button value="RETAIL">虚拟型</el-radio-button>
|
<el-radio-button value="RETAIL">{{ isECouponGoods ? "电子卡券" : "虚拟型" }}</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="baseInfoForm.salesModel == 'WHOLESALE'" class="form-item-view-el" label="销售规则"
|
<el-form-item v-if="baseInfoForm.salesModel == 'WHOLESALE'" class="form-item-view-el" label="销售规则"
|
||||||
@@ -336,7 +361,7 @@
|
|||||||
@change="updateSkuTable(row, 'sn', $index)"
|
@change="updateSkuTable(row, 'sn', $index)"
|
||||||
/>
|
/>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="col.slot === 'weight' && baseInfoForm.goodsType !== 'VIRTUAL_GOODS'"
|
v-else-if="col.slot === 'weight' && needsLogistics && baseInfoForm.salesModel !== 'WHOLESALE'"
|
||||||
v-model="row.weight"
|
v-model="row.weight"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请输入重量"
|
placeholder="请输入重量"
|
||||||
@@ -344,6 +369,14 @@
|
|||||||
>
|
>
|
||||||
<template #append>kg</template>
|
<template #append>kg</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
<el-input
|
||||||
|
v-else-if="col.slot === 'quantity' && isECouponGoods"
|
||||||
|
:model-value="row.quantity ?? 0"
|
||||||
|
disabled
|
||||||
|
placeholder="库存"
|
||||||
|
>
|
||||||
|
<template #append>{{ baseInfoForm.goodsUnit || "" }}</template>
|
||||||
|
</el-input>
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="col.slot === 'quantity'"
|
v-else-if="col.slot === 'quantity'"
|
||||||
v-model="row.quantity"
|
v-model="row.quantity"
|
||||||
@@ -444,7 +477,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="baseInfoForm.goodsType != 'VIRTUAL_GOODS'">
|
<div v-if="needsLogistics">
|
||||||
<h4>商品物流信息</h4>
|
<h4>商品物流信息</h4>
|
||||||
<div class="form-item-view">
|
<div class="form-item-view">
|
||||||
<el-form-item class="form-item-view-el" label="物流模板" prop="templateId">
|
<el-form-item class="form-item-view-el" label="物流模板" prop="templateId">
|
||||||
@@ -486,7 +519,6 @@
|
|||||||
<!-- 底部按钮 -->
|
<!-- 底部按钮 -->
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="footer-btns">
|
<div class="footer-btns">
|
||||||
<el-button type="primary" @click="pre">上一步</el-button>
|
|
||||||
<el-button :loading="submitLoading" type="primary" @click="save">
|
<el-button :loading="submitLoading" type="primary" @click="save">
|
||||||
{{ $route.query.id ? "保存" : "保存商品" }}
|
{{ $route.query.id ? "保存" : "保存商品" }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -513,7 +545,7 @@
|
|||||||
@selected="(list) => { selectedImage = list }"
|
@selected="(list) => { selectedImage = list }"
|
||||||
/>
|
/>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="picModelFlag = false">取消</el-button>
|
<el-button @click="picModelFlag = false; selectedImage = []">取消</el-button>
|
||||||
<el-button type="primary" @click="confirmUrls">确定</el-button>
|
<el-button type="primary" @click="confirmUrls">确定</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -552,6 +584,28 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
/** 卡密商品:库存只读展示 quantity,提交时 quantity 传 0(FR-S-01 / P-02) */
|
||||||
|
isECouponGoods() {
|
||||||
|
return this.baseInfoForm.goodsType === "E_COUPON";
|
||||||
|
},
|
||||||
|
isVirtualGoods() {
|
||||||
|
return this.baseInfoForm.goodsType === "VIRTUAL_GOODS";
|
||||||
|
},
|
||||||
|
isVirtualLikeGoods() {
|
||||||
|
return this.isVirtualGoods || this.isECouponGoods;
|
||||||
|
},
|
||||||
|
/** 仅实物需要运费模板;E_COUPON 强制 templateId=0(§6.1) */
|
||||||
|
needsLogistics() {
|
||||||
|
return this.baseInfoForm.goodsType === "PHYSICAL_GOODS";
|
||||||
|
},
|
||||||
|
currentGoodsTypeDesc() {
|
||||||
|
const matched = this.goodsTypeOptions.find(
|
||||||
|
(item) => item.value === this.baseInfoForm.goodsType
|
||||||
|
);
|
||||||
|
return matched ? matched.desc : "";
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
// 表单验证项,商品价格
|
// 表单验证项,商品价格
|
||||||
const checkPrice = (rule, value, callback) => {
|
const checkPrice = (rule, value, callback) => {
|
||||||
@@ -584,6 +638,16 @@ export default {
|
|||||||
accessToken: "", //令牌token
|
accessToken: "", //令牌token
|
||||||
goodsParams: [],
|
goodsParams: [],
|
||||||
categoryId: "", // 商品分类第三级id
|
categoryId: "", // 商品分类第三级id
|
||||||
|
/** 商品类型选项 */
|
||||||
|
goodsTypeOptions: [
|
||||||
|
{ label: "实物商品", value: "PHYSICAL_GOODS", desc: "零售批发,物流配送" },
|
||||||
|
{ label: "虚拟商品", value: "VIRTUAL_GOODS", desc: "虚拟核验,无需物流" },
|
||||||
|
{ label: "电子卡券", value: "E_COUPON", desc: "卡密自动发卡,无需物流" },
|
||||||
|
],
|
||||||
|
/** 商城分类级联数据 */
|
||||||
|
categoryList: [],
|
||||||
|
/** 商城分类选中值 [一级id, 二级id, 三级id] */
|
||||||
|
selectedCategory: [],
|
||||||
//提交状态
|
//提交状态
|
||||||
submitLoading: false,
|
submitLoading: false,
|
||||||
//上传图片路径
|
//上传图片路径
|
||||||
@@ -705,6 +769,8 @@ export default {
|
|||||||
/** 存储未通过校验的单元格位置 */
|
/** 存储未通过校验的单元格位置 */
|
||||||
validateError: [],
|
validateError: [],
|
||||||
baseInfoFormRule: {
|
baseInfoFormRule: {
|
||||||
|
goodsType: [{ required: true, message: "请选择商品类型", trigger: "change" }],
|
||||||
|
categoryPath: [{ required: true, message: "请选择商品分类", trigger: "change" }],
|
||||||
goodsName: [regular.REQUIRED, regular.WHITE_SPACE, regular.VARCHAR60],
|
goodsName: [regular.REQUIRED, regular.WHITE_SPACE, regular.VARCHAR60],
|
||||||
price: [regular.REQUIRED, { validator: checkPrice }],
|
price: [regular.REQUIRED, { validator: checkPrice }],
|
||||||
sellingPoint: [regular.REQUIRED, regular.VARCHAR60],
|
sellingPoint: [regular.REQUIRED, regular.VARCHAR60],
|
||||||
@@ -759,11 +825,22 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** E_COUPON 提交固定传 0,真实库存由卡池 syncSkuStock 同步至 quantity */
|
||||||
|
resolveSubmitQuantity(sku) {
|
||||||
|
if (this.isECouponGoods) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return sku.quantity;
|
||||||
|
},
|
||||||
|
defaultSkuQuantity() {
|
||||||
|
return this.isECouponGoods ? 0 : "";
|
||||||
|
},
|
||||||
draggableItemKey(item) {
|
draggableItemKey(item) {
|
||||||
return item;
|
return item;
|
||||||
},
|
},
|
||||||
// 选择图片modal
|
// 选择图片modal
|
||||||
handleCLickImg(val, index) {
|
handleCLickImg(val, index) {
|
||||||
|
this.selectedImage = [];
|
||||||
this.picModelFlag = true;
|
this.picModelFlag = true;
|
||||||
this.selectedFormBtnName = val;
|
this.selectedFormBtnName = val;
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -793,22 +870,60 @@ export default {
|
|||||||
desc: "视频大小不能超过10MB",
|
desc: "视频大小不能超过10MB",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
parseOssSelectionUrl(item) {
|
||||||
|
if (!item) return "";
|
||||||
|
if (typeof item === "string") {
|
||||||
|
const index = item.indexOf(",");
|
||||||
|
return index >= 0 ? item.slice(index + 1) : item;
|
||||||
|
}
|
||||||
|
return item.url || "";
|
||||||
|
},
|
||||||
// 图片选择后回调
|
// 图片选择后回调
|
||||||
callbackSelected(val) {
|
callbackSelected(val) {
|
||||||
this.picModelFlag = false;
|
this.picModelFlag = false;
|
||||||
if (val && this.selectedFormBtnName == 'selectedSkuImages') {
|
if (!val?.url) {
|
||||||
this.selectedSku.images.push(val);
|
return;
|
||||||
|
}
|
||||||
|
if (this.selectedFormBtnName === "selectedSkuImages") {
|
||||||
|
if (!this.selectedSku.images) {
|
||||||
|
this.selectedSku.images = [];
|
||||||
|
}
|
||||||
|
this.selectedSku.images.push(val.url);
|
||||||
} else {
|
} else {
|
||||||
this.baseInfoForm[this.selectedFormBtnName].push(val.url);
|
this.baseInfoForm[this.selectedFormBtnName].push(val.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
confirmUrls() {
|
confirmUrls() {
|
||||||
if (this.selectedImage && this.selectedFormBtnName == 'selectedSkuImages') {
|
const urls = (this.selectedImage || [])
|
||||||
this.selectedSku.images = [...this.selectedSku.images, ...this.selectedImage];
|
.map((item) => this.parseOssSelectionUrl(item))
|
||||||
} else {
|
.filter(Boolean);
|
||||||
this.baseInfoForm[this.selectedFormBtnName] = [...this.baseInfoForm[this.selectedFormBtnName], ...this.selectedImage];
|
if (!urls.length) {
|
||||||
|
this.$Message.warning("请选择图片");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.selectedFormBtnName === "selectedSkuImages") {
|
||||||
|
if (!this.selectedSku.images) {
|
||||||
|
this.selectedSku.images = [];
|
||||||
|
}
|
||||||
|
urls.forEach((url) => {
|
||||||
|
if (this.selectedSku.images.length < 5) {
|
||||||
|
this.selectedSku.images.push(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this.selectedFormBtnName === "goodsGalleryFiles") {
|
||||||
|
urls.forEach((url) => {
|
||||||
|
if (this.baseInfoForm.goodsGalleryFiles.length < 5) {
|
||||||
|
this.baseInfoForm.goodsGalleryFiles.push(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this.selectedFormBtnName) {
|
||||||
|
const target = this.baseInfoForm[this.selectedFormBtnName];
|
||||||
|
if (Array.isArray(target)) {
|
||||||
|
urls.forEach((url) => target.push(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.selectedImage = [];
|
||||||
|
this.picModelFlag = false;
|
||||||
},
|
},
|
||||||
// 局部刷新
|
// 局部刷新
|
||||||
refresh(v) {
|
refresh(v) {
|
||||||
@@ -913,9 +1028,79 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pre() {
|
/** 加载商城商品分类树 */
|
||||||
// 上一步
|
loadCategoryList() {
|
||||||
this.$parent.activestep--;
|
return API_GOODS.getGoodsCategoryAll().then((res) => {
|
||||||
|
if (res.success && res.result) {
|
||||||
|
this.categoryList = this.formatCategoryTree(res.result);
|
||||||
|
this.syncSelectedCategoryFromPath();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatCategoryTree(list) {
|
||||||
|
return (list || []).map((item) => ({
|
||||||
|
value: String(item.id),
|
||||||
|
label: item.name,
|
||||||
|
children: (item.children || []).map((child) => ({
|
||||||
|
value: String(child.id),
|
||||||
|
label: child.name,
|
||||||
|
children: (child.children || []).map((grandson) => ({
|
||||||
|
value: String(grandson.id),
|
||||||
|
label: grandson.name,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
syncSelectedCategoryFromPath() {
|
||||||
|
const path = this.baseInfoForm.categoryPath;
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ids = path.split(",").filter(Boolean).map(String);
|
||||||
|
if (ids.length >= 3) {
|
||||||
|
this.selectedCategory = ids;
|
||||||
|
this.categoryId = ids[2];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getCategoryNames(ids) {
|
||||||
|
const names = [];
|
||||||
|
let options = this.categoryList;
|
||||||
|
ids.forEach((id) => {
|
||||||
|
const found = (options || []).find((item) => item.value === id);
|
||||||
|
if (found) {
|
||||||
|
names.push(found.label);
|
||||||
|
options = found.children || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return names;
|
||||||
|
},
|
||||||
|
handleGoodsTypeChange() {
|
||||||
|
if (this.isVirtualLikeGoods) {
|
||||||
|
this.baseInfoForm.salesModel = "RETAIL";
|
||||||
|
}
|
||||||
|
if (this.isECouponGoods) {
|
||||||
|
this.baseInfoForm.templateId = 0;
|
||||||
|
}
|
||||||
|
this.renderTableData(this.skuTableData);
|
||||||
|
},
|
||||||
|
handleCategoryChange(val) {
|
||||||
|
if (!val || val.length < 3) {
|
||||||
|
this.selectedCategory = val || [];
|
||||||
|
this.categoryId = "";
|
||||||
|
this.baseInfoForm.categoryPath = "";
|
||||||
|
this.baseInfoForm.categoryName = [];
|
||||||
|
this.baseInfoForm.brandId = 0;
|
||||||
|
this.brandList = [];
|
||||||
|
this.goodsParams = [];
|
||||||
|
this.params_panel = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.categoryId = val[2];
|
||||||
|
this.baseInfoForm.categoryPath = val.join(",");
|
||||||
|
this.baseInfoForm.categoryName = this.getCategoryNames(val);
|
||||||
|
this.baseInfoForm.brandId = 0;
|
||||||
|
this.getGoodsBrandList();
|
||||||
|
this.GET_GoodsParams();
|
||||||
},
|
},
|
||||||
// 预览图片
|
// 预览图片
|
||||||
handleView(url) {
|
handleView(url) {
|
||||||
@@ -1192,6 +1377,7 @@ export default {
|
|||||||
this.baseInfoForm = { ...this.baseInfoForm, ...response.result };
|
this.baseInfoForm = { ...this.baseInfoForm, ...response.result };
|
||||||
this.baseInfoForm.release = 1; //即使是被放入仓库,修改的时候也会显示会立即发布
|
this.baseInfoForm.release = 1; //即使是被放入仓库,修改的时候也会显示会立即发布
|
||||||
this.categoryId = response.result.categoryPath.split(",")[2];
|
this.categoryId = response.result.categoryPath.split(",")[2];
|
||||||
|
this.syncSelectedCategoryFromPath();
|
||||||
|
|
||||||
// 如果是复制商品,需要清除ID,确保提交时作为新商品
|
// 如果是复制商品,需要清除ID,确保提交时作为新商品
|
||||||
if (this.$route.query.copyId) {
|
if (this.$route.query.copyId) {
|
||||||
@@ -1228,19 +1414,6 @@ export default {
|
|||||||
this.GET_ShopGoodsLabel();
|
this.GET_ShopGoodsLabel();
|
||||||
this.GET_GoodsUnit();
|
this.GET_GoodsUnit();
|
||||||
|
|
||||||
if (this.firstData.category) {
|
|
||||||
const cateId = [];
|
|
||||||
this.baseInfoForm.categoryName = [];
|
|
||||||
this.firstData.category.forEach((cate) => {
|
|
||||||
this.baseInfoForm.categoryName.push(cate.name);
|
|
||||||
cateId.push(cate.id);
|
|
||||||
});
|
|
||||||
this.categoryId = cateId[2];
|
|
||||||
|
|
||||||
this.baseInfoForm.categoryPath = cateId.toString();
|
|
||||||
}
|
|
||||||
this.firstData.goodsType &&
|
|
||||||
(this.baseInfoForm.goodsType = this.firstData.goodsType);
|
|
||||||
/** 查询商品参数 */
|
/** 查询商品参数 */
|
||||||
this.GET_GoodsParams();
|
this.GET_GoodsParams();
|
||||||
},
|
},
|
||||||
@@ -1724,14 +1897,19 @@ export default {
|
|||||||
...combination,
|
...combination,
|
||||||
id: existingCombination.id || "",
|
id: existingCombination.id || "",
|
||||||
sn: existingCombination.sn || "",
|
sn: existingCombination.sn || "",
|
||||||
quantity: existingCombination.quantity || "",
|
quantity: this.isECouponGoods
|
||||||
|
? (existingCombination.quantity ?? 0)
|
||||||
|
: (existingCombination.quantity || ""),
|
||||||
cost: existingCombination.cost || "",
|
cost: existingCombination.cost || "",
|
||||||
price: existingCombination.price || "",
|
price: existingCombination.price || "",
|
||||||
weight: existingCombination.weight || ""
|
weight: existingCombination.weight || ""
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 新组合使用默认值
|
// 新组合使用默认值
|
||||||
return combination;
|
return {
|
||||||
|
...combination,
|
||||||
|
quantity: this.defaultSkuQuantity(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.baseInfoForm.regeneratorSkuFlag = true;
|
this.baseInfoForm.regeneratorSkuFlag = true;
|
||||||
@@ -1779,14 +1957,19 @@ export default {
|
|||||||
...combination,
|
...combination,
|
||||||
id: existingCombination.id || "",
|
id: existingCombination.id || "",
|
||||||
sn: existingCombination.sn || "",
|
sn: existingCombination.sn || "",
|
||||||
quantity: existingCombination.quantity || "",
|
quantity: this.isECouponGoods
|
||||||
|
? (existingCombination.quantity ?? 0)
|
||||||
|
: (existingCombination.quantity || ""),
|
||||||
cost: existingCombination.cost || "",
|
cost: existingCombination.cost || "",
|
||||||
price: existingCombination.price || "",
|
price: existingCombination.price || "",
|
||||||
weight: existingCombination.weight || ""
|
weight: existingCombination.weight || ""
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 新组合使用默认值
|
// 新组合使用默认值
|
||||||
return combination;
|
return {
|
||||||
|
...combination,
|
||||||
|
quantity: this.defaultSkuQuantity(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1833,7 +2016,7 @@ export default {
|
|||||||
|
|
||||||
// 有重量的情况
|
// 有重量的情况
|
||||||
if (
|
if (
|
||||||
this.baseInfoForm.goodsType !== "VIRTUAL_GOODS" &&
|
this.needsLogistics &&
|
||||||
this.baseInfoForm.salesModel !== "WHOLESALE"
|
this.baseInfoForm.salesModel !== "WHOLESALE"
|
||||||
) {
|
) {
|
||||||
pushData.push({
|
pushData.push({
|
||||||
@@ -1878,7 +2061,9 @@ export default {
|
|||||||
...combination,
|
...combination,
|
||||||
id: existingCombination.id || "",
|
id: existingCombination.id || "",
|
||||||
sn: existingCombination.sn || "",
|
sn: existingCombination.sn || "",
|
||||||
quantity: existingCombination.quantity || "",
|
quantity: this.isECouponGoods
|
||||||
|
? (existingCombination.quantity ?? 0)
|
||||||
|
: (existingCombination.quantity || ""),
|
||||||
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 || ""
|
||||||
@@ -1889,7 +2074,7 @@ export default {
|
|||||||
...combination,
|
...combination,
|
||||||
id: "",
|
id: "",
|
||||||
sn: "",
|
sn: "",
|
||||||
quantity: "",
|
quantity: this.defaultSkuQuantity(),
|
||||||
cost: "",
|
cost: "",
|
||||||
price: this.baseInfoForm.salesModel === 'WHOLESALE' && this.wholesaleData.length > 0 ? this.wholesaleData[0].price : "",
|
price: this.baseInfoForm.salesModel === 'WHOLESALE' && this.wholesaleData.length > 0 ? this.wholesaleData[0].price : "",
|
||||||
weight: ""
|
weight: ""
|
||||||
@@ -2119,6 +2304,10 @@ export default {
|
|||||||
this.$Message.error(`以下参数为必填项:${missingParams.join('、')}`);
|
this.$Message.error(`以下参数为必填项:${missingParams.join('、')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.selectedCategory || this.selectedCategory.length < 3) {
|
||||||
|
this.$Message.error("必须选择到三级分类");
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.submitLoading = true;
|
this.submitLoading = true;
|
||||||
this.$refs["baseInfoForm"].validate((valid) => {
|
this.$refs["baseInfoForm"].validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
@@ -2153,6 +2342,10 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (submit.templateId === "") submit.templateId = 0;
|
if (submit.templateId === "") submit.templateId = 0;
|
||||||
|
if (this.isECouponGoods) {
|
||||||
|
submit.templateId = 0;
|
||||||
|
submit.salesModel = "RETAIL";
|
||||||
|
}
|
||||||
let flag = false;
|
let flag = false;
|
||||||
let paramValue = "";
|
let paramValue = "";
|
||||||
|
|
||||||
@@ -2175,7 +2368,7 @@ export default {
|
|||||||
let skuCopy = {
|
let skuCopy = {
|
||||||
cost: 1,
|
cost: 1,
|
||||||
price: sku.price,
|
price: sku.price,
|
||||||
quantity: sku.quantity,
|
quantity: this.resolveSubmitQuantity(sku),
|
||||||
// alertQuantity: sku.alertQuantity,
|
// alertQuantity: sku.alertQuantity,
|
||||||
sn: sku.sn,
|
sn: sku.sn,
|
||||||
images: [],
|
images: [],
|
||||||
@@ -2273,37 +2466,20 @@ export default {
|
|||||||
accessToken: this.getStore("accessToken"),
|
accessToken: this.getStore("accessToken"),
|
||||||
};
|
};
|
||||||
this.GET_ShipTemplate()
|
this.GET_ShipTemplate()
|
||||||
|
this.loadCategoryList();
|
||||||
if (this.$route.query.id || this.$route.query.draftId) {
|
if (this.$route.query.id || this.$route.query.draftId) {
|
||||||
// 编辑商品、模板
|
// 编辑商品、模板
|
||||||
this.GET_GoodData(this.$route.query.id, this.$route.query.draftId);
|
this.GET_GoodData(this.$route.query.id, this.$route.query.draftId);
|
||||||
} else if (this.$route.query.copyId) {
|
} else if (this.$route.query.copyId) {
|
||||||
// 复制商品
|
// 复制商品
|
||||||
this.GET_GoodData(this.$route.query.copyId);
|
this.GET_GoodData(this.$route.query.copyId);
|
||||||
|
} else if (this.firstData.tempId) {
|
||||||
|
// 选择模板
|
||||||
|
this.GET_GoodData("", this.firstData.tempId);
|
||||||
} else {
|
} else {
|
||||||
// 新增商品、模板
|
// 新增商品
|
||||||
if (this.firstData.tempId) {
|
this.GET_GoodsUnit();
|
||||||
// 选择模板
|
this.GET_ShopGoodsLabel();
|
||||||
this.GET_GoodData("", this.firstData.tempId);
|
|
||||||
} else {
|
|
||||||
const cateId = [];
|
|
||||||
this.firstData.category.forEach((cate) => {
|
|
||||||
this.baseInfoForm.categoryName.push(cate.name);
|
|
||||||
cateId.push(cate.id);
|
|
||||||
});
|
|
||||||
this.categoryId = cateId[2];
|
|
||||||
this.baseInfoForm.categoryPath = cateId.toString();
|
|
||||||
this.baseInfoForm.goodsType = this.firstData.goodsType;
|
|
||||||
|
|
||||||
|
|
||||||
/** 获取该商城分类下 商品参数信息 */
|
|
||||||
this.GET_GoodsParams();
|
|
||||||
/** 查询品牌列表 */
|
|
||||||
this.getGoodsBrandList();
|
|
||||||
// 获取商品单位
|
|
||||||
this.GET_GoodsUnit();
|
|
||||||
// 获取当前店铺分类
|
|
||||||
this.GET_ShopGoodsLabel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -2416,4 +2592,58 @@ export default {
|
|||||||
.refresh-icon {
|
.refresh-icon {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-tip {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 4px;
|
||||||
|
max-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-type-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-type-option-label {
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-type-option-desc {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss">
|
||||||
|
.goods-type-select-popper {
|
||||||
|
.el-select-dropdown__item {
|
||||||
|
height: auto;
|
||||||
|
min-height: 58px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-type-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-type-option-label {
|
||||||
|
color: #303133;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-type-option-desc {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
<div class="content-goods-publish">
|
<div class="content-goods-publish">
|
||||||
<div class="success" style="text-align: left">
|
<div class="success" style="text-align: left">
|
||||||
<h1>恭喜您,商品发布成功!</h1>
|
<h1>恭喜您,商品发布成功!</h1>
|
||||||
<div class="goToGoodsList" @click="gotoGoodsList">
|
|
||||||
<a>去店铺查看商品列表>></a>
|
|
||||||
</div>
|
|
||||||
<div class="operation">
|
<div class="operation">
|
||||||
<h3>您还可以:</h3>
|
<h3>您还可以:</h3>
|
||||||
<div>
|
<div>
|
||||||
@@ -34,5 +31,17 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "./addGoods.scss";
|
@import "./addGoods.scss";
|
||||||
|
|
||||||
|
.success {
|
||||||
|
a {
|
||||||
|
color: #409eff;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -253,6 +253,7 @@ export default {
|
|||||||
goodsData: [],
|
goodsData: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
tableHeight: 400,
|
tableHeight: 400,
|
||||||
|
syncingSelection: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -339,14 +340,15 @@ export default {
|
|||||||
initGoods(res) {
|
initGoods(res) {
|
||||||
const records = res?.result?.records || [];
|
const records = res?.result?.records || [];
|
||||||
this.total = res?.result?.total || 0;
|
this.total = res?.result?.total || 0;
|
||||||
|
const selectedIds = new Set(
|
||||||
|
(this.selectedList || [])
|
||||||
|
.map((e) => e.id || e.skuId)
|
||||||
|
.filter((id) => id != null)
|
||||||
|
.map(String)
|
||||||
|
);
|
||||||
records.forEach((item) => {
|
records.forEach((item) => {
|
||||||
item.selected = false;
|
|
||||||
item.___type = "goods";
|
item.___type = "goods";
|
||||||
this.selectedList.forEach((e) => {
|
item.selected = selectedIds.has(String(item.id));
|
||||||
if (e.id && e.id === item.id) {
|
|
||||||
item.selected = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
this.goodsData = records;
|
this.goodsData = records;
|
||||||
this.syncTableSelection();
|
this.syncTableSelection();
|
||||||
@@ -356,12 +358,18 @@ export default {
|
|||||||
const table = this.$refs.goodsTable;
|
const table = this.$refs.goodsTable;
|
||||||
if (!table) return;
|
if (!table) return;
|
||||||
if (this.type === "multiple") {
|
if (this.type === "multiple") {
|
||||||
|
// clearSelection/toggleRowSelection 都会触发 selection-change,
|
||||||
|
// 同步期间忽略,避免把已选状态清掉
|
||||||
|
this.syncingSelection = true;
|
||||||
table.clearSelection();
|
table.clearSelection();
|
||||||
this.goodsData.forEach((row) => {
|
this.goodsData.forEach((row) => {
|
||||||
if (row.selected) {
|
if (row.selected) {
|
||||||
table.toggleRowSelection(row, true);
|
table.toggleRowSelection(row, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.syncingSelection = false;
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selected = this.goodsData.find((item) => item.selected);
|
const selected = this.goodsData.find((item) => item.selected);
|
||||||
@@ -382,13 +390,16 @@ export default {
|
|||||||
this.$refs.goodsTable?.setCurrentRow(row);
|
this.$refs.goodsTable?.setCurrentRow(row);
|
||||||
},
|
},
|
||||||
handleSelectionChange(selection) {
|
handleSelectionChange(selection) {
|
||||||
if (this.type !== "multiple") return;
|
if (this.type !== "multiple" || this.syncingSelection) return;
|
||||||
const selectedIds = new Set(selection.map((item) => item.id));
|
const selectedIds = new Set(selection.map((item) => String(item.id)));
|
||||||
this.goodsData.forEach((item) => {
|
this.goodsData.forEach((item) => {
|
||||||
item.selected = selectedIds.has(item.id);
|
item.selected = selectedIds.has(String(item.id));
|
||||||
|
});
|
||||||
|
const currentPageIds = new Set(this.goodsData.map((item) => String(item.id)));
|
||||||
|
const otherPages = this.selectedList.filter((item) => {
|
||||||
|
const id = item.id || item.skuId;
|
||||||
|
return id != null && !currentPageIds.has(String(id));
|
||||||
});
|
});
|
||||||
const currentPageIds = new Set(this.goodsData.map((item) => item.id));
|
|
||||||
const otherPages = this.selectedList.filter((item) => !currentPageIds.has(item.id));
|
|
||||||
this.emitSelected([...otherPages, ...selection]);
|
this.emitSelected([...otherPages, ...selection]);
|
||||||
},
|
},
|
||||||
formatShopCategory(list) {
|
formatShopCategory(list) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
@click="toPrint"
|
@click="toPrint"
|
||||||
>打印电子面单</el-button>
|
>打印电子面单</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="$route.query.orderType != 'VIRTUAL'"
|
v-if="!isNonPhysicalOrder"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
style="float: right"
|
style="float: right"
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 36%; float: left">
|
<div style="width: 36%; float: left">
|
||||||
<div class="div-item" v-if="orderInfo.order.deliveryMethod != 'SELF_PICK_UP'">
|
<div class="div-item" v-if="!isECouponOrder && orderInfo.order.deliveryMethod != 'SELF_PICK_UP'">
|
||||||
<div class="div-item-left">收货信息:</div>
|
<div class="div-item-left">收货信息:</div>
|
||||||
<div class="div-item-right">
|
<div class="div-item-right">
|
||||||
{{ orderInfo.order.consigneeName }}
|
{{ orderInfo.order.consigneeName }}
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<div class="div-item" v-if="$route.query.orderType != 'VIRTUAL'">
|
<div class="div-item" v-if="!isNonPhysicalOrder">
|
||||||
<div class="div-item-left">配送方式:</div>
|
<div class="div-item-left">配送方式:</div>
|
||||||
<div class="div-item-right">
|
<div class="div-item-right">
|
||||||
{{ orderInfo.deliveryMethodValue }}
|
{{ orderInfo.deliveryMethodValue }}
|
||||||
@@ -256,6 +256,48 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 卡密信息(当前订单 orderItems;满赠赠品卡密在赠品子单详情查看) -->
|
||||||
|
<div v-if="hasCardKeySection" class="ecoupon-card-keys mt_10">
|
||||||
|
<h4>卡密信息</h4>
|
||||||
|
<div
|
||||||
|
v-for="line in cardKeyFulfillLines"
|
||||||
|
:key="line.key"
|
||||||
|
class="ecoupon-fulfill-block"
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
v-if="line.status !== 'DELIVERED'"
|
||||||
|
:type="cardKeyFulfillAlertType(line.status)"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
:title="resolveCardKeyFulfillMessage(line)"
|
||||||
|
class="mb_10"
|
||||||
|
/>
|
||||||
|
<el-table
|
||||||
|
v-else-if="line.cardKeys.length"
|
||||||
|
border
|
||||||
|
:data="lineCardKeyRows(line)"
|
||||||
|
style="width: 100%"
|
||||||
|
class="mb_10"
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||||
|
<el-table-column prop="cardNo" label="卡号" min-width="140" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="cardSecret" label="卡密" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="allocatedTime" label="发卡时间" width="170" />
|
||||||
|
<el-table-column label="状态" width="90" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row">{{ formatCardKeyStatus(row.status || 'ALLOCATED') }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button v-if="row" link type="primary" @click="copyCardKey(row)">复制</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="goods-total">
|
<div class="goods-total">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@@ -319,7 +361,7 @@
|
|||||||
<span class="label" v-if="typeList.length == 1 && index == 0" style="font-size:10px !important;"><a @click="gotoHomes" style="display: inline-block;border-top: 1px dashed;border-bottom: 1px dashed;color:black;width:80px;">{{item.promotionName}}:</a><span class="op-split">|</span>
|
<span class="label" v-if="typeList.length == 1 && index == 0" style="font-size:10px !important;"><a @click="gotoHomes" style="display: inline-block;border-top: 1px dashed;border-bottom: 1px dashed;color:black;width:80px;">{{item.promotionName}}:</a><span class="op-split">|</span>
|
||||||
<span class="txt" v-if="typeList.length == 1 && index == 0" style="border-top: 1px dashed;border-bottom: 1px dashed;font-size:10px !important;">¥{{ $filters.unitPrice(item.discountPrice) }}</span>
|
<span class="txt" v-if="typeList.length == 1 && index == 0" style="border-top: 1px dashed;border-bottom: 1px dashed;font-size:10px !important;">¥{{ $filters.unitPrice(item.discountPrice) }}</span>
|
||||||
</li> -->
|
</li> -->
|
||||||
<li>
|
<li v-if="!isECouponOrder">
|
||||||
<span class="label">运费:</span>
|
<span class="label">运费:</span>
|
||||||
<span class="txt">{{
|
<span class="txt">{{
|
||||||
$filters.unitPrice(orderInfo.order.freightPrice, "¥")
|
$filters.unitPrice(orderInfo.order.freightPrice, "¥")
|
||||||
@@ -694,6 +736,14 @@ import * as RegExp from "@/libs/RegExp.js";
|
|||||||
import multipleMap from "@/views/my-components/map/multiple-map";
|
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 {
|
||||||
|
collectCardKeyFulfillLines,
|
||||||
|
orderHasCardKeySection,
|
||||||
|
flattenOrderCardKeys,
|
||||||
|
cardKeyFulfillAlertType,
|
||||||
|
resolveCardKeyFulfillMessage,
|
||||||
|
} from "@/constants/goodsType";
|
||||||
export default {
|
export default {
|
||||||
name: "orderDetail",
|
name: "orderDetail",
|
||||||
components: {
|
components: {
|
||||||
@@ -814,11 +864,35 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
/** 电子卡券订单:隐藏物流/发货/售后,展示 orderItems[].cardKeys(FR-S-04 / FR-S-05) */
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
hasCardKeySection() {
|
||||||
|
return orderHasCardKeySection({
|
||||||
|
orderItems: this.data,
|
||||||
|
isECouponOrder: this.isECouponOrder,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cardKeyFulfillLines() {
|
||||||
|
return collectCardKeyFulfillLines(this.data);
|
||||||
|
},
|
||||||
|
ecouponCardKeyRows() {
|
||||||
|
return flattenOrderCardKeys(this.data, true);
|
||||||
|
},
|
||||||
canPartDelivery() {
|
canPartDelivery() {
|
||||||
if (!this.allowOperation.ship) return false;
|
if (!this.allowOperation.ship) return false;
|
||||||
const status = this.orderInfo.order && this.orderInfo.order.orderStatus;
|
const status = this.orderInfo.order && this.orderInfo.order.orderStatus;
|
||||||
return (
|
return (
|
||||||
this.$route.query.orderType !== "VIRTUAL" &&
|
!this.isNonPhysicalOrder &&
|
||||||
["UNDELIVERED", "PARTS_DELIVERED"].includes(status) &&
|
["UNDELIVERED", "PARTS_DELIVERED"].includes(status) &&
|
||||||
this.deliverableOrderItems.length > 0
|
this.deliverableOrderItems.length > 0
|
||||||
);
|
);
|
||||||
@@ -845,6 +919,28 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatCardKeyStatus,
|
||||||
|
cardKeyFulfillAlertType,
|
||||||
|
resolveCardKeyFulfillMessage,
|
||||||
|
lineCardKeyRows(line) {
|
||||||
|
return line.cardKeys || [];
|
||||||
|
},
|
||||||
|
copyCardKey(row) {
|
||||||
|
const text = `卡号:${row.cardNo || ""}\n卡密:${row.cardSecret || ""}`;
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.$Message.success("已复制到剪贴板");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const ta = document.createElement("textarea");
|
||||||
|
ta.value = text;
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
this.$Message.success("已复制到剪贴板");
|
||||||
|
}
|
||||||
|
},
|
||||||
getPromotionText(row) {
|
getPromotionText(row) {
|
||||||
let resultText = "";
|
let resultText = "";
|
||||||
if (row && row.promotionType) {
|
if (row && row.promotionType) {
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export default {
|
|||||||
receiptModalMode: "detail",
|
receiptModalMode: "detail",
|
||||||
uploadFileUrl: uploadFile,
|
uploadFileUrl: uploadFile,
|
||||||
accessToken: {},
|
accessToken: {},
|
||||||
receiptUploadData: { directoryPath: "receipt" },
|
receiptUploadData: { directoryPath: "default" }, // OSS 上传目录(与后端存储配置一致)
|
||||||
currentReceipt: {},
|
currentReceipt: {},
|
||||||
selectedReceiptRow: null,
|
selectedReceiptRow: null,
|
||||||
searchForm: {
|
searchForm: {
|
||||||
|
|||||||
@@ -6,7 +6,14 @@
|
|||||||
<el-input v-model="form.promotionName" style="width: 320px" />
|
<el-input v-model="form.promotionName" style="width: 320px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="活动时间" required>
|
<el-form-item label="活动时间" required>
|
||||||
<el-date-picker v-model="form.rangeTime" type="datetimerange" style="width: 360px" />
|
<el-date-picker
|
||||||
|
v-model="form.rangeTime"
|
||||||
|
class="flash-discount-range-picker"
|
||||||
|
type="datetimerange"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="限购数量">
|
<el-form-item label="限购数量">
|
||||||
<el-input-number v-model="form.limitNum" :min="0" /> <span class="tip">0 表示不限</span>
|
<el-input-number v-model="form.limitNum" :min="0" /> <span class="tip">0 表示不限</span>
|
||||||
@@ -16,12 +23,22 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="活动商品">
|
<el-form-item label="活动商品">
|
||||||
<el-button type="primary" @click="openGoods">选择商品</el-button>
|
<el-button type="primary" @click="openGoods">选择商品</el-button>
|
||||||
<el-table v-if="form.promotionGoodsList.length" :data="form.promotionGoodsList" border class="mt_10">
|
<el-table
|
||||||
<el-table-column prop="goodsName" label="商品" />
|
v-if="form.promotionGoodsList.length"
|
||||||
<el-table-column prop="originalPrice" label="原价" width="100" />
|
:data="form.promotionGoodsList"
|
||||||
<el-table-column label="活动价" width="140">
|
border
|
||||||
|
class="mt_10 flash-discount-goods-table"
|
||||||
|
>
|
||||||
|
<el-table-column prop="goodsName" label="商品" width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="originalPrice" label="原价" width="90" 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" />
|
<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>
|
||||||
@@ -38,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() {
|
||||||
@@ -63,6 +81,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
promotionPriceMin,
|
||||||
openGoods() {
|
openGoods() {
|
||||||
this.$refs.liliDialog.open("goods");
|
this.$refs.liliDialog.open("goods");
|
||||||
},
|
},
|
||||||
@@ -76,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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -83,9 +103,18 @@ export default {
|
|||||||
submit() {
|
submit() {
|
||||||
const payload = { ...this.form };
|
const payload = { ...this.form };
|
||||||
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
||||||
payload.startTime = payload.rangeTime[0];
|
const start =
|
||||||
payload.endTime = payload.rangeTime[1];
|
payload.rangeTime[0] instanceof Date
|
||||||
|
? payload.rangeTime[0]
|
||||||
|
: new Date(payload.rangeTime[0]);
|
||||||
|
const end =
|
||||||
|
payload.rangeTime[1] instanceof Date
|
||||||
|
? payload.rangeTime[1]
|
||||||
|
: new Date(payload.rangeTime[1]);
|
||||||
|
payload.startTime = this.$filters.unixToDate(start.getTime() / 1000);
|
||||||
|
payload.endTime = this.$filters.unixToDate(end.getTime() / 1000);
|
||||||
}
|
}
|
||||||
|
delete payload.rangeTime;
|
||||||
const req = payload.id ? editFlashDiscount(payload) : saveFlashDiscount(payload);
|
const req = payload.id ? editFlashDiscount(payload) : saveFlashDiscount(payload);
|
||||||
req.then((res) => {
|
req.then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
@@ -101,4 +130,16 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.tip { margin-left: 8px; color: #999; }
|
.tip { margin-left: 8px; color: #999; }
|
||||||
.mt_10 { margin-top: 10px; }
|
.mt_10 { margin-top: 10px; }
|
||||||
|
|
||||||
|
:deep(.flash-discount-range-picker.el-date-editor--datetimerange) {
|
||||||
|
width: 380px;
|
||||||
|
max-width: 380px;
|
||||||
|
flex-grow: 0;
|
||||||
|
--el-date-editor-width: 380px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-discount-goods-table {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -113,23 +113,26 @@
|
|||||||
</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">
|
||||||
<el-select
|
<div class="gift-field">
|
||||||
v-model="form.giftId"
|
<el-select
|
||||||
filterable
|
v-model="form.giftId"
|
||||||
remote
|
filterable
|
||||||
:remote-method="getGiftList"
|
remote
|
||||||
placeholder="输入赠品名称搜索"
|
:remote-method="getGiftList"
|
||||||
:disabled="fieldDisabled"
|
placeholder="输入赠品名称搜索"
|
||||||
:loading="giftLoading"
|
:disabled="fieldDisabled"
|
||||||
style="width: 260px"
|
:loading="giftLoading"
|
||||||
>
|
style="width: 260px"
|
||||||
<el-option
|
>
|
||||||
v-for="item in giftList"
|
<el-option
|
||||||
:value="item.id"
|
v-for="item in giftList"
|
||||||
:key="item.id"
|
:value="item.id"
|
||||||
:label="item.goodsName"
|
:key="item.id"
|
||||||
/>
|
:label="giftOptionLabel(item)"
|
||||||
</el-select>
|
/>
|
||||||
|
</el-select>
|
||||||
|
<p class="describe describe-block">电子卡券赠品支付后自动发卡;卡池不足时仅取消赠品子单,不影响主订单。</p>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="form.pointFlag" label="赠积分" prop="point">
|
<el-form-item v-if="form.pointFlag" label="赠积分" prop="point">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -241,6 +244,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 +317,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 +456,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;
|
||||||
@@ -500,6 +515,17 @@ h4 {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
.describe-block {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 260px;
|
||||||
|
}
|
||||||
|
.gift-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
.link-text {
|
.link-text {
|
||||||
color: #409eff;
|
color: #409eff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -6,7 +6,14 @@
|
|||||||
<el-input v-model="form.promotionName" style="width: 320px" />
|
<el-input v-model="form.promotionName" style="width: 320px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="活动时间" required>
|
<el-form-item label="活动时间" required>
|
||||||
<el-date-picker v-model="form.rangeTime" type="datetimerange" style="width: 360px" />
|
<el-date-picker
|
||||||
|
v-model="form.rangeTime"
|
||||||
|
class="nth-item-range-picker"
|
||||||
|
type="datetimerange"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="第N件" required>
|
<el-form-item label="第N件" required>
|
||||||
<el-input-number v-model="form.nthNum" :min="2" />
|
<el-input-number v-model="form.nthNum" :min="2" />
|
||||||
@@ -92,9 +99,18 @@ export default {
|
|||||||
submit() {
|
submit() {
|
||||||
const payload = { ...this.form };
|
const payload = { ...this.form };
|
||||||
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
if (payload.rangeTime && payload.rangeTime.length === 2) {
|
||||||
payload.startTime = payload.rangeTime[0];
|
const start =
|
||||||
payload.endTime = payload.rangeTime[1];
|
payload.rangeTime[0] instanceof Date
|
||||||
|
? payload.rangeTime[0]
|
||||||
|
: new Date(payload.rangeTime[0]);
|
||||||
|
const end =
|
||||||
|
payload.rangeTime[1] instanceof Date
|
||||||
|
? payload.rangeTime[1]
|
||||||
|
: new Date(payload.rangeTime[1]);
|
||||||
|
payload.startTime = this.$filters.unixToDate(start.getTime() / 1000);
|
||||||
|
payload.endTime = this.$filters.unixToDate(end.getTime() / 1000);
|
||||||
}
|
}
|
||||||
|
delete payload.rangeTime;
|
||||||
const req = payload.id ? editNthItemDiscount(payload) : saveNthItemDiscount(payload);
|
const req = payload.id ? editNthItemDiscount(payload) : saveNthItemDiscount(payload);
|
||||||
req.then((res) => {
|
req.then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
@@ -110,4 +126,11 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.tip { margin-left: 8px; color: #999; }
|
.tip { margin-left: 8px; color: #999; }
|
||||||
.mt_10 { margin-top: 10px; }
|
.mt_10 { margin-top: 10px; }
|
||||||
|
|
||||||
|
:deep(.nth-item-range-picker.el-date-editor--datetimerange) {
|
||||||
|
width: 380px;
|
||||||
|
max-width: 380px;
|
||||||
|
flex-grow: 0;
|
||||||
|
--el-date-editor-width: 380px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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