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