forked from gxwebsoft/mp-10550
feat(auth): 添加统一认证工具和优化登录流程
- 新增 auth 工具模块,包含 isLoggedIn、goToRegister、ensureLoggedIn 方法 - 将硬编码的服务器URL更新为 glt-server 域名 - 重构多个页面的登录检查逻辑,使用统一的认证工具 - 在用户注册/登录流程中集成邀请关系处理 - 更新注册页面配置和实现,支持跳转参数传递 - 优化分销商二维码页面的加载状态和错误处理 - 在水票使用页面添加无票时的购买引导 - 统一文件上传和API请求的服务器地址 - 添加加密库类型定义文件
This commit is contained in:
@@ -73,7 +73,7 @@ function Profile() {
|
||||
avatar: `${detail.avatarUrl}`,
|
||||
})
|
||||
Taro.uploadFile({
|
||||
url: 'https://server.websoft.top/api/oss/upload',
|
||||
url: 'https://glt-server.websoft.top/api/oss/upload',
|
||||
filePath: detail.avatarUrl,
|
||||
name: 'file',
|
||||
header: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Cell,
|
||||
CellGroup,
|
||||
ConfigProvider,
|
||||
Empty,
|
||||
Input,
|
||||
InputNumber,
|
||||
Popup,
|
||||
@@ -70,6 +71,7 @@ const OrderConfirm = () => {
|
||||
const [selectedTicketId, setSelectedTicketId] = useState<number | undefined>(undefined)
|
||||
const [ticketPopupVisible, setTicketPopupVisible] = useState(false)
|
||||
const [ticketLoading, setTicketLoading] = useState(false)
|
||||
const noTicketPromptedRef = useRef(false)
|
||||
|
||||
// Delivery range (geofence): block ordering if address/current location is outside.
|
||||
const [fences, setFences] = useState<ShopStoreFence[]>([])
|
||||
@@ -119,6 +121,11 @@ const OrderConfirm = () => {
|
||||
return Number(selectedTicket?.availableQty || 0)
|
||||
}, [selectedTicket?.availableQty])
|
||||
|
||||
const noUsableTickets = useMemo(() => {
|
||||
// Only show "go buy tickets" guidance after we have finished loading.
|
||||
return !!userId && !ticketLoading && usableTickets.length === 0
|
||||
}, [ticketLoading, usableTickets.length, userId])
|
||||
|
||||
const maxQuantity = useMemo(() => {
|
||||
const stockMax = goods?.stock ?? 999
|
||||
return Math.max(0, Math.min(stockMax, availableTicketTotal))
|
||||
@@ -428,6 +435,21 @@ const OrderConfirm = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const goBuyTickets = async () => {
|
||||
try {
|
||||
setTicketPopupVisible(false)
|
||||
// If this page is opened with a goodsId, guide user back to that goods detail to purchase.
|
||||
if (numericGoodsId) {
|
||||
await Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${numericGoodsId}` })
|
||||
return
|
||||
}
|
||||
await Taro.switchTab({ url: '/pages/index/index' })
|
||||
} catch (e) {
|
||||
console.error('跳转购买水票失败:', e)
|
||||
Taro.showToast({ title: '跳转失败,请稍后重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (submitLoading) return
|
||||
if (deliveryRangeCheckingRef.current) return
|
||||
@@ -623,6 +645,26 @@ const OrderConfirm = () => {
|
||||
}
|
||||
}, [usableTickets, selectedTicketId])
|
||||
|
||||
// If user has no usable tickets, proactively guide them to purchase (only once per page lifecycle).
|
||||
useEffect(() => {
|
||||
if (!noUsableTickets) return
|
||||
if (noTicketPromptedRef.current) return
|
||||
noTicketPromptedRef.current = true
|
||||
|
||||
;(async () => {
|
||||
const r = await Taro.showModal({
|
||||
title: '暂无可用水票',
|
||||
content: '您当前没有可用水票,购买后再来下单更方便。',
|
||||
confirmText: '去购买',
|
||||
cancelText: '暂不'
|
||||
})
|
||||
if (r.confirm) {
|
||||
await goBuyTickets()
|
||||
}
|
||||
})()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [noUsableTickets])
|
||||
|
||||
// 重新加载数据
|
||||
const handleRetry = () => {
|
||||
loadAllData()
|
||||
@@ -738,26 +780,50 @@ const OrderConfirm = () => {
|
||||
<Text>选择水票</Text>
|
||||
</View>
|
||||
)}
|
||||
extra={(
|
||||
<View className={'flex items-center gap-2'}>
|
||||
<View className={'text-gray-900'}>
|
||||
{ticketLoading
|
||||
? '加载中...'
|
||||
: (selectedTicket
|
||||
? `${selectedTicket.templateName || '水票'}(可用${selectedTicket.availableQty ?? 0})`
|
||||
: '请选择'
|
||||
)
|
||||
}
|
||||
</View>
|
||||
<ArrowRight className={'text-gray-400'} size={14}/>
|
||||
</View>
|
||||
extra={(
|
||||
<View className={'flex items-center gap-2'}>
|
||||
<View className={'text-gray-900'}>
|
||||
{ticketLoading
|
||||
? '加载中...'
|
||||
: (selectedTicket
|
||||
? `${selectedTicket.templateName || '水票'}(可用${selectedTicket.availableQty ?? 0})`
|
||||
: (noUsableTickets ? '暂无可用水票' : '请选择')
|
||||
)
|
||||
}
|
||||
</View>
|
||||
<ArrowRight className={'text-gray-400'} size={14}/>
|
||||
</View>
|
||||
)}
|
||||
onClick={async () => {
|
||||
if (ticketLoading) return
|
||||
if (noUsableTickets) {
|
||||
const r = await Taro.showModal({
|
||||
title: '暂无可用水票',
|
||||
content: '您还没有可用水票,是否前往购买?',
|
||||
confirmText: '去购买',
|
||||
cancelText: '暂不'
|
||||
})
|
||||
if (r.confirm) await goBuyTickets()
|
||||
return
|
||||
}
|
||||
setTicketPopupVisible(true)
|
||||
}}
|
||||
/>
|
||||
{noUsableTickets && (
|
||||
<Cell
|
||||
title={<Text className="text-gray-500">还没有购买水票</Text>}
|
||||
description="购买水票后即可在这里直接下单送水"
|
||||
extra={(
|
||||
<Button type="primary" size="small" onClick={goBuyTickets}>
|
||||
去购买
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
onClick={() => !ticketLoading && setTicketPopupVisible(true)}
|
||||
/>
|
||||
<Cell
|
||||
title={'本次使用'}
|
||||
extra={<View className={'font-medium'}>{displayQty} 张</View>}
|
||||
/>
|
||||
<Cell
|
||||
title={'本次使用'}
|
||||
extra={<View className={'font-medium'}>{displayQty} 张</View>}
|
||||
/>
|
||||
</CellGroup>
|
||||
|
||||
<CellGroup>
|
||||
@@ -795,26 +861,37 @@ const OrderConfirm = () => {
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<CellGroup>
|
||||
{usableTickets.map((t) => {
|
||||
const active = selectedTicket?.id && Number(selectedTicket.id) === Number(t.id)
|
||||
return (
|
||||
<Cell
|
||||
key={t.id}
|
||||
title={<Text className={active ? 'text-green-600' : ''}>票号 {t.id}</Text>}
|
||||
description={t.orderNo ? `来源订单:${t.orderNo}` : ''}
|
||||
extra={<Text className="text-gray-700">可用 {t.availableQty ?? 0}</Text>}
|
||||
onClick={() => {
|
||||
setSelectedTicketId(Number(t.id))
|
||||
setTicketPopupVisible(false)
|
||||
Taro.showToast({ title: '水票已选择', icon: 'success' })
|
||||
}}
|
||||
/>
|
||||
)})}
|
||||
{!usableTickets.length && (
|
||||
<Cell title={<Text className="text-gray-500">暂无可用水票</Text>} />
|
||||
<>
|
||||
{!!usableTickets.length ? (
|
||||
<CellGroup>
|
||||
{usableTickets.map((t) => {
|
||||
const active = selectedTicket?.id && Number(selectedTicket.id) === Number(t.id)
|
||||
return (
|
||||
<Cell
|
||||
key={t.id}
|
||||
title={<Text className={active ? 'text-green-600' : ''}>票号 {t.id}</Text>}
|
||||
description={t.orderNo ? `来源订单:${t.orderNo}` : ''}
|
||||
extra={<Text className="text-gray-700">可用 {t.availableQty ?? 0}</Text>}
|
||||
onClick={() => {
|
||||
setSelectedTicketId(Number(t.id))
|
||||
setTicketPopupVisible(false)
|
||||
Taro.showToast({ title: '水票已选择', icon: 'success' })
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</CellGroup>
|
||||
) : (
|
||||
<View className="py-10 text-center">
|
||||
<Empty description="暂无可用水票" />
|
||||
<View className="mt-4 flex justify-center">
|
||||
<Button type="primary" onClick={goBuyTickets}>
|
||||
去购买水票
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</CellGroup>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Popup>
|
||||
@@ -889,29 +966,35 @@ const OrderConfirm = () => {
|
||||
</span>
|
||||
<span className={'text-sm text-gray-500'}>张</span>
|
||||
</View>
|
||||
</div>
|
||||
<div className={'buy-btn mx-4'}>
|
||||
<Button
|
||||
type="success"
|
||||
size="large"
|
||||
loading={submitLoading || deliveryRangeChecking}
|
||||
disabled={
|
||||
deliveryRangeChecking ||
|
||||
inDeliveryRange === false ||
|
||||
!selectedTicket?.id ||
|
||||
availableTicketTotal <= 0 ||
|
||||
!canStartOrder
|
||||
}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{deliveryRangeChecking
|
||||
? '校验配送范围...'
|
||||
: (inDeliveryRange === false ? '不在配送范围' : (submitLoading ? '提交中...' : '立即提交'))
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
</View>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'buy-btn mx-4'}>
|
||||
{noUsableTickets ? (
|
||||
<Button type="primary" size="large" onClick={goBuyTickets}>
|
||||
去购买水票
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="success"
|
||||
size="large"
|
||||
loading={submitLoading || deliveryRangeChecking}
|
||||
disabled={
|
||||
deliveryRangeChecking ||
|
||||
inDeliveryRange === false ||
|
||||
!selectedTicket?.id ||
|
||||
availableTicketTotal <= 0 ||
|
||||
!canStartOrder
|
||||
}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{deliveryRangeChecking
|
||||
? '校验配送范围...'
|
||||
: (inDeliveryRange === false ? '不在配送范围' : (submitLoading ? '提交中...' : '立即提交'))
|
||||
}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</View>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user