diff --git a/src/api/glt/gltUserTicketRelease/index.ts b/src/api/glt/gltUserTicketRelease/index.ts index 40b5e7d..bb9404d 100644 --- a/src/api/glt/gltUserTicketRelease/index.ts +++ b/src/api/glt/gltUserTicketRelease/index.ts @@ -8,9 +8,7 @@ import type { GltUserTicketRelease, GltUserTicketReleaseParam } from './model'; export async function pageGltUserTicketRelease(params: GltUserTicketReleaseParam) { const res = await request.get>>( '/glt/glt-user-ticket-release/page', - { - params - } + params ); if (res.code === 0) { return res.data; @@ -24,9 +22,7 @@ export async function pageGltUserTicketRelease(params: GltUserTicketReleaseParam export async function listGltUserTicketRelease(params?: GltUserTicketReleaseParam) { const res = await request.get>( '/glt/glt-user-ticket-release', - { - params - } + params ); if (res.code === 0 && res.data) { return res.data; diff --git a/src/app.config.ts b/src/app.config.ts index f0e520f..8a1a0d3 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -56,6 +56,7 @@ export default { "points/points", "ticket/index", "ticket/use", + "ticket/release/index", "ticket/orders/index", // "gift/index", // "gift/redeem", diff --git a/src/user/ticket/index.tsx b/src/user/ticket/index.tsx index 9583dfb..6b854bb 100644 --- a/src/user/ticket/index.tsx +++ b/src/user/ticket/index.tsx @@ -116,6 +116,20 @@ const UserTicketList = () => { await Taro.navigateTo({ url }); }; + const goReleasePlanDetail = async (ticket: GltUserTicket) => { + if (!ticket?.id) { + Taro.showToast({ title: '水票信息不完整', icon: 'none' }); + return; + } + const url = `/user/ticket/release/index?userTicketId=${encodeURIComponent(String(ticket.id))}&templateName=${encodeURIComponent( + String(ticket.templateName ?? '') + )}&frozenQty=${encodeURIComponent(String(ticket.frozenQty ?? 0))}&releasedQty=${encodeURIComponent( + String(ticket.releasedQty ?? 0) + )}`; + if (!ensureLoggedIn(url)) return; + await Taro.navigateTo({ url }); + }; + const showTicketDetail = (ticket: GltUserTicket) => { const lines: string[] = []; if (ticket.templateName) lines.push(`水票:${ticket.templateName}`); @@ -566,6 +580,9 @@ const UserTicketList = () => { 票号:{item.id} + + 套票名称:{item.templateName} + {item.orderNo && ( 订单编号:{item.orderNo} @@ -617,7 +634,14 @@ const UserTicketList = () => { {item.usedQty ?? 0} 已用水票 - + { + e.stopPropagation(); + void goReleasePlanDetail(item); + }} + > {item.frozenQty ?? 0} 剩余赠票 diff --git a/src/user/ticket/release/index.config.ts b/src/user/ticket/release/index.config.ts new file mode 100644 index 0000000..4a9ac3c --- /dev/null +++ b/src/user/ticket/release/index.config.ts @@ -0,0 +1,6 @@ +export default definePageConfig({ + navigationBarTitleText: '释放计划', + navigationBarTextStyle: 'black', + navigationBarBackgroundColor: '#ffffff' +}) + diff --git a/src/user/ticket/release/index.tsx b/src/user/ticket/release/index.tsx new file mode 100644 index 0000000..bceb4fd --- /dev/null +++ b/src/user/ticket/release/index.tsx @@ -0,0 +1,221 @@ +import { useState } from 'react' +import Taro, { useDidShow } from '@tarojs/taro' +import { View, Text } from '@tarojs/components' +import { ConfigProvider, Empty, InfiniteLoading, Loading, PullToRefresh, Tag } from '@nutui/nutui-react-taro' +import dayjs from 'dayjs' + +import { pageGltUserTicketRelease } from '@/api/glt/gltUserTicketRelease' +import type { GltUserTicketRelease } from '@/api/glt/gltUserTicketRelease/model' +import { ensureLoggedIn } from '@/utils/auth' + +const PAGE_SIZE = 10 + +export default function TicketReleasePlanPage() { + const [list, setList] = useState([]) + const [loading, setLoading] = useState(false) + const [hasMore, setHasMore] = useState(true) + const [page, setPage] = useState(1) + const [total, setTotal] = useState(undefined) + + const router = Taro.getCurrentInstance().router + const userTicketId = String(router?.params?.userTicketId || '').trim() + const templateName = (() => { + const raw = String(router?.params?.templateName || '') + try { + return decodeURIComponent(raw) + } catch { + return raw + } + })() + const frozenQtyText = router?.params?.frozenQty !== undefined ? String(router?.params?.frozenQty) : undefined + const releasedQtyText = router?.params?.releasedQty !== undefined ? String(router?.params?.releasedQty) : undefined + + const getUserId = () => { + const raw = Taro.getStorageSync('UserId') + const id = Number(raw) + return Number.isFinite(id) && id > 0 ? id : undefined + } + + const getStatusMeta = (item: GltUserTicketRelease) => { + const status = Number(item.status) + if (status === 1) return { text: '已释放', type: 'success' as const } + if (status === 0) return { text: '待释放', type: 'warning' as const } + return { text: `状态${Number.isFinite(status) ? status : '-'}`, type: 'primary' as const } + } + + const formatDateTime = (v?: string) => { + if (!v) return '-' + const d = dayjs(v) + return d.isValid() ? d.format('YYYY年MM月DD日 HH:mm:ss') : v + } + + const reload = async (isRefresh = true) => { + if (loading) return + + const uid = getUserId() + if (!uid) { + setList([]) + setHasMore(false) + setTotal(0) + return + } + if (!userTicketId) { + setList([]) + setHasMore(false) + setTotal(0) + return + } + + if (isRefresh) { + setPage(1) + setList([]) + setHasMore(true) + } + + setLoading(true) + try { + const currentPage = isRefresh ? 1 : page + const res = await pageGltUserTicketRelease({ + page: currentPage, + limit: PAGE_SIZE, + userId: uid, + userTicketId: userTicketId as any + } as any) + + const incoming = Array.isArray(res?.list) ? res.list : [] + const safe = incoming + .filter(r => Number((r as any)?.deleted) !== 1) + .filter(r => !userTicketId || String(r.userTicketId || '') === userTicketId) + .sort((a, b) => { + const at = dayjs(a.releaseTime || a.createTime || 0).valueOf() + const bt = dayjs(b.releaseTime || b.createTime || 0).valueOf() + return bt - at + }) + + const nextList = isRefresh ? safe : list.concat(safe) + setList(nextList) + + const serverCount = typeof (res as any)?.count === 'number' ? Number((res as any).count) : undefined + const nextTotal = typeof serverCount === 'number' ? serverCount : nextList.length + setTotal(nextTotal) + setHasMore(typeof serverCount === 'number' ? nextList.length < serverCount : incoming.length >= PAGE_SIZE) + + if (incoming.length > 0) setPage(currentPage + 1) + else setHasMore(false) + } catch (e) { + console.error('加载释放计划失败:', e) + Taro.showToast({ title: '加载失败', icon: 'none' }) + setHasMore(false) + } finally { + setLoading(false) + } + } + + useDidShow(() => { + const redirect = userTicketId + ? `/user/ticket/release/index?userTicketId=${encodeURIComponent(userTicketId)}` + : '/user/ticket/index' + if (!ensureLoggedIn(redirect)) return + void reload(true) + }) + + const handleRefresh = async () => { + await reload(true) + } + + const loadMore = async () => { + if (!loading && hasMore) await reload(false) + } + + return ( + + + + + + + 释放计划明细 + {typeof total === 'number' ? ( + 共 {total} 条 + ) : null} + + + 票号:{userTicketId || '-'} + + {templateName ? ( + + 套票名称:{templateName} + + ) : null} + {frozenQtyText !== undefined || releasedQtyText !== undefined ? ( + + {frozenQtyText !== undefined ? ( + + 剩余赠票:{frozenQtyText} + + ) : null} + {releasedQtyText !== undefined ? ( + + 已释放:{releasedQtyText} + + ) : null} + + ) : null} + + + {list.length === 0 && !loading ? ( + + + + ) : ( + + + 加载中... + + } + loadMoreText={ + + {list.length === 0 ? '暂无数据' : '没有更多了'} + + } + > + + {list.map((item, index) => { + const meta = getStatusMeta(item) + return ( + + + + + 周期:{item.periodNo ?? '-'} + + + 释放数量:{item.releaseQty ?? 0} + + + 释放时间:{formatDateTime(item.releaseTime)} + + + {meta.text} + + + ) + })} + + + )} + + + + + ) +} + diff --git a/src/user/ticket/use.tsx b/src/user/ticket/use.tsx index 8a98f8f..70ddd5b 100644 --- a/src/user/ticket/use.tsx +++ b/src/user/ticket/use.tsx @@ -13,7 +13,7 @@ import { Space } from '@nutui/nutui-react-taro' import { ArrowRight, Location, Ticket } from '@nutui/icons-react-taro' -import dayjs from 'dayjs' +import dayjs, { type Dayjs } from 'dayjs' import type { ShopGoods } from '@/api/shop/shopGoods/model' import { getShopGoods } from '@/api/shop/shopGoods' import { getShopUserAddress, listShopUserAddress } from '@/api/shop/shopUserAddress' @@ -136,6 +136,12 @@ const OrderConfirm = () => { return d.isValid() ? d : null } + const clampSendDateToToday = (d: Dayjs) => { + const today = dayjs().startOf('day') + if (!d.isValid()) return today + return d.isBefore(today, 'day') ? today : d.startOf('day') + } + const getOrderTime = (o?: Partial | null) => { return parseTime(o?.createTime) || parseTime(o?.updateTime) } @@ -728,6 +734,11 @@ const OrderConfirm = () => { Taro.showToast({ title: '请选择配送时间', icon: 'none' }) return } + if (dayjs(sendTime).isBefore(dayjs().startOf('day'), 'day')) { + Taro.showToast({ title: '配送时间不能早于今天', icon: 'none' }) + setSendTime(dayjs().startOf('day').toDate()) + return + } // 配送范围校验(电子围栏) const ok = await ensureInDeliveryRange() @@ -869,7 +880,7 @@ const OrderConfirm = () => { setQuantity(Number.isFinite(initQty) && initQty > 0 ? initQty : minStartQty) setOrderRemark(String(editingOrderRes.buyerRemarks || '')) const st = parseTime(editingOrderRes.sendTime) - if (st) setSendTime(st.startOf('day').toDate()) + if (st) setSendTime(clampSendDateToToday(st).toDate()) const addrId = Number(editingOrderRes.addressId) const addrIdSafe = Number.isFinite(addrId) && addrId > 0 ? addrId : undefined @@ -1162,11 +1173,18 @@ const OrderConfirm = () => { extra={( { const v = (e as any)?.detail?.value const d = dayjs(v) - if (d.isValid()) setSendTime(d.startOf('day').toDate()) + if (!d.isValid()) return + if (d.isBefore(dayjs().startOf('day'), 'day')) { + Taro.showToast({ title: '配送时间不能早于今天', icon: 'none' }) + setSendTime(dayjs().startOf('day').toDate()) + return + } + setSendTime(d.startOf('day').toDate()) }} >