feat(ticket): 实现基于模板配置的动态起送数量功能
- 引入 gltTicketTemplate API 获取模板配置 - 将固定起送数量改为动态可配置的最小起送数量 - 添加基于商品ID或票据模板ID获取起送配置的功能 - 实现页面初始化时从票据模板加载起送数量配置 - 更新用户界面显示实际的动态起送数量要求 - 添加异步加载和取消请求的安全处理机制
This commit is contained in:
@@ -26,6 +26,7 @@ import {getShopStore, listShopStore} from "@/api/shop/shopStore";
|
||||
import {getSelectedStoreFromStorage, saveSelectedStoreToStorage} from "@/utils/storeSelection";
|
||||
import type { GltUserTicket } from '@/api/glt/gltUserTicket/model'
|
||||
import { getGltUserTicket, listGltUserTicket } from '@/api/glt/gltUserTicket'
|
||||
import { getGltTicketTemplate, getGltTicketTemplateByGoodsId } from '@/api/glt/gltTicketTemplate'
|
||||
import { addGltTicketOrder, getGltTicketOrder, updateGltTicketOrder } from '@/api/glt/gltTicketOrder'
|
||||
import { pageGltTicketOrder } from '@/api/glt/gltTicketOrder'
|
||||
import type { GltTicketOrder } from '@/api/glt/gltTicketOrder/model'
|
||||
@@ -35,13 +36,14 @@ import type { ShopStoreFence } from '@/api/shop/shopStoreFence/model'
|
||||
import { listShopStoreFence } from '@/api/shop/shopStoreFence'
|
||||
import { parseFencePoints, parseLngLatFromText, pointInAnyPolygon, pointInPolygon } from '@/utils/geofence'
|
||||
|
||||
const MIN_START_QTY = 10
|
||||
const DEFAULT_MIN_START_QTY = 10
|
||||
const ADDRESS_CHANGE_COOLDOWN_DAYS = 30
|
||||
|
||||
const OrderConfirm = () => {
|
||||
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
||||
const [address, setAddress] = useState<ShopUserAddress>()
|
||||
const [quantity, setQuantity] = useState<number>(MIN_START_QTY)
|
||||
const [minStartQty, setMinStartQty] = useState<number>(DEFAULT_MIN_START_QTY)
|
||||
const [quantity, setQuantity] = useState<number>(DEFAULT_MIN_START_QTY)
|
||||
const [orderRemark, setOrderRemark] = useState<string>('')
|
||||
// Delivery date only (no hour/min selection).
|
||||
const [sendTime, setSendTime] = useState<Date>(() => dayjs().startOf('day').toDate())
|
||||
@@ -325,13 +327,13 @@ const OrderConfirm = () => {
|
||||
}, [availableTicketTotal, editingOrder?.totalNum, editingOrder?.userTicketId, goods?.stock, isEditMode, tickets])
|
||||
|
||||
const canStartOrder = useMemo(() => {
|
||||
return maxQuantity >= MIN_START_QTY
|
||||
}, [maxQuantity])
|
||||
return maxQuantity >= minStartQty
|
||||
}, [maxQuantity, minStartQty])
|
||||
|
||||
const displayQty = useMemo(() => {
|
||||
if (!canStartOrder) return 0
|
||||
return Math.max(MIN_START_QTY, Math.min(quantity, maxQuantity))
|
||||
}, [quantity, maxQuantity, canStartOrder])
|
||||
return Math.max(minStartQty, Math.min(quantity, maxQuantity))
|
||||
}, [quantity, maxQuantity, canStartOrder, minStartQty])
|
||||
|
||||
const sendTimeText = useMemo(() => {
|
||||
return dayjs(sendTime).format('YYYY-MM-DD')
|
||||
@@ -600,7 +602,7 @@ const OrderConfirm = () => {
|
||||
setQuantity(0)
|
||||
return
|
||||
}
|
||||
setQuantity(Math.max(MIN_START_QTY, Math.min(newQuantity || MIN_START_QTY, upper)))
|
||||
setQuantity(Math.max(minStartQty, Math.min(newQuantity || minStartQty, upper)))
|
||||
}
|
||||
|
||||
const loadUserTickets = async () => {
|
||||
@@ -718,8 +720,8 @@ const OrderConfirm = () => {
|
||||
Taro.showToast({ title: '商品库存不足', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (finalQty < MIN_START_QTY) {
|
||||
Taro.showToast({ title: `最低起送 ${MIN_START_QTY} 桶`, icon: 'none' })
|
||||
if (finalQty < minStartQty) {
|
||||
Taro.showToast({ title: `最低起送 ${minStartQty} 桶`, icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!sendTime) {
|
||||
@@ -863,8 +865,8 @@ const OrderConfirm = () => {
|
||||
return
|
||||
}
|
||||
|
||||
const initQty = Number(editingOrderRes.totalNum ?? MIN_START_QTY)
|
||||
setQuantity(Number.isFinite(initQty) && initQty > 0 ? initQty : MIN_START_QTY)
|
||||
const initQty = Number(editingOrderRes.totalNum ?? minStartQty)
|
||||
setQuantity(Number.isFinite(initQty) && initQty > 0 ? initQty : minStartQty)
|
||||
setOrderRemark(String(editingOrderRes.buyerRemarks || ''))
|
||||
const st = parseTime(editingOrderRes.sendTime)
|
||||
if (st) setSendTime(st.startOf('day').toDate())
|
||||
@@ -1000,11 +1002,54 @@ const OrderConfirm = () => {
|
||||
useEffect(() => {
|
||||
setQuantity(prev => {
|
||||
if (maxQuantity <= 0) return 0
|
||||
if (maxQuantity < MIN_START_QTY) return 0
|
||||
if (!prev || prev < MIN_START_QTY) return MIN_START_QTY
|
||||
if (maxQuantity < minStartQty) return 0
|
||||
if (!prev || prev < minStartQty) return minStartQty
|
||||
return Math.min(prev, maxQuantity)
|
||||
})
|
||||
}, [maxQuantity])
|
||||
}, [maxQuantity, minStartQty])
|
||||
|
||||
const minStartQtyKey = useMemo(() => {
|
||||
const gid = Number(goods?.goodsId)
|
||||
if (Number.isFinite(gid) && gid > 0) return `g:${gid}`
|
||||
|
||||
// If there is exactly one ticket template available, infer min start qty from it (covers "稍后再送" without goodsId).
|
||||
const ids = Array.from(
|
||||
new Set(
|
||||
(usableTickets || [])
|
||||
.map(t => Number(t?.templateId))
|
||||
.filter(id => Number.isFinite(id) && id > 0)
|
||||
)
|
||||
)
|
||||
if (ids.length === 1) return `t:${ids[0]}`
|
||||
return ''
|
||||
}, [goods?.goodsId, usableTickets])
|
||||
|
||||
// Use configured min start-send qty from ticket template (by goodsId or by user's unique templateId).
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
;(async () => {
|
||||
try {
|
||||
if (!minStartQtyKey) {
|
||||
setMinStartQty(DEFAULT_MIN_START_QTY)
|
||||
return
|
||||
}
|
||||
const [kind, rawId] = minStartQtyKey.split(':')
|
||||
const id = Number(rawId)
|
||||
const tpl =
|
||||
kind === 'g'
|
||||
? await getGltTicketTemplateByGoodsId(id)
|
||||
: await getGltTicketTemplate(id)
|
||||
const n = Number(tpl?.startSendQty)
|
||||
const safe = Number.isFinite(n) && n > 0 ? n : DEFAULT_MIN_START_QTY
|
||||
if (!cancelled) setMinStartQty(safe)
|
||||
} catch (_e) {
|
||||
if (!cancelled) setMinStartQty(DEFAULT_MIN_START_QTY)
|
||||
}
|
||||
})()
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [minStartQtyKey])
|
||||
|
||||
// If user has no usable tickets, proactively guide them to purchase (only once per page lifecycle).
|
||||
useEffect(() => {
|
||||
@@ -1138,16 +1183,16 @@ const OrderConfirm = () => {
|
||||
title={'送水数量'}
|
||||
description={
|
||||
canStartOrder
|
||||
? `最低起送 ${MIN_START_QTY} 桶`
|
||||
: `最低起送 ${MIN_START_QTY} 桶(当前最多 ${maxQuantity} 桶)`
|
||||
? `最低起送 ${minStartQty} 桶`
|
||||
: `最低起送 ${minStartQty} 桶(当前最多 ${maxQuantity} 桶)`
|
||||
}
|
||||
extra={(
|
||||
<ConfigProvider theme={customTheme}>
|
||||
<InputNumber
|
||||
value={displayQty}
|
||||
min={canStartOrder ? MIN_START_QTY : 0}
|
||||
min={canStartOrder ? minStartQty : 0}
|
||||
max={canStartOrder ? maxQuantity : 0}
|
||||
step={10}
|
||||
step={minStartQty >= 10 ? 10 : 1}
|
||||
readOnly
|
||||
disabled={!canStartOrder}
|
||||
onChange={handleQuantityChange}
|
||||
|
||||
Reference in New Issue
Block a user