forked from gxwebsoft/mp-10550
feat(shop): 添加套票活动功能并优化购买数量控制
- 在仓库模型中添加状态字段 - 实现套票活动最低购买量的灵活配置,优先取模板配置值 - 优化数量输入逻辑,支持套票活动下的默认数量设置 - 改进优惠券加载逻辑,使用初始数量对应总价进行推荐 - 修复商品信息加载顺序,确保套票模板数据正确应用 - 更新支付工具类中的仓库类型引用 - 调整数量输入组件的最小值和禁用状态逻辑
This commit is contained in:
@@ -28,6 +28,8 @@ export interface ShopStoreWarehouse {
|
|||||||
lngAndLat?: string;
|
lngAndLat?: string;
|
||||||
// 用户ID
|
// 用户ID
|
||||||
userId?: number;
|
userId?: number;
|
||||||
|
// 状态
|
||||||
|
status?: number;
|
||||||
// 备注
|
// 备注
|
||||||
comments?: string;
|
comments?: string;
|
||||||
// 排序号
|
// 排序号
|
||||||
|
|||||||
@@ -89,8 +89,12 @@ const OrderConfirm = () => {
|
|||||||
ticketTemplate.status !== 1 &&
|
ticketTemplate.status !== 1 &&
|
||||||
ticketTemplate.deleted !== 1
|
ticketTemplate.deleted !== 1
|
||||||
|
|
||||||
// 需求:套票活动最低购买量固定为 20 桶
|
// 套票活动最低购买量:优先取模板配置
|
||||||
const minBuyQty = isTicketTemplateActive ? 20 : 1
|
const ticketMinBuyQty = (() => {
|
||||||
|
const n = Number(ticketTemplate?.minBuyQty)
|
||||||
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1
|
||||||
|
})()
|
||||||
|
const minBuyQty = isTicketTemplateActive ? ticketMinBuyQty : 1
|
||||||
|
|
||||||
const getGiftTicketQty = (buyQty: number) => {
|
const getGiftTicketQty = (buyQty: number) => {
|
||||||
if (!isTicketTemplateActive) return 0
|
if (!isTicketTemplateActive) return 0
|
||||||
@@ -165,8 +169,9 @@ const OrderConfirm = () => {
|
|||||||
|
|
||||||
// 处理数量变化
|
// 处理数量变化
|
||||||
const handleQuantityChange = (value: string | number) => {
|
const handleQuantityChange = (value: string | number) => {
|
||||||
const newQuantity = typeof value === 'string' ? parseInt(value) || 1 : value
|
const fallback = isTicketTemplateActive ? minBuyQty : 1
|
||||||
const finalQuantity = Math.max(1, Math.min(newQuantity, goods?.stock || 999))
|
const newQuantity = typeof value === 'string' ? parseInt(value, 10) || fallback : value
|
||||||
|
const finalQuantity = Math.max(fallback, Math.min(newQuantity, goods?.stock || 999))
|
||||||
setQuantity(finalQuantity)
|
setQuantity(finalQuantity)
|
||||||
|
|
||||||
// 数量变化时,重新排序优惠券并检查当前选中的优惠券是否还可用
|
// 数量变化时,重新排序优惠券并检查当前选中的优惠券是否还可用
|
||||||
@@ -305,7 +310,7 @@ const OrderConfirm = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载用户优惠券
|
// 加载用户优惠券
|
||||||
const loadUserCoupons = async () => {
|
const loadUserCoupons = async (totalOverride?: number) => {
|
||||||
try {
|
try {
|
||||||
setCouponLoading(true)
|
setCouponLoading(true)
|
||||||
|
|
||||||
@@ -317,7 +322,7 @@ const OrderConfirm = () => {
|
|||||||
const transformedCoupons = res.map(transformCouponData)
|
const transformedCoupons = res.map(transformCouponData)
|
||||||
|
|
||||||
// 按优惠金额排序
|
// 按优惠金额排序
|
||||||
const total = getGoodsTotal()
|
const total = totalOverride ?? getGoodsTotal()
|
||||||
const sortedCoupons = sortCoupons(transformedCoupons, total)
|
const sortedCoupons = sortCoupons(transformedCoupons, total)
|
||||||
const usableCoupons = filterUsableCoupons(sortedCoupons, total)
|
const usableCoupons = filterUsableCoupons(sortedCoupons, total)
|
||||||
|
|
||||||
@@ -592,22 +597,42 @@ const OrderConfirm = () => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
// 设置商品信息
|
// 设置商品信息
|
||||||
if (goodsRes) {
|
|
||||||
setGoods(goodsRes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询当前商品是否存在水票套票活动(失败/无数据时不影响正常下单)
|
// 查询当前商品是否存在水票套票活动(失败/无数据时不影响正常下单)
|
||||||
|
let tpl: GltTicketTemplate | null = null
|
||||||
if (goodsId) {
|
if (goodsId) {
|
||||||
try {
|
try {
|
||||||
const tpl = await getGltTicketTemplateByGoodsId(Number(goodsId))
|
tpl = await getGltTicketTemplateByGoodsId(Number(goodsId))
|
||||||
setTicketTemplate(tpl)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setTicketTemplate(null)
|
tpl = null
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setTicketTemplate(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tplActive =
|
||||||
|
!!tpl &&
|
||||||
|
tpl.enabled !== false &&
|
||||||
|
tpl.status !== 1 &&
|
||||||
|
tpl.deleted !== 1
|
||||||
|
|
||||||
|
const tplMinBuyQty = (() => {
|
||||||
|
const n = Number(tpl?.minBuyQty)
|
||||||
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1
|
||||||
|
})()
|
||||||
|
|
||||||
|
// 设置商品信息(若存在套票模板,则默认 canBuyNumber 使用模板最小购买量)
|
||||||
|
if (goodsRes) {
|
||||||
|
const patchedGoods: ShopGoods = { ...goodsRes }
|
||||||
|
if (tplActive && ((patchedGoods.canBuyNumber ?? 0) === 0)) {
|
||||||
|
patchedGoods.canBuyNumber = tplMinBuyQty
|
||||||
|
}
|
||||||
|
setGoods(patchedGoods)
|
||||||
|
|
||||||
|
// 设置默认购买数量:优先使用 canBuyNumber,否则使用 1
|
||||||
|
const initQty = (patchedGoods.canBuyNumber ?? 0) > 0 ? (patchedGoods.canBuyNumber as number) : 1
|
||||||
|
setQuantity(initQty)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTicketTemplate(tpl)
|
||||||
|
|
||||||
// 设置默认收货地址
|
// 设置默认收货地址
|
||||||
if (addressRes && addressRes.length > 0) {
|
if (addressRes && addressRes.length > 0) {
|
||||||
setAddress(addressRes[0])
|
setAddress(addressRes[0])
|
||||||
@@ -622,9 +647,16 @@ const OrderConfirm = () => {
|
|||||||
setPayment(paymentRes[0])
|
setPayment(paymentRes[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载优惠券(在商品信息加载完成后)
|
// 加载优惠券:使用“初始数量”对应的总价做推荐,避免默认数量变化导致推荐不准
|
||||||
if (goodsRes) {
|
if (goodsRes) {
|
||||||
await loadUserCoupons()
|
const initQty = (() => {
|
||||||
|
const n = Number(goodsRes?.canBuyNumber)
|
||||||
|
if (Number.isFinite(n) && n > 0) return Math.floor(n)
|
||||||
|
if (tplActive) return tplMinBuyQty
|
||||||
|
return 1
|
||||||
|
})()
|
||||||
|
const total = parseFloat(goodsRes.price || '0') * initQty
|
||||||
|
await loadUserCoupons(total)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载数据失败:', err)
|
console.error('加载数据失败:', err)
|
||||||
@@ -737,9 +769,9 @@ const OrderConfirm = () => {
|
|||||||
<ConfigProvider theme={customTheme}>
|
<ConfigProvider theme={customTheme}>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
value={quantity}
|
value={quantity}
|
||||||
min={1}
|
min={isTicketTemplateActive ? minBuyQty : 1}
|
||||||
max={goods.stock || 999}
|
max={goods.stock || 999}
|
||||||
disabled={goods.canBuyNumber != 0}
|
disabled={((goods.canBuyNumber ?? 0) !== 0) && !isTicketTemplateActive}
|
||||||
onChange={handleQuantityChange}
|
onChange={handleQuantityChange}
|
||||||
/>
|
/>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createOrder, WxPayResult } from '@/api/shop/shopOrder';
|
|||||||
import { OrderCreateRequest } from '@/api/shop/shopOrder/model';
|
import { OrderCreateRequest } from '@/api/shop/shopOrder/model';
|
||||||
import { getSelectedStoreFromStorage, getSelectedStoreIdFromStorage } from '@/utils/storeSelection';
|
import { getSelectedStoreFromStorage, getSelectedStoreIdFromStorage } from '@/utils/storeSelection';
|
||||||
import type { ShopStoreRider } from '@/api/shop/shopStoreRider/model';
|
import type { ShopStoreRider } from '@/api/shop/shopStoreRider/model';
|
||||||
import type { ShopWarehouse } from '@/api/shop/shopWarehouse/model';
|
import type { ShopStoreWarehouse } from '@/api/shop/shopStoreWarehouse/model';
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,7 +30,7 @@ export interface PaymentCallback {
|
|||||||
export class PaymentHandler {
|
export class PaymentHandler {
|
||||||
// 简单缓存,避免频繁请求(小程序单次运行生命周期内有效)
|
// 简单缓存,避免频繁请求(小程序单次运行生命周期内有效)
|
||||||
private static storeRidersCache = new Map<number, ShopStoreRider[]>();
|
private static storeRidersCache = new Map<number, ShopStoreRider[]>();
|
||||||
private static warehousesCache: ShopWarehouse[] | null = null;
|
private static warehousesCache: ShopStoreWarehouse[] | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行支付
|
* 执行支付
|
||||||
@@ -220,10 +220,10 @@ export class PaymentHandler {
|
|||||||
return sorted[0]?.userId;
|
return sorted[0]?.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async getWarehouses(): Promise<ShopWarehouse[]> {
|
private static async getWarehouses(): Promise<ShopStoreWarehouse[]> {
|
||||||
if (this.warehousesCache) return this.warehousesCache;
|
if (this.warehousesCache) return this.warehousesCache;
|
||||||
const list = await this.listByCompatEndpoint<ShopWarehouse>(
|
const list = await this.listByCompatEndpoint<ShopStoreWarehouse>(
|
||||||
['/shop/shop-warehouse'],
|
['/shop/shop-store-warehouse'],
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const usable = (list || []).filter(w => w?.isDelete !== 1 && (w.status === undefined || w.status === 1));
|
const usable = (list || []).filter(w => w?.isDelete !== 1 && (w.status === undefined || w.status === 1));
|
||||||
|
|||||||
Reference in New Issue
Block a user