feat: refresh product and customization flows
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: rgba(0,0,0,0.03);
|
background: rgba(0,0,0,0.03);
|
||||||
border: 2px dashed var(--line-star);
|
border: 1rpx solid var(--border-strong);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
border-bottom: 1px dashed rgba(0,0,0,0.06);
|
border-bottom: 1rpx solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-row:last-child {
|
.info-row:last-child {
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
.info-total {
|
.info-total {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 地址卡片 */
|
/* 地址卡片 */
|
||||||
@@ -86,12 +86,12 @@
|
|||||||
.address-change {
|
.address-change {
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: rgba(91, 140, 255, 0.08);
|
background: var(--accent-secondary-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.change-text {
|
.change-text {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: var(--accent-blue);
|
color: var(--accent-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-address-body {
|
.checkout-address-body {
|
||||||
@@ -160,11 +160,11 @@
|
|||||||
|
|
||||||
.addr-picker-item {
|
.addr-picker-item {
|
||||||
padding: 20px 16px;
|
padding: 20px 16px;
|
||||||
border-bottom: 1px dashed rgba(0, 0, 0, 0.05);
|
border-bottom: 1rpx solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.addr-picker-item.active {
|
.addr-picker-item.active {
|
||||||
background: rgba(91, 140, 255, 0.06);
|
background: var(--accent-secondary-soft);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +188,8 @@
|
|||||||
|
|
||||||
.addr-picker-default {
|
.addr-picker-default {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
border: 1px solid var(--accent-pink);
|
border: 1px solid var(--accent-primary);
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
@@ -210,31 +210,18 @@
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 2px dashed var(--accent-blue);
|
border: 1rpx solid var(--border-strong);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addr-picker-add-text {
|
.addr-picker-add-text {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--accent-blue);
|
color: var(--accent-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部操作 */
|
/* 底部操作 — 毛玻璃规则由全局 .action-bar 提供 */
|
||||||
.checkout-actions {
|
.action-bar .btn-secondary,
|
||||||
position: fixed;
|
.action-bar .btn-primary {
|
||||||
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;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ export default function CheckoutPage() {
|
|||||||
const [addrList, setAddrList] = useState<AddressItem[]>([])
|
const [addrList, setAddrList] = useState<AddressItem[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = Taro.getCurrentInstance().router?.params
|
const router = Taro.getCurrentInstance().router
|
||||||
const dId = params?.designId
|
const params = router ? router.params : undefined
|
||||||
|
const dId = params ? params.designId : undefined
|
||||||
if (!dId) return
|
if (!dId) return
|
||||||
const list = getDesignList()
|
const list = getDesignList()
|
||||||
const d = list.find(x => x.id === dId)
|
const d = list.find(x => x.id === dId)
|
||||||
@@ -35,7 +36,7 @@ export default function CheckoutPage() {
|
|||||||
}
|
}
|
||||||
Taro.showModal({
|
Taro.showModal({
|
||||||
title: '确认下单',
|
title: '确认下单',
|
||||||
content: `确认后将从设计清单生成订单,并寄送至:\n${address.region?.join(' ')} ${address.detail}`,
|
content: `确认后将从设计清单生成订单,并寄送至:\n${address.region ? address.region.join(' ') : ''} ${address.detail}`,
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
designToOrder(design.id)
|
designToOrder(design.id)
|
||||||
@@ -54,15 +55,15 @@ export default function CheckoutPage() {
|
|||||||
Taro.showToast({ title: '地址已更改', icon: 'success' })
|
Taro.showToast({ title: '地址已更改', icon: 'success' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasStickers = design?.designData?.stickers && design.designData.stickers.length > 0
|
const hasStickers = design && design.designData && design.designData.stickers && design.designData.stickers.length > 0
|
||||||
const hasLegacyImage = design?.designData?.imageSrc
|
const hasLegacyImage = design && design.designData ? design.designData.imageSrc : undefined
|
||||||
|
|
||||||
if (!design) {
|
if (!design) {
|
||||||
return (
|
return (
|
||||||
<View className={`theme-${resolvedTheme}`}>
|
<View className={`theme-${resolvedTheme}`}>
|
||||||
<ThemedPageMeta />
|
<ThemedPageMeta />
|
||||||
<View className='checkout-page'>
|
<View className='checkout-page'>
|
||||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
<View className='page-header surface-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||||
<Text className='page-title'>设计不存在</Text>
|
<Text className='page-title'>设计不存在</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -71,7 +72,7 @@ export default function CheckoutPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const maskStyle: any = { width: 300, height: 420 }
|
const maskStyle: any = { width: 300, height: 420 }
|
||||||
if (design.designData?.category?.mask) {
|
if (design.designData && design.designData.category && design.designData.category.mask) {
|
||||||
const m = design.designData.category.mask
|
const m = design.designData.category.mask
|
||||||
maskStyle.width = m.width
|
maskStyle.width = m.width
|
||||||
maskStyle.height = m.height
|
maskStyle.height = m.height
|
||||||
@@ -84,8 +85,7 @@ export default function CheckoutPage() {
|
|||||||
<ThemedPageMeta />
|
<ThemedPageMeta />
|
||||||
<View className='checkout-page'>
|
<View className='checkout-page'>
|
||||||
|
|
||||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
<View className='page-header surface-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='flex-between' style={{ width: '100%' }}>
|
<View className='flex-between' style={{ width: '100%' }}>
|
||||||
<Text className='back-btn' onTap={() => Taro.navigateBack()}>←</Text>
|
<Text className='back-btn' onTap={() => Taro.navigateBack()}>←</Text>
|
||||||
<Text className='page-title'>设计效果确认</Text>
|
<Text className='page-title'>设计效果确认</Text>
|
||||||
@@ -94,8 +94,7 @@ export default function CheckoutPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 效果展示区 */}
|
{/* 效果展示区 */}
|
||||||
<View className='checkout-canvas dashed-card mt-20'>
|
<View className='checkout-canvas surface-card mt-20'>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='canvas-area' style={maskStyle}>
|
<View className='canvas-area' style={maskStyle}>
|
||||||
{hasStickers ? (
|
{hasStickers ? (
|
||||||
design.designData.stickers.map((s: any) => (
|
design.designData.stickers.map((s: any) => (
|
||||||
@@ -116,7 +115,7 @@ export default function CheckoutPage() {
|
|||||||
className='checkout-image'
|
className='checkout-image'
|
||||||
src={design.designData.imageSrc}
|
src={design.designData.imageSrc}
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(${design.designData.imagePos?.x || 0}px, ${design.designData.imagePos?.y || 0}px) scale(${design.designData.imagePos?.scale || 1})`
|
transform: `translate(${design.designData.imagePos ? design.designData.imagePos.x || 0 : 0}px, ${design.designData.imagePos ? design.designData.imagePos.y || 0 : 0}px) scale(${design.designData.imagePos ? design.designData.imagePos.scale || 1 : 1})`
|
||||||
}}
|
}}
|
||||||
mode='aspectFit'
|
mode='aspectFit'
|
||||||
/>
|
/>
|
||||||
@@ -125,8 +124,7 @@ export default function CheckoutPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 商品信息 */}
|
{/* 商品信息 */}
|
||||||
<View className='checkout-info dashed-card mt-20'>
|
<View className='checkout-info surface-card mt-20'>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='info-row'>
|
<View className='info-row'>
|
||||||
<Text className='info-label'>商品</Text>
|
<Text className='info-label'>商品</Text>
|
||||||
<Text className='info-value'>{design.productName}</Text>
|
<Text className='info-value'>{design.productName}</Text>
|
||||||
@@ -146,8 +144,7 @@ export default function CheckoutPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 收货地址 */}
|
{/* 收货地址 */}
|
||||||
<View className='checkout-address-card dashed-card mt-20'>
|
<View className='checkout-address-card surface-card mt-20'>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='checkout-address-header'>
|
<View className='checkout-address-header'>
|
||||||
<Text className='info-label'>收货地址</Text>
|
<Text className='info-label'>收货地址</Text>
|
||||||
<View className='address-change' onTap={() => setShowAddrPicker(true)}>
|
<View className='address-change' onTap={() => setShowAddrPicker(true)}>
|
||||||
@@ -161,7 +158,7 @@ export default function CheckoutPage() {
|
|||||||
<Text className='addr-phone'>{address.phone}</Text>
|
<Text className='addr-phone'>{address.phone}</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className='addr-detail'>
|
<Text className='addr-detail'>
|
||||||
{address.region?.join(' ')} {address.detail}
|
{address.region ? address.region.join(' ') : ''} {address.detail}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
@@ -172,21 +169,21 @@ export default function CheckoutPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 底部操作 */}
|
{/* 底部操作 */}
|
||||||
<View className='checkout-actions'>
|
<View className='action-bar'>
|
||||||
<View className='btn-outline' onTap={() => Taro.navigateBack()}>
|
<View className='btn-secondary' onTap={() => Taro.navigateBack()}>
|
||||||
<Text>返回修改</Text>
|
<Text>返回修改</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='btn-gradient' onTap={handleConfirm}>
|
<View className='btn-primary' onTap={handleConfirm}>
|
||||||
<Text>确认下单</Text>
|
<Text>确认下单</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={{ height: '40px' }} />
|
<View className='safe-bottom-placeholder' />
|
||||||
|
|
||||||
{/* 地址选择底部弹窗 */}
|
{/* 地址选择底部弹窗 */}
|
||||||
{showAddrPicker && (
|
{showAddrPicker && (
|
||||||
<View className='modal-overlay' style={{ alignItems: 'flex-end', justifyContent: 'flex-end' }}>
|
<View className='modal-overlay' style={{ alignItems: 'flex-end', justifyContent: 'flex-end' }}>
|
||||||
<View className='addr-picker-sheet dashed-card'>
|
<View className='addr-picker-sheet surface-card'>
|
||||||
<View className='addr-picker-header'>
|
<View className='addr-picker-header'>
|
||||||
<Text className='addr-picker-title'>选择收货地址</Text>
|
<Text className='addr-picker-title'>选择收货地址</Text>
|
||||||
<Text className='addr-picker-close' onTap={() => setShowAddrPicker(false)}>✕</Text>
|
<Text className='addr-picker-close' onTap={() => setShowAddrPicker(false)}>✕</Text>
|
||||||
@@ -195,7 +192,7 @@ export default function CheckoutPage() {
|
|||||||
{addrList.map((addr) => (
|
{addrList.map((addr) => (
|
||||||
<View
|
<View
|
||||||
key={addr.id}
|
key={addr.id}
|
||||||
className={`addr-picker-item ${address?.id === addr.id ? 'active' : ''}`}
|
className={`addr-picker-item ${address && address.id === addr.id ? 'active' : ''}`}
|
||||||
onTap={() => handlePickAddress(addr)}
|
onTap={() => handlePickAddress(addr)}
|
||||||
>
|
>
|
||||||
<View className='addr-picker-row'>
|
<View className='addr-picker-row'>
|
||||||
@@ -204,7 +201,7 @@ export default function CheckoutPage() {
|
|||||||
{addr.isDefault && <Text className='addr-picker-default'>默认</Text>}
|
{addr.isDefault && <Text className='addr-picker-default'>默认</Text>}
|
||||||
</View>
|
</View>
|
||||||
<Text className='addr-picker-detail'>
|
<Text className='addr-picker-detail'>
|
||||||
{addr.region?.join(' ')} {addr.detail}
|
{addr.region ? addr.region.join(' ') : ''} {addr.detail}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+33
-31
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
.canvas-area {
|
.canvas-area {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: linear-gradient(135deg, rgba(255, 154, 158, 0.08) 0%, rgba(160, 196, 255, 0.08) 100%);
|
background: var(--bg-input);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
border: 3px dashed var(--accent-blue);
|
border: 1rpx solid var(--border-strong);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
@@ -41,12 +41,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.canvas-image.active {
|
.canvas-image.active {
|
||||||
border-color: var(--accent-pink);
|
border-color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas-image.overlap {
|
.canvas-image.overlap {
|
||||||
border-color: #ff4757;
|
border-color: var(--danger);
|
||||||
box-shadow: 0 0 12px rgba(255, 71, 87, 0.5);
|
box-shadow: 0 0 12px var(--danger-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 贴纸面板 */
|
/* 贴纸面板 */
|
||||||
@@ -64,19 +64,19 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
border: 2px dashed var(--line-star);
|
border: 1rpx solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticker-thumb.active {
|
.sticker-thumb.active {
|
||||||
border-color: var(--accent-pink);
|
border-color: var(--accent-primary);
|
||||||
border-width: 3px;
|
border-width: 2rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticker-thumb.overlap {
|
.sticker-thumb.overlap {
|
||||||
border-color: #ff4757;
|
border-color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sticker-thumb-img {
|
.sticker-thumb-img {
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
right: 4px;
|
right: 4px;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
background: rgba(255, 71, 87, 0.85);
|
background: var(--danger);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
.sticker-add-btn {
|
.sticker-add-btn {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
border: 2px dashed var(--line-star);
|
border: 1rpx solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
|
|
||||||
.add-icon {
|
.add-icon {
|
||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
color: #ff4757;
|
color: var(--danger);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -162,8 +162,8 @@
|
|||||||
.tool-btn {
|
.tool-btn {
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
background: linear-gradient(135deg, rgba(255, 154, 158, 0.08) 0%, rgba(160, 196, 255, 0.08) 100%);
|
background: var(--bg-input);
|
||||||
border: 2px dashed var(--accent-blue);
|
border: 1rpx solid var(--border-strong);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
.tool-value {
|
.tool-value {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -219,11 +219,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.preview-canvas {
|
.preview-canvas {
|
||||||
background: linear-gradient(135deg, rgba(255, 154, 158, 0.08) 0%, rgba(160, 196, 255, 0.08) 100%);
|
background: var(--bg-input);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: 0 auto 24px;
|
margin: 0 auto 24px;
|
||||||
border: 3px dashed var(--accent-blue);
|
border: 1rpx solid var(--border-strong);
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.preview-image.overlap {
|
.preview-image.overlap {
|
||||||
border: 2px solid #ff4757;
|
border: 2px solid var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info {
|
.product-info {
|
||||||
@@ -265,13 +265,13 @@
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-actions .btn-outline,
|
.preview-actions .btn-secondary,
|
||||||
.preview-actions .btn-gradient {
|
.preview-actions .btn-primary {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-actions .btn-gradient.disabled {
|
.preview-actions .btn-primary.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -290,14 +290,14 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
border: 2px dashed var(--text-muted);
|
border: 1rpx solid var(--border-strong);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-btn.active {
|
.edit-btn.active {
|
||||||
background: linear-gradient(135deg, rgba(255, 154, 158, 0.12) 0%, rgba(160, 196, 255, 0.12) 100%);
|
background: var(--accent-primary-soft);
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
border-color: var(--accent-pink);
|
border-color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-btn.disabled {
|
.edit-btn.disabled {
|
||||||
@@ -305,11 +305,13 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 按钮 */
|
/* 底部操作栏 — 毛玻璃规则由全局 .action-bar 提供 */
|
||||||
.action-btns {
|
.action-bar .btn-primary,
|
||||||
display: flex;
|
.action-bar .btn-secondary {
|
||||||
flex-direction: column;
|
flex: 1;
|
||||||
gap: 20px;
|
text-align: center;
|
||||||
|
padding: 24px 0;
|
||||||
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-20 {
|
.mt-20 {
|
||||||
|
|||||||
+27
-30
@@ -25,22 +25,23 @@ export default function DIYPage() {
|
|||||||
const [hasOverlap, setHasOverlap] = useState(false)
|
const [hasOverlap, setHasOverlap] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = Taro.getCurrentInstance().router?.params
|
const router = Taro.getCurrentInstance().router
|
||||||
const source = params?.source
|
const params = router ? router.params : undefined
|
||||||
const productId = params?.productId || params?.category
|
const source = params ? params.source : undefined
|
||||||
|
const productId = (params && params.productId) || (params && params.category)
|
||||||
|
|
||||||
if (source === 'product' && productId) {
|
if (source === 'product' && productId) {
|
||||||
const product = getProductById(productId)
|
const product = getProductById(productId)
|
||||||
if (product) {
|
if (product) {
|
||||||
setCategory(product)
|
setCategory(product)
|
||||||
setQuantity(Number(params?.quantity) || 1)
|
setQuantity(Number(params ? params.quantity : undefined) || 1)
|
||||||
const newDesign: DesignItem = {
|
const newDesign: DesignItem = {
|
||||||
id: 'DSG' + Date.now(),
|
id: 'DSG' + Date.now(),
|
||||||
productId: product.id,
|
productId: product.id,
|
||||||
productName: product.name,
|
productName: product.name,
|
||||||
productIcon: product.icon,
|
productIcon: product.icon,
|
||||||
unitPrice: product.price,
|
unitPrice: product.price,
|
||||||
count: Number(params?.quantity) || 1,
|
count: Number(params ? params.quantity : undefined) || 1,
|
||||||
status: 'designing',
|
status: 'designing',
|
||||||
createdAt: new Date().toISOString().slice(0, 10)
|
createdAt: new Date().toISOString().slice(0, 10)
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ export default function DIYPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source === 'designList' && params?.designId) {
|
if (source === 'designList' && params && params.designId) {
|
||||||
const dId = params.designId
|
const dId = params.designId
|
||||||
const list = getDesignList()
|
const list = getDesignList()
|
||||||
const design = list.find(d => d.id === dId)
|
const design = list.find(d => d.id === dId)
|
||||||
@@ -60,16 +61,16 @@ export default function DIYPage() {
|
|||||||
setQuantity(design.count)
|
setQuantity(design.count)
|
||||||
const product = getProductById(design.productId)
|
const product = getProductById(design.productId)
|
||||||
if (product) setCategory(product)
|
if (product) setCategory(product)
|
||||||
if (design.designData?.stickers) {
|
if (design.designData ? design.designData.stickers : undefined) {
|
||||||
setStickers(design.designData.stickers)
|
setStickers(design.designData.stickers)
|
||||||
} else if (design.designData?.imageSrc) {
|
} else if (design.designData ? design.designData.imageSrc : undefined) {
|
||||||
// 兼容旧版数据
|
// 兼容旧版数据
|
||||||
const s: StickerItem = {
|
const s: StickerItem = {
|
||||||
id: 'legacy_' + Date.now(),
|
id: 'legacy_' + Date.now(),
|
||||||
src: design.designData.imageSrc,
|
src: design.designData.imageSrc,
|
||||||
x: design.designData.imagePos?.x || 0,
|
x: design.designData.imagePos ? design.designData.imagePos.x || 0 : 0,
|
||||||
y: design.designData.imagePos?.y || 0,
|
y: design.designData.imagePos ? design.designData.imagePos.y || 0 : 0,
|
||||||
scale: design.designData.imagePos?.scale || 1,
|
scale: design.designData.imagePos ? design.designData.imagePos.scale || 1 : 1,
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 200,
|
height: 200,
|
||||||
isOverlapping: false
|
isOverlapping: false
|
||||||
@@ -255,6 +256,9 @@ export default function DIYPage() {
|
|||||||
Taro.navigateTo({ url: `/pages/checkout/index?designId=${designId}` })
|
Taro.navigateTo({ url: `/pages/checkout/index?designId=${designId}` })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeSticker = activeStickerId ? stickers.find(s => s.id === activeStickerId) : undefined
|
||||||
|
const activeScale = activeSticker ? activeSticker.scale : 1
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
return (
|
return (
|
||||||
<View className={`theme-${resolvedTheme}`}>
|
<View className={`theme-${resolvedTheme}`}>
|
||||||
@@ -271,8 +275,7 @@ export default function DIYPage() {
|
|||||||
<ThemedPageMeta />
|
<ThemedPageMeta />
|
||||||
<View className='diy-page'>
|
<View className='diy-page'>
|
||||||
|
|
||||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
<View className='page-header surface-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='flex-between' style={{ width: '100%' }}>
|
<View className='flex-between' style={{ width: '100%' }}>
|
||||||
<Text className='back-btn' onTap={goBack}>←</Text>
|
<Text className='back-btn' onTap={goBack}>←</Text>
|
||||||
<Text className='page-title'>设计工作台</Text>
|
<Text className='page-title'>设计工作台</Text>
|
||||||
@@ -281,8 +284,7 @@ export default function DIYPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 画布区域 — catchtouchmove 阻止事件冒泡导致页面滚动 */}
|
{/* 画布区域 — catchtouchmove 阻止事件冒泡导致页面滚动 */}
|
||||||
<View className='canvas-wrapper dashed-card mt-20' catchMove>
|
<View className='canvas-wrapper surface-card mt-20' catchMove>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='canvas-area' style={getMaskStyle()}>
|
<View className='canvas-area' style={getMaskStyle()}>
|
||||||
{stickers.map(s => (
|
{stickers.map(s => (
|
||||||
<Image
|
<Image
|
||||||
@@ -308,8 +310,7 @@ export default function DIYPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 贴纸列表与控制 */}
|
{/* 贴纸列表与控制 */}
|
||||||
<View className='sticker-panel dashed-card mt-20'>
|
<View className='sticker-panel surface-card mt-20'>
|
||||||
<View className='star-badge' />
|
|
||||||
<Text className='section-title'>贴纸</Text>
|
<Text className='section-title'>贴纸</Text>
|
||||||
<View className='sticker-list'>
|
<View className='sticker-list'>
|
||||||
{stickers.map(s => (
|
{stickers.map(s => (
|
||||||
@@ -330,15 +331,12 @@ export default function DIYPage() {
|
|||||||
|
|
||||||
{/* 控制工具栏 */}
|
{/* 控制工具栏 */}
|
||||||
{activeStickerId && (
|
{activeStickerId && (
|
||||||
<View className='toolbar dashed-card mt-20'>
|
<View className='toolbar surface-card mt-20'>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='tool-row'>
|
<View className='tool-row'>
|
||||||
<Text className='tool-label'>缩放</Text>
|
<Text className='tool-label'>缩放</Text>
|
||||||
<View className='flex-center' style={{ gap: '20px' }}>
|
<View className='flex-center' style={{ gap: '20px' }}>
|
||||||
<View className='tool-btn' onTap={() => handleScale(activeStickerId, -0.1)}>-</View>
|
<View className='tool-btn' onTap={() => handleScale(activeStickerId, -0.1)}>-</View>
|
||||||
<Text className='tool-value'>
|
<Text className='tool-value'>{Math.round(activeScale * 100)}%</Text>
|
||||||
{Math.round((stickers.find(s => s.id === activeStickerId)?.scale || 1) * 100)}%
|
|
||||||
</Text>
|
|
||||||
<View className='tool-btn' onTap={() => handleScale(activeStickerId, 0.1)}>+</View>
|
<View className='tool-btn' onTap={() => handleScale(activeStickerId, 0.1)}>+</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -360,11 +358,11 @@ export default function DIYPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 底部操作按钮 */}
|
{/* 底部操作按钮 */}
|
||||||
<View className='action-btns mt-20'>
|
<View className='action-bar'>
|
||||||
<View className='btn-gradient' onTap={() => setPreviewMode(true)}>
|
<View className='btn-primary' onTap={() => setPreviewMode(true)}>
|
||||||
<Text>预览效果</Text>
|
<Text>预览效果</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='btn-outline' onTap={addSticker}>
|
<View className='btn-secondary' onTap={addSticker}>
|
||||||
<Text>添加贴纸</Text>
|
<Text>添加贴纸</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -372,8 +370,7 @@ export default function DIYPage() {
|
|||||||
{/* 预览弹层 */}
|
{/* 预览弹层 */}
|
||||||
{previewMode && (
|
{previewMode && (
|
||||||
<View className='preview-overlay'>
|
<View className='preview-overlay'>
|
||||||
<View className='preview-card dashed-card'>
|
<View className='preview-card surface-card'>
|
||||||
<View className='star-badge' />
|
|
||||||
<Text className='preview-title'>确认效果</Text>
|
<Text className='preview-title'>确认效果</Text>
|
||||||
<View className='preview-canvas' style={getMaskStyle()}>
|
<View className='preview-canvas' style={getMaskStyle()}>
|
||||||
{stickers.map(s => (
|
{stickers.map(s => (
|
||||||
@@ -396,10 +393,10 @@ export default function DIYPage() {
|
|||||||
{hasOverlap && <Text className='overlap-hint'>贴纸有重叠,不可提交</Text>}
|
{hasOverlap && <Text className='overlap-hint'>贴纸有重叠,不可提交</Text>}
|
||||||
</View>
|
</View>
|
||||||
<View className='preview-actions'>
|
<View className='preview-actions'>
|
||||||
<View className='btn-outline' onTap={() => setPreviewMode(false)}>
|
<View className='btn-secondary' onTap={() => setPreviewMode(false)}>
|
||||||
<Text>返回修改</Text>
|
<Text>返回修改</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={`btn-gradient ${hasOverlap ? 'disabled' : ''}`} onTap={handleComplete}>
|
<View className={`btn-primary ${hasOverlap ? 'disabled' : ''}`} onTap={handleComplete}>
|
||||||
<Text>确认完成</Text>
|
<Text>确认完成</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -407,7 +404,7 @@ export default function DIYPage() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View style={{ height: '40px' }} />
|
<View className='safe-bottom-placeholder' />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
.edit-save {
|
.edit-save {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
.slider-fill {
|
.slider-fill {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: linear-gradient(90deg, #ff9a9e, #fecfef);
|
background: linear-gradient(90deg, var(--accent-primary), var(--accent-primary-soft));
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 4px solid var(--accent-pink);
|
border: 4px solid var(--accent-primary);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background: var(--bg-input);
|
background: var(--bg-input);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
border: 2px dashed var(--line-star);
|
border: 1rpx solid var(--border);
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,20 +142,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-btn.active {
|
.quick-btn.active {
|
||||||
background: rgba(255, 154, 158, 0.12);
|
background: var(--accent-primary-soft);
|
||||||
border-color: var(--accent-pink);
|
border-color: var(--accent-primary);
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部操作 */
|
/* 底部操作栏 — 毛玻璃规则由全局 .action-bar 提供 */
|
||||||
.edit-footer {
|
.edit-footer .btn-primary,
|
||||||
padding: 20px 0 calc(20px + env(safe-area-inset-bottom));
|
.edit-footer .btn-secondary {
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-footer .btn-gradient,
|
|
||||||
.edit-footer .btn-outline {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 24px 0;
|
padding: 24px 0;
|
||||||
|
|||||||
@@ -45,13 +45,15 @@ function TouchSlider({ value, min, max, step = 1, onChange, format }: SliderProp
|
|||||||
|
|
||||||
const handleTouchStart = (e: any) => {
|
const handleTouchStart = (e: any) => {
|
||||||
setDragging(true)
|
setDragging(true)
|
||||||
const x = e.touches?.[0]?.clientX ?? 0
|
const touch = e.touches && e.touches[0]
|
||||||
|
const x = touch ? touch.clientX : 0
|
||||||
onChange(computedFromClientX(x))
|
onChange(computedFromClientX(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTouchMove = (e: any) => {
|
const handleTouchMove = (e: any) => {
|
||||||
if (!dragging) return
|
if (!dragging) return
|
||||||
const x = e.touches?.[0]?.clientX ?? 0
|
const touch = e.touches && e.touches[0]
|
||||||
|
const x = touch ? touch.clientX : 0
|
||||||
onChange(computedFromClientX(x))
|
onChange(computedFromClientX(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,14 +108,15 @@ export default function StickerEditPage() {
|
|||||||
query.select('#editCanvas')
|
query.select('#editCanvas')
|
||||||
.fields({ node: true, size: true })
|
.fields({ node: true, size: true })
|
||||||
.exec((res: any) => {
|
.exec((res: any) => {
|
||||||
const canvas = res?.[0]?.node
|
const resFirst = res && res[0]
|
||||||
|
const canvas = resFirst ? resFirst.node : undefined
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
setTimeout(() => initCanvas(st), 300)
|
setTimeout(() => initCanvas(st), 300)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
let cw = res[0]?.width || 300
|
let cw = (res[0] && res[0].width) || 300
|
||||||
let ch = res[0]?.height || 400
|
let ch = (res[0] && res[0].height) || 400
|
||||||
if (cw <= 0) cw = 300
|
if (cw <= 0) cw = 300
|
||||||
if (ch <= 0) ch = 400
|
if (ch <= 0) ch = 400
|
||||||
const dpr = Taro.getSystemInfoSync().pixelRatio || 1
|
const dpr = Taro.getSystemInfoSync().pixelRatio || 1
|
||||||
@@ -130,20 +133,21 @@ export default function StickerEditPage() {
|
|||||||
}, [canvasReady])
|
}, [canvasReady])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = Taro.getCurrentInstance().router?.params
|
const router = Taro.getCurrentInstance().router
|
||||||
const dId = params?.designId as string
|
const params = router ? router.params : undefined
|
||||||
const sId = params?.stickerId as string
|
const dId = params ? params.designId : ''
|
||||||
|
const sId = params ? params.stickerId : ''
|
||||||
setDesignId(dId)
|
setDesignId(dId)
|
||||||
setStickerId(sId)
|
setStickerId(sId)
|
||||||
|
|
||||||
const list = getDesignList()
|
const list = getDesignList()
|
||||||
const design = list.find(d => d.id === dId)
|
const design = list.find(d => d.id === dId)
|
||||||
const st = design?.designData?.stickers?.find(s => s.id === sId)
|
const st = design && design.designData && design.designData.stickers ? design.designData.stickers.find(s => s.id === sId) : undefined
|
||||||
if (st) {
|
if (st) {
|
||||||
setSticker(st)
|
setSticker(st)
|
||||||
setBrightness(st.edits?.brightness || 0)
|
setBrightness((st.edits && st.edits.brightness) || 0)
|
||||||
setHue(st.edits?.hue || 0)
|
setHue((st.edits && st.edits.hue) || 0)
|
||||||
setContrast(st.edits?.contrast || 0)
|
setContrast((st.edits && st.edits.contrast) || 0)
|
||||||
initCanvas(st)
|
initCanvas(st)
|
||||||
}
|
}
|
||||||
}, [initCanvas])
|
}, [initCanvas])
|
||||||
@@ -171,9 +175,9 @@ export default function StickerEditPage() {
|
|||||||
const y = (ch - h) / 2
|
const y = (ch - h) / 2
|
||||||
|
|
||||||
// 应用滤镜(小程序 Canvas 2D 支持 filter 属性 2.16.0+)
|
// 应用滤镜(小程序 Canvas 2D 支持 filter 属性 2.16.0+)
|
||||||
const b = opts?.brightness ?? brightness
|
const b = opts ? opts.brightness : brightness
|
||||||
const hVal = opts?.hue ?? hue
|
const hVal = opts ? opts.hue : hue
|
||||||
const c = opts?.contrast ?? contrast
|
const c = opts ? opts.contrast : contrast
|
||||||
if (b !== 0 || hVal !== 0 || c !== 0) {
|
if (b !== 0 || hVal !== 0 || c !== 0) {
|
||||||
ctx.filter = `brightness(${100 + b}%) hue-rotate(${hVal}deg) contrast(${100 + c}%)`
|
ctx.filter = `brightness(${100 + b}%) hue-rotate(${hVal}deg) contrast(${100 + c}%)`
|
||||||
} else {
|
} else {
|
||||||
@@ -217,7 +221,7 @@ export default function StickerEditPage() {
|
|||||||
const dList = getDesignList()
|
const dList = getDesignList()
|
||||||
const dIdx = dList.findIndex(d => d.id === designId)
|
const dIdx = dList.findIndex(d => d.id === designId)
|
||||||
if (dIdx === -1) { setLoading(false); return }
|
if (dIdx === -1) { setLoading(false); return }
|
||||||
const stickers = dList[dIdx].designData?.stickers || []
|
const stickers = dList[dIdx].designData ? dList[dIdx].designData.stickers || [] : []
|
||||||
const sIdx = stickers.findIndex(s => s.id === stickerId)
|
const sIdx = stickers.findIndex(s => s.id === stickerId)
|
||||||
if (sIdx === -1) { setLoading(false); return }
|
if (sIdx === -1) { setLoading(false); return }
|
||||||
stickers[sIdx] = {
|
stickers[sIdx] = {
|
||||||
@@ -325,7 +329,7 @@ export default function StickerEditPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 快速操作 */}
|
{/* 快速操作 */}
|
||||||
<View className='edit-tools dashed-card mt-20'>
|
<View className='edit-tools surface-card mt-20'>
|
||||||
<View className='edit-row'>
|
<View className='edit-row'>
|
||||||
<Text className='edit-label'>快速操作</Text>
|
<Text className='edit-label'>快速操作</Text>
|
||||||
<View className='edit-quick-actions'>
|
<View className='edit-quick-actions'>
|
||||||
@@ -411,6 +415,17 @@ export default function StickerEditPage() {
|
|||||||
|
|
||||||
<View style={{ flex: 1 }} />
|
<View style={{ flex: 1 }} />
|
||||||
|
|
||||||
|
<View className='action-bar edit-footer'>
|
||||||
|
<View className='btn-secondary' onTap={() => Taro.navigateBack()}>
|
||||||
|
<Text>取消</Text>
|
||||||
|
</View>
|
||||||
|
<View className='btn-primary' onTap={handleSave}>
|
||||||
|
<Text>保存</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='safe-bottom-placeholder' />
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<View className='edit-loading'>
|
<View className='edit-loading'>
|
||||||
<Text className='edit-loading-text'>处理中…</Text>
|
<Text className='edit-loading-text'>处理中…</Text>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.product-page {
|
.product-page {
|
||||||
padding: 0 24px 24px;
|
padding: 0 24px 24px;
|
||||||
padding-bottom: calc(24px + 160px); /* 为底部 action-bar 留空 */
|
padding-bottom: calc(24px + 200px); /* 为底部毛玻璃 action-bar 留空 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 返回按钮 */
|
/* 返回按钮 */
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
border-bottom: 1px dashed rgba(0,0,0,0.06);
|
border-bottom: 1rpx solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-row:last-child {
|
.info-row:last-child {
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
.info-price {
|
.info-price {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
.summary-price {
|
.summary-price {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 弹窗 */
|
/* 弹窗 */
|
||||||
@@ -211,7 +211,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
border-top: 1px dashed rgba(0,0,0,0.06);
|
border-top: 1rpx solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-label {
|
.total-label {
|
||||||
@@ -222,7 +222,7 @@
|
|||||||
.total-price {
|
.total-price {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: var(--accent-pink);
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-actions {
|
.modal-actions {
|
||||||
@@ -230,8 +230,8 @@
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-actions .btn-outline,
|
.modal-actions .btn-secondary,
|
||||||
.modal-actions .btn-gradient {
|
.modal-actions .btn-primary {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-22
@@ -10,7 +10,7 @@ import { useStatusBar } from '../../hooks/useStatusBar'
|
|||||||
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
import ThemedPageMeta from '../../components/ThemedPageMeta'
|
||||||
import { getProductIconImg } from '../../utils/productConfig'
|
import { getProductIconImg } from '../../utils/productConfig'
|
||||||
|
|
||||||
const HERO_COLORS = ['#FFE4EC', '#FFF0F5', '#FCE4EC']
|
const HERO_COLORS = ['var(--accent-primary-soft)', 'var(--bg-muted)', 'var(--accent-secondary-soft)']
|
||||||
|
|
||||||
export default function ProductPage() {
|
export default function ProductPage() {
|
||||||
const { theme, resolvedTheme } = useThemeContext()
|
const { theme, resolvedTheme } = useThemeContext()
|
||||||
@@ -19,8 +19,9 @@ export default function ProductPage() {
|
|||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [quantity, setQuantity] = useState(1)
|
const [quantity, setQuantity] = useState(1)
|
||||||
|
|
||||||
const params = Taro.getCurrentInstance().router?.params
|
const router = Taro.getCurrentInstance().router
|
||||||
const productId = params?.id || ''
|
const params = router ? router.params : undefined
|
||||||
|
const productId = params ? params.id : ''
|
||||||
const product = getProductById(productId)
|
const product = getProductById(productId)
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
@@ -28,7 +29,7 @@ export default function ProductPage() {
|
|||||||
<View className={`theme-${resolvedTheme}`}>
|
<View className={`theme-${resolvedTheme}`}>
|
||||||
<ThemedPageMeta />
|
<ThemedPageMeta />
|
||||||
<View className='product-page'>
|
<View className='product-page'>
|
||||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
<View className='page-header surface-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||||
<Text className='page-title'>商品未找到</Text>
|
<Text className='page-title'>商品未找到</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -63,8 +64,7 @@ export default function ProductPage() {
|
|||||||
<ThemedPageMeta />
|
<ThemedPageMeta />
|
||||||
<View className='product-page'>
|
<View className='product-page'>
|
||||||
|
|
||||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
<View className='page-header surface-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='flex-between'>
|
<View className='flex-between'>
|
||||||
<Text className='back-btn' onTap={() => Taro.navigateBack()}>←</Text>
|
<Text className='back-btn' onTap={() => Taro.navigateBack()}>←</Text>
|
||||||
<Text className='page-title'>商品详情</Text>
|
<Text className='page-title'>商品详情</Text>
|
||||||
@@ -81,14 +81,13 @@ export default function ProductPage() {
|
|||||||
interval={3000}
|
interval={3000}
|
||||||
duration={500}
|
duration={500}
|
||||||
indicatorDots
|
indicatorDots
|
||||||
indicatorColor='rgba(0,0,0,0.2)'
|
indicatorColor='#e0d5c8'
|
||||||
indicatorActiveColor='#ff9a9e'
|
indicatorActiveColor='#d96c6a'
|
||||||
>
|
>
|
||||||
{product.images && product.images.length > 0 ? (
|
{product.images && product.images.length > 0 ? (
|
||||||
product.images.map((img, idx) => (
|
product.images.map((img, idx) => (
|
||||||
<SwiperItem key={idx}>
|
<SwiperItem key={idx}>
|
||||||
<View className='hero-card dashed-card'>
|
<View className='hero-card surface-card'>
|
||||||
<View className='star-badge' />
|
|
||||||
<Image className='hero-image' src={img} mode='aspectFit' />
|
<Image className='hero-image' src={img} mode='aspectFit' />
|
||||||
</View>
|
</View>
|
||||||
</SwiperItem>
|
</SwiperItem>
|
||||||
@@ -96,8 +95,7 @@ export default function ProductPage() {
|
|||||||
) : (
|
) : (
|
||||||
HERO_COLORS.map((color, idx) => (
|
HERO_COLORS.map((color, idx) => (
|
||||||
<SwiperItem key={idx}>
|
<SwiperItem key={idx}>
|
||||||
<View className='hero-card dashed-card' style={{ background: color }}>
|
<View className='hero-card surface-card' style={{ background: color }}>
|
||||||
<View className='star-badge' />
|
|
||||||
<Image className='hero-icon-img' src={getProductIconImg(product)} mode='aspectFit' />
|
<Image className='hero-icon-img' src={getProductIconImg(product)} mode='aspectFit' />
|
||||||
<Text className='hero-name'>{product.name}</Text>
|
<Text className='hero-name'>{product.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -108,8 +106,7 @@ export default function ProductPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 基本信息 */}
|
{/* 基本信息 */}
|
||||||
<View className='info-section dashed-card mt-20'>
|
<View className='info-section surface-card mt-20'>
|
||||||
<View className='star-badge' />
|
|
||||||
<View className='info-row'>
|
<View className='info-row'>
|
||||||
<Text className='info-label'>单价</Text>
|
<Text className='info-label'>单价</Text>
|
||||||
<Text className='info-price'>¥{product.price}</Text>
|
<Text className='info-price'>¥{product.price}</Text>
|
||||||
@@ -125,8 +122,7 @@ export default function ProductPage() {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 详细介绍 */}
|
{/* 详细介绍 */}
|
||||||
<View className='detail-section dashed-card mt-20'>
|
<View className='detail-section surface-card mt-20'>
|
||||||
<View className='star-badge' />
|
|
||||||
<Text className='section-title'>商品介绍</Text>
|
<Text className='section-title'>商品介绍</Text>
|
||||||
<Text className='detail-text'>{product.description}</Text>
|
<Text className='detail-text'>{product.description}</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -137,10 +133,10 @@ export default function ProductPage() {
|
|||||||
<Text className='summary-label'>起价</Text>
|
<Text className='summary-label'>起价</Text>
|
||||||
<Text className='summary-price'>¥{product.price}</Text>
|
<Text className='summary-price'>¥{product.price}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='btn-outline' onTap={handleAddToList}>
|
<View className='btn-secondary' onTap={handleAddToList}>
|
||||||
<Text>加入设计清单</Text>
|
<Text>加入设计清单</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='btn-gradient' onTap={goToDIY}>
|
<View className='btn-primary' onTap={goToDIY}>
|
||||||
<Text>立即下单</Text>
|
<Text>立即下单</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -151,8 +147,7 @@ export default function ProductPage() {
|
|||||||
{/* 数量选择弹窗 */}
|
{/* 数量选择弹窗 */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<View className='modal-overlay'>
|
<View className='modal-overlay'>
|
||||||
<View className='modal-card dashed-card'>
|
<View className='modal-card surface-card'>
|
||||||
<View className='star-badge' />
|
|
||||||
<Text className='modal-title'>加入设计清单</Text>
|
<Text className='modal-title'>加入设计清单</Text>
|
||||||
<View className='modal-product'>
|
<View className='modal-product'>
|
||||||
<Image className='modal-icon-img' src={getProductIconImg(product)} mode='aspectFit' />
|
<Image className='modal-icon-img' src={getProductIconImg(product)} mode='aspectFit' />
|
||||||
@@ -171,10 +166,10 @@ export default function ProductPage() {
|
|||||||
<Text className='total-price'>¥{(product.price * quantity).toFixed(2)}</Text>
|
<Text className='total-price'>¥{(product.price * quantity).toFixed(2)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='modal-actions'>
|
<View className='modal-actions'>
|
||||||
<View className='btn-outline' onTap={() => setShowModal(false)}>
|
<View className='btn-secondary' onTap={() => setShowModal(false)}>
|
||||||
<Text>取消</Text>
|
<Text>取消</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='btn-gradient' onTap={confirmAdd}>
|
<View className='btn-primary' onTap={confirmAdd}>
|
||||||
<Text>确定</Text>
|
<Text>确定</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
.shop-hero-price {
|
.shop-hero-price {
|
||||||
font-size: 44rpx;
|
font-size: 44rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #ff9a9e;
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-hero-original {
|
.shop-hero-original {
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
.shop-band-price {
|
.shop-band-price {
|
||||||
font-size: 48rpx;
|
font-size: 48rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #ff9a9e;
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-band-original {
|
.shop-band-original {
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
bottom: 6rpx;
|
bottom: 6rpx;
|
||||||
width: 6rpx;
|
width: 6rpx;
|
||||||
border-radius: 4rpx;
|
border-radius: 4rpx;
|
||||||
background: linear-gradient(180deg, #ff9a9e, #fecfef);
|
background: linear-gradient(180deg, var(--accent-primary), var(--accent-primary-soft));
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-section-text {
|
.shop-section-text {
|
||||||
@@ -212,15 +212,15 @@
|
|||||||
border-radius: 40rpx;
|
border-radius: 40rpx;
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background: rgba(255, 154, 158, 0.12);
|
background: var(--accent-primary-soft);
|
||||||
border: 1rpx solid rgba(255, 154, 158, 0.2);
|
border: 1rpx solid var(--accent-primary);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-tag-text {
|
.shop-tag-text {
|
||||||
color: #ff9a9e;
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Spec Table --- */
|
/* --- Spec Table --- */
|
||||||
@@ -272,7 +272,7 @@
|
|||||||
backdrop-filter: blur(16rpx) saturate(1.4);
|
backdrop-filter: blur(16rpx) saturate(1.4);
|
||||||
-webkit-backdrop-filter: blur(16rpx) saturate(1.4);
|
-webkit-backdrop-filter: blur(16rpx) saturate(1.4);
|
||||||
border: 1rpx solid rgba(255,255,255,0.12);
|
border: 1rpx solid rgba(255,255,255,0.12);
|
||||||
box-shadow: 0 2rpx 16rpx rgba(0,0,0,0.25);
|
box-shadow: var(--shadow-card);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -303,6 +303,7 @@
|
|||||||
gap: 24rpx;
|
gap: 24rpx;
|
||||||
backdrop-filter: blur(16rpx) saturate(1.2);
|
backdrop-filter: blur(16rpx) saturate(1.2);
|
||||||
-webkit-backdrop-filter: blur(16rpx) saturate(1.2);
|
-webkit-backdrop-filter: blur(16rpx) saturate(1.2);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
transition: background-color 0.4s ease;
|
transition: background-color 0.4s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +316,7 @@
|
|||||||
.shop-cta-price {
|
.shop-cta-price {
|
||||||
font-size: 40rpx;
|
font-size: 40rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #ff9a9e;
|
color: var(--accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-cta-original {
|
.shop-cta-original {
|
||||||
@@ -327,8 +328,8 @@
|
|||||||
.shop-cta-btn {
|
.shop-cta-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
max-width: 260rpx;
|
max-width: 260rpx;
|
||||||
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 99%);
|
background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-primary-strong) 99%);
|
||||||
color: #5c3a3a;
|
color: #fff;
|
||||||
border-radius: 50rpx;
|
border-radius: 50rpx;
|
||||||
padding: 24rpx 48rpx;
|
padding: 24rpx 48rpx;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
@@ -337,6 +338,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.shop-cta-btn-text {
|
.shop-cta-btn-text {
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ export default function ShopDetailPage() {
|
|||||||
// 顶部为沉浸式商品图,状态栏始终使用白字。
|
// 顶部为沉浸式商品图,状态栏始终使用白字。
|
||||||
useStatusBar(resolvedTheme, { mode: 'light' })
|
useStatusBar(resolvedTheme, { mode: 'light' })
|
||||||
|
|
||||||
const routerParams = Taro.getCurrentInstance().router?.params
|
const routerParams = Taro.getCurrentInstance().router
|
||||||
const productId = routerParams?.id || ''
|
const routerParamsValues = routerParams ? routerParams.params : undefined
|
||||||
|
const productId = routerParamsValues ? routerParamsValues.id : ''
|
||||||
const product: ProductCategory | undefined = PRODUCTS.find(p => p.id === productId)
|
const product: ProductCategory | undefined = PRODUCTS.find(p => p.id === productId)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -35,7 +36,7 @@ export default function ShopDetailPage() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleScroll = useCallback((e: any) => {
|
const handleScroll = useCallback((e: any) => {
|
||||||
const st = e.detail?.scrollTop ?? 0
|
const st = (e.detail && e.detail.scrollTop) || 0
|
||||||
const total = winHeight * 1.2
|
const total = winHeight * 1.2
|
||||||
const p = clamp(st / total, 0, 1)
|
const p = clamp(st / total, 0, 1)
|
||||||
setProgress(p)
|
setProgress(p)
|
||||||
@@ -103,7 +104,7 @@ export default function ShopDetailPage() {
|
|||||||
}
|
}
|
||||||
}, [progress])
|
}, [progress])
|
||||||
|
|
||||||
const tone = useMemo(() => product?.tone || [28, 22, 18], [product])
|
const tone = useMemo(() => (product ? product.tone : [28, 22, 18]), [product])
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
return (
|
return (
|
||||||
@@ -129,7 +130,7 @@ export default function ShopDetailPage() {
|
|||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
className="shop-hero-img"
|
className="shop-hero-img"
|
||||||
src={product.images?.[0] || product.iconImg || ''}
|
src={(product.images && product.images[0]) || product.iconImg || ''}
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|||||||
Reference in New Issue
Block a user