添加登录和后端校验
完成后端设计(未在本仓库体现),通过安全的手段完成了登录鉴权
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '确认下单'
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,240 @@
|
||||
.checkout-page {
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 40px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 画布展示区 */
|
||||
.checkout-canvas {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.canvas-area {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: rgba(0,0,0,0.03);
|
||||
border: 2px dashed var(--line-star);
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.checkout-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
/* 商品信息 */
|
||||
.checkout-info {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px dashed rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 28px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
padding-top: 24px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.info-total {
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
color: var(--accent-pink);
|
||||
}
|
||||
|
||||
/* 地址卡片 */
|
||||
.checkout-address-card {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.checkout-address-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.address-change {
|
||||
padding: 6px 16px;
|
||||
border-radius: 8px;
|
||||
background: rgba(91, 140, 255, 0.08);
|
||||
}
|
||||
|
||||
.change-text {
|
||||
font-size: 24px;
|
||||
color: var(--accent-blue);
|
||||
}
|
||||
|
||||
.checkout-address-body {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.addr-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.addr-name {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.addr-phone {
|
||||
font-size: 26px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.addr-detail {
|
||||
font-size: 26px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkout-address-empty {
|
||||
padding: 32px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 26px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 底部弹窗 — 地址选择(样式复用 app.scss 全局定义,移除非必要覆写) */
|
||||
|
||||
.addr-picker-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.addr-picker-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.addr-picker-close {
|
||||
font-size: 28px;
|
||||
color: var(--text-secondary);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.addr-picker-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.addr-picker-item {
|
||||
padding: 20px 16px;
|
||||
border-bottom: 1px dashed rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.addr-picker-item.active {
|
||||
background: rgba(91, 140, 255, 0.06);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.addr-picker-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.addr-picker-name {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.addr-picker-phone {
|
||||
font-size: 26px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.addr-picker-default {
|
||||
font-size: 22px;
|
||||
color: var(--accent-pink);
|
||||
border: 1px solid var(--accent-pink);
|
||||
padding: 2px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.addr-picker-detail {
|
||||
font-size: 26px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.addr-picker-empty {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.addr-picker-add {
|
||||
margin-top: 16px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border: 2px dashed var(--accent-blue);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.addr-picker-add-text {
|
||||
font-size: 28px;
|
||||
color: var(--accent-blue);
|
||||
}
|
||||
|
||||
/* 底部操作 */
|
||||
.checkout-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--bg-card);
|
||||
border-top: var(--line-card);
|
||||
padding: 20px 32px calc(20px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.checkout-actions .btn-outline,
|
||||
.checkout-actions .btn-gradient {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import { View, Text, Image, Button } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { useState, useEffect } from 'react'
|
||||
import './index.scss'
|
||||
import { getDesignList, designToOrder, getDefaultAddress, getAddressList, type AddressItem } from '../../utils/store'
|
||||
import { useThemeContext } from '../../context/ThemeContext'
|
||||
import { useSafeArea } from '../../hooks/useSafeArea'
|
||||
import { useStatusBar } from '../../hooks/useStatusBar'
|
||||
|
||||
export default function CheckoutPage() {
|
||||
const { theme, resolvedTheme } = useThemeContext()
|
||||
const safe = useSafeArea()
|
||||
useStatusBar(resolvedTheme)
|
||||
const [design, setDesign] = useState<any>(null)
|
||||
const [address, setAddress] = useState<AddressItem | null>(null)
|
||||
const [showAddrPicker, setShowAddrPicker] = useState(false)
|
||||
const [addrList, setAddrList] = useState<AddressItem[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const params = Taro.getCurrentInstance().router?.params
|
||||
const dId = params?.designId
|
||||
if (!dId) return
|
||||
const list = getDesignList()
|
||||
const d = list.find(x => x.id === dId)
|
||||
setDesign(d || null)
|
||||
setAddress(getDefaultAddress())
|
||||
setAddrList(getAddressList())
|
||||
}, [])
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!address) {
|
||||
Taro.showToast({ title: '请先添加收货地址', icon: 'none' })
|
||||
return
|
||||
}
|
||||
Taro.showModal({
|
||||
title: '确认下单',
|
||||
content: `确认后将从设计清单生成订单,并寄送至:\n${address.region?.join(' ')} ${address.detail}`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
designToOrder(design.id)
|
||||
Taro.showToast({ title: '下单成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
Taro.switchTab({ url: '/pages/index/index' })
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePickAddress = (addr: AddressItem) => {
|
||||
setAddress(addr)
|
||||
setShowAddrPicker(false)
|
||||
Taro.showToast({ title: '地址已更改', icon: 'success' })
|
||||
}
|
||||
|
||||
const hasStickers = design?.designData?.stickers && design.designData.stickers.length > 0
|
||||
const hasLegacyImage = design?.designData?.imageSrc
|
||||
|
||||
if (!design) {
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<View className='checkout-page'>
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<Text className='page-title'>设计不存在</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const maskStyle: any = { width: 300, height: 420 }
|
||||
if (design.designData?.category?.mask) {
|
||||
const m = design.designData.category.mask
|
||||
maskStyle.width = m.width
|
||||
maskStyle.height = m.height
|
||||
if (m.shape === 'circle') maskStyle.borderRadius = '50%'
|
||||
else maskStyle.borderRadius = m.borderRadius || 0
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
<View className='checkout-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='checkout-canvas dashed-card mt-20'>
|
||||
<View className='star-badge' />
|
||||
<View className='canvas-area' style={maskStyle}>
|
||||
{hasStickers ? (
|
||||
design.designData.stickers.map((s: any) => (
|
||||
<Image
|
||||
key={s.id}
|
||||
className='checkout-image'
|
||||
src={s.src}
|
||||
style={{
|
||||
transform: `translate(${s.x}px, ${s.y}px) scale(${s.scale || 1})`,
|
||||
width: s.width || 200,
|
||||
height: s.height || 200
|
||||
}}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
))
|
||||
) : hasLegacyImage ? (
|
||||
<Image
|
||||
className='checkout-image'
|
||||
src={design.designData.imageSrc}
|
||||
style={{
|
||||
transform: `translate(${design.designData.imagePos?.x || 0}px, ${design.designData.imagePos?.y || 0}px) scale(${design.designData.imagePos?.scale || 1})`
|
||||
}}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 商品信息 */}
|
||||
<View className='checkout-info dashed-card mt-20'>
|
||||
<View className='star-badge' />
|
||||
<View className='info-row'>
|
||||
<Text className='info-label'>商品</Text>
|
||||
<Text className='info-value'>{design.productName}</Text>
|
||||
</View>
|
||||
<View className='info-row'>
|
||||
<Text className='info-label'>数量</Text>
|
||||
<Text className='info-value'>{design.count} 件</Text>
|
||||
</View>
|
||||
<View className='info-row'>
|
||||
<Text className='info-label'>单价</Text>
|
||||
<Text className='info-value'>¥{design.unitPrice}</Text>
|
||||
</View>
|
||||
<View className='info-row total-row'>
|
||||
<Text className='info-label'>总价</Text>
|
||||
<Text className='info-total'>¥{(design.unitPrice * design.count).toFixed(2)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 收货地址 */}
|
||||
<View className='checkout-address-card dashed-card mt-20'>
|
||||
<View className='star-badge' />
|
||||
<View className='checkout-address-header'>
|
||||
<Text className='info-label'>收货地址</Text>
|
||||
<View className='address-change' onTap={() => setShowAddrPicker(true)}>
|
||||
<Text className='change-text'>更改收货地址</Text>
|
||||
</View>
|
||||
</View>
|
||||
{address ? (
|
||||
<View className='checkout-address-body'>
|
||||
<View className='addr-row'>
|
||||
<Text className='addr-name'>{address.name}</Text>
|
||||
<Text className='addr-phone'>{address.phone}</Text>
|
||||
</View>
|
||||
<Text className='addr-detail'>
|
||||
{address.region?.join(' ')} {address.detail}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='checkout-address-empty' onTap={() => setShowAddrPicker(true)}>
|
||||
<Text className='empty-text'>暂无收货地址,点击添加</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 底部操作 */}
|
||||
<View className='checkout-actions'>
|
||||
<View className='btn-outline' onTap={() => Taro.navigateBack()}>
|
||||
<Text>返回修改</Text>
|
||||
</View>
|
||||
<View className='btn-gradient' onTap={handleConfirm}>
|
||||
<Text>确认下单</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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user