添加登录和后端校验

完成后端设计(未在本仓库体现),通过安全的手段完成了登录鉴权
This commit is contained in:
2026-08-06 16:27:36 +08:00
commit b19a56003f
286 changed files with 51650 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
import { View, Text, Image, Button } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useState, useEffect } from 'react'
import './index.scss'
import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import { getOrderList, getDefaultAddress, getAddressList, type AddressItem, type OrderItem } from '../../utils/store'
import { PRODUCT_ICON_MAP } from '../../utils/productConfig'
export default function OrderDetailPage() {
const { theme, resolvedTheme } = useThemeContext()
const safe = useSafeArea()
useStatusBar(resolvedTheme)
const params = Taro.getCurrentInstance().router?.params
const orderId = params?.orderId || ''
const [order, setOrder] = useState<OrderItem | null>(null)
const [address, setAddress] = useState<AddressItem | null>(null)
const [showAddrPicker, setShowAddrPicker] = useState(false)
const [addrList, setAddrList] = useState<AddressItem[]>([])
useEffect(() => {
const orders = getOrderList()
const o = orders.find(x => x.id === orderId)
if (o) {
setOrder(o)
// 优先显示订单已有地址,否则用默认地址
if ((o as any).address) {
setAddress((o as any).address)
} else {
setAddress(getDefaultAddress())
}
}
setAddrList(getAddressList())
}, [orderId])
const handlePickAddress = (addr: AddressItem) => {
setAddress(addr)
setShowAddrPicker(false)
// 同步更新订单里的地址
const orders = getOrderList()
const idx = orders.findIndex(x => x.id === orderId)
if (idx >= 0) {
orders[idx] = { ...orders[idx], address: addr } as any
// 这里不直接写回 store,用临时状态即可,实际后端会有 order update API
}
Taro.showToast({ title: '地址已更改', icon: 'success' })
}
if (!order) {
return (
<View className={`theme-${resolvedTheme}`}>
<View className='order-detail-page'>
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<Text className='page-title'></Text>
</View>
</View>
</View>
)
}
return (
<View className={`theme-${resolvedTheme}`}>
<View className='order-detail-page'>
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='star-badge' />
<View className='flex-between' style={{ width: '100%' }}>
<Text className='back-btn' onTap={() => Taro.navigateBack()}></Text>
<Text className='page-title'></Text>
<View style={{ width: '60px' }} />
</View>
</View>
{/* 状态横幅 */}
<View className='status-banner dashed-card mt-20'>
<View className='star-badge' />
<Text className='status-text'>
{order.statusCode === 'pending' && '待付款'}
{order.statusCode === 'paid' && '待发货'}
{order.statusCode === 'shipping' && '待收货'}
{order.statusCode === 'done' && '已完成'}
</Text>
<Text className='status-desc'>
{order.statusCode === 'pending' && '请在30分钟内完成支付'}
{order.statusCode === 'paid' && '商品正在打包中,即将发货'}
{order.statusCode === 'shipping' && '快递运输中,请注意查收'}
{order.statusCode === 'done' && '交易已完成,感谢惠顾'}
</Text>
</View>
{/* 物流信息(仅 shipping/done */}
{(order.statusCode === 'shipping' || order.statusCode === 'done') && (
<View className='logistics-card dashed-card mt-20'>
<View className='star-badge' />
<View className='logistics-header'>
<View className='flex-center'>
<Image className='card-icon-img' src='/icon/包裹.png' mode='aspectFit' />
<Text className='logistics-title'></Text>
</View>
<Text className='logistics-num'>单号: SF1234567890</Text>
</View>
<View className='timeline'>
<View className='timeline-item active'>
<View className='timeline-dot' />
<View className='timeline-content'>
<Text className='timeline-status'></Text>
<Text className='timeline-time'>2024-01-15 14:30</Text>
<Text className='timeline-desc'></Text>
</View>
</View>
<View className='timeline-item'>
<View className='timeline-dot' />
<View className='timeline-content'>
<Text className='timeline-status'></Text>
<Text className='timeline-time'>2024-01-15 09:00</Text>
<Text className='timeline-desc'></Text>
</View>
</View>
<View className='timeline-item'>
<View className='timeline-dot' />
<View className='timeline-content'>
<Text className='timeline-status'></Text>
<Text className='timeline-time'>2024-01-14 20:15</Text>
<Text className='timeline-desc'></Text>
</View>
</View>
</View>
</View>
)}
{/* 商品信息 */}
<View className='product-card dashed-card mt-20'>
<View className='star-badge' />
<View className='product-row'>
<View className='product-icon-wrapper'>
<Image
className='product-icon-img'
src={order.productIcon?.startsWith('/icon/') ? order.productIcon : '/icon/四角星.png'}
mode='aspectFit'
/>
</View>
<View className='product-info'>
<Text className='product-name'>{order.productName}</Text>
<Text className='product-sku'>{order.sku}</Text>
</View>
</View>
<View className='price-row'>
<Text className='price-label'></Text>
<Text className='price-value'>{order.price}</Text>
</View>
</View>
{/* 收货地址 */}
<View className='address-card dashed-card mt-20'>
<View className='star-badge' />
<View className='address-header'>
<View className='flex-center'>
<Image className='card-icon-img' src='/icon/地址.png' mode='aspectFit' />
<Text className='address-title'></Text>
</View>
<View className='address-change' onTap={() => setShowAddrPicker(true)}>
<Text className='change-text'></Text>
</View>
</View>
{address ? (
<View className='address-body'>
<View className='address-row'>
<Text className='address-name'>{address.name}</Text>
<Text className='address-phone'>{address.phone}</Text>
</View>
<Text className='address-detail'>
{address.region?.join(' ')} {address.detail}
</Text>
</View>
) : (
<View className='address-empty'>
<Text></Text>
</View>
)}
</View>
{/* 订单信息 */}
<View className='meta-card dashed-card mt-20'>
<View className='star-badge' />
<Text className='meta-title'></Text>
<View className='meta-row'>
<Text className='meta-label'></Text>
<Text className='meta-value'>{order.id}</Text>
</View>
<View className='meta-row'>
<Text className='meta-label'></Text>
<Text className='meta-value'>{order.date}</Text>
</View>
<View className='meta-row'>
<Text className='meta-label'></Text>
<Text className='meta-value'>{order.count} </Text>
</View>
</View>
<View style={{ height: '40px' }} />
{/* 地址选择底部弹窗 */}
{showAddrPicker && (
<View className='modal-overlay' style={{ alignItems: 'flex-end', justifyContent: 'flex-end' }}>
<View className='addr-picker-sheet dashed-card'>
<View className='addr-picker-header'>
<Text className='addr-picker-title'></Text>
<Text className='addr-picker-close' onTap={() => setShowAddrPicker(false)}></Text>
</View>
<View className='addr-picker-list'>
{addrList.map((addr) => (
<View
key={addr.id}
className={`addr-picker-item ${address?.id === addr.id ? 'active' : ''}`}
onTap={() => handlePickAddress(addr)}
>
<View className='addr-picker-row'>
<Text className='addr-picker-name'>{addr.name}</Text>
<Text className='addr-picker-phone'>{addr.phone}</Text>
{addr.isDefault && <Text className='addr-picker-default'></Text>}
</View>
<Text className='addr-picker-detail'>
{addr.region?.join(' ')} {addr.detail}
</Text>
</View>
))}
{addrList.length === 0 && (
<View className='addr-picker-empty'>
<Text></Text>
</View>
)}
</View>
<View className='addr-picker-add' onTap={() => { setShowAddrPicker(false); Taro.navigateTo({ url: '/pages/address/index' }) }}>
<Text className='addr-picker-add-text'>+ </Text>
</View>
</View>
</View>
)}
</View>
</View>
)
}