feat: refresh product and customization flows
This commit is contained in:
+33
-31
@@ -12,7 +12,7 @@
|
||||
|
||||
.canvas-area {
|
||||
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;
|
||||
border-radius: 16px;
|
||||
min-width: 200px;
|
||||
@@ -27,7 +27,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: 3px dashed var(--accent-blue);
|
||||
border: 1rpx solid var(--border-strong);
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -41,12 +41,12 @@
|
||||
}
|
||||
|
||||
.canvas-image.active {
|
||||
border-color: var(--accent-pink);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.canvas-image.overlap {
|
||||
border-color: #ff4757;
|
||||
box-shadow: 0 0 12px rgba(255, 71, 87, 0.5);
|
||||
border-color: var(--danger);
|
||||
box-shadow: 0 0 12px var(--danger-soft);
|
||||
}
|
||||
|
||||
/* 贴纸面板 */
|
||||
@@ -64,19 +64,19 @@
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 2px dashed var(--line-star);
|
||||
border: 1rpx solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-input);
|
||||
}
|
||||
|
||||
.sticker-thumb.active {
|
||||
border-color: var(--accent-pink);
|
||||
border-width: 3px;
|
||||
border-color: var(--accent-primary);
|
||||
border-width: 2rpx;
|
||||
}
|
||||
|
||||
.sticker-thumb.overlap {
|
||||
border-color: #ff4757;
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.sticker-thumb-img {
|
||||
@@ -90,7 +90,7 @@
|
||||
right: 4px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: rgba(255, 71, 87, 0.85);
|
||||
background: var(--danger);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -107,7 +107,7 @@
|
||||
.sticker-add-btn {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 2px dashed var(--line-star);
|
||||
border: 1rpx solid var(--border);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -118,7 +118,7 @@
|
||||
|
||||
.add-icon {
|
||||
font-size: 48px;
|
||||
color: var(--accent-pink);
|
||||
color: var(--accent-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
font-size: 26px;
|
||||
color: #ff4757;
|
||||
color: var(--danger);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -162,8 +162,8 @@
|
||||
.tool-btn {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: linear-gradient(135deg, rgba(255, 154, 158, 0.08) 0%, rgba(160, 196, 255, 0.08) 100%);
|
||||
border: 2px dashed var(--accent-blue);
|
||||
background: var(--bg-input);
|
||||
border: 1rpx solid var(--border-strong);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -176,7 +176,7 @@
|
||||
.tool-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-pink);
|
||||
color: var(--accent-primary);
|
||||
min-width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -219,11 +219,11 @@
|
||||
}
|
||||
|
||||
.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;
|
||||
overflow: hidden;
|
||||
margin: 0 auto 24px;
|
||||
border: 3px dashed var(--accent-blue);
|
||||
border: 1rpx solid var(--border-strong);
|
||||
position: relative;
|
||||
min-width: 200px;
|
||||
min-height: 200px;
|
||||
@@ -239,7 +239,7 @@
|
||||
}
|
||||
|
||||
.preview-image.overlap {
|
||||
border: 2px solid #ff4757;
|
||||
border: 2px solid var(--danger);
|
||||
}
|
||||
|
||||
.product-info {
|
||||
@@ -265,13 +265,13 @@
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.preview-actions .btn-outline,
|
||||
.preview-actions .btn-gradient {
|
||||
.preview-actions .btn-secondary,
|
||||
.preview-actions .btn-primary {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preview-actions .btn-gradient.disabled {
|
||||
.preview-actions .btn-primary.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -290,14 +290,14 @@
|
||||
text-align: center;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-secondary);
|
||||
border: 2px dashed var(--text-muted);
|
||||
border: 1rpx solid var(--border-strong);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.edit-btn.active {
|
||||
background: linear-gradient(135deg, rgba(255, 154, 158, 0.12) 0%, rgba(160, 196, 255, 0.12) 100%);
|
||||
color: var(--accent-pink);
|
||||
border-color: var(--accent-pink);
|
||||
background: var(--accent-primary-soft);
|
||||
color: var(--accent-primary);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.edit-btn.disabled {
|
||||
@@ -305,11 +305,13 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.action-btns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
/* 底部操作栏 — 毛玻璃规则由全局 .action-bar 提供 */
|
||||
.action-bar .btn-primary,
|
||||
.action-bar .btn-secondary {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
|
||||
+27
-30
@@ -25,22 +25,23 @@ export default function DIYPage() {
|
||||
const [hasOverlap, setHasOverlap] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const params = Taro.getCurrentInstance().router?.params
|
||||
const source = params?.source
|
||||
const productId = params?.productId || params?.category
|
||||
const router = Taro.getCurrentInstance().router
|
||||
const params = router ? router.params : undefined
|
||||
const source = params ? params.source : undefined
|
||||
const productId = (params && params.productId) || (params && params.category)
|
||||
|
||||
if (source === 'product' && productId) {
|
||||
const product = getProductById(productId)
|
||||
if (product) {
|
||||
setCategory(product)
|
||||
setQuantity(Number(params?.quantity) || 1)
|
||||
setQuantity(Number(params ? params.quantity : undefined) || 1)
|
||||
const newDesign: DesignItem = {
|
||||
id: 'DSG' + Date.now(),
|
||||
productId: product.id,
|
||||
productName: product.name,
|
||||
productIcon: product.icon,
|
||||
unitPrice: product.price,
|
||||
count: Number(params?.quantity) || 1,
|
||||
count: Number(params ? params.quantity : undefined) || 1,
|
||||
status: 'designing',
|
||||
createdAt: new Date().toISOString().slice(0, 10)
|
||||
}
|
||||
@@ -51,7 +52,7 @@ export default function DIYPage() {
|
||||
return
|
||||
}
|
||||
|
||||
if (source === 'designList' && params?.designId) {
|
||||
if (source === 'designList' && params && params.designId) {
|
||||
const dId = params.designId
|
||||
const list = getDesignList()
|
||||
const design = list.find(d => d.id === dId)
|
||||
@@ -60,16 +61,16 @@ export default function DIYPage() {
|
||||
setQuantity(design.count)
|
||||
const product = getProductById(design.productId)
|
||||
if (product) setCategory(product)
|
||||
if (design.designData?.stickers) {
|
||||
if (design.designData ? design.designData.stickers : undefined) {
|
||||
setStickers(design.designData.stickers)
|
||||
} else if (design.designData?.imageSrc) {
|
||||
} else if (design.designData ? design.designData.imageSrc : undefined) {
|
||||
// 兼容旧版数据
|
||||
const s: StickerItem = {
|
||||
id: 'legacy_' + Date.now(),
|
||||
src: design.designData.imageSrc,
|
||||
x: design.designData.imagePos?.x || 0,
|
||||
y: design.designData.imagePos?.y || 0,
|
||||
scale: design.designData.imagePos?.scale || 1,
|
||||
x: design.designData.imagePos ? design.designData.imagePos.x || 0 : 0,
|
||||
y: design.designData.imagePos ? design.designData.imagePos.y || 0 : 0,
|
||||
scale: design.designData.imagePos ? design.designData.imagePos.scale || 1 : 1,
|
||||
width: 200,
|
||||
height: 200,
|
||||
isOverlapping: false
|
||||
@@ -255,6 +256,9 @@ export default function DIYPage() {
|
||||
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) {
|
||||
return (
|
||||
<View className={`theme-${resolvedTheme}`}>
|
||||
@@ -271,8 +275,7 @@ export default function DIYPage() {
|
||||
<ThemedPageMeta />
|
||||
<View className='diy-page'>
|
||||
|
||||
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<View className='star-badge' />
|
||||
<View className='page-header surface-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
|
||||
<View className='flex-between' style={{ width: '100%' }}>
|
||||
<Text className='back-btn' onTap={goBack}>←</Text>
|
||||
<Text className='page-title'>设计工作台</Text>
|
||||
@@ -281,8 +284,7 @@ export default function DIYPage() {
|
||||
</View>
|
||||
|
||||
{/* 画布区域 — catchtouchmove 阻止事件冒泡导致页面滚动 */}
|
||||
<View className='canvas-wrapper dashed-card mt-20' catchMove>
|
||||
<View className='star-badge' />
|
||||
<View className='canvas-wrapper surface-card mt-20' catchMove>
|
||||
<View className='canvas-area' style={getMaskStyle()}>
|
||||
{stickers.map(s => (
|
||||
<Image
|
||||
@@ -308,8 +310,7 @@ export default function DIYPage() {
|
||||
</View>
|
||||
|
||||
{/* 贴纸列表与控制 */}
|
||||
<View className='sticker-panel dashed-card mt-20'>
|
||||
<View className='star-badge' />
|
||||
<View className='sticker-panel surface-card mt-20'>
|
||||
<Text className='section-title'>贴纸</Text>
|
||||
<View className='sticker-list'>
|
||||
{stickers.map(s => (
|
||||
@@ -330,15 +331,12 @@ export default function DIYPage() {
|
||||
|
||||
{/* 控制工具栏 */}
|
||||
{activeStickerId && (
|
||||
<View className='toolbar dashed-card mt-20'>
|
||||
<View className='star-badge' />
|
||||
<View className='toolbar surface-card mt-20'>
|
||||
<View className='tool-row'>
|
||||
<Text className='tool-label'>缩放</Text>
|
||||
<View className='flex-center' style={{ gap: '20px' }}>
|
||||
<View className='tool-btn' onTap={() => handleScale(activeStickerId, -0.1)}>-</View>
|
||||
<Text className='tool-value'>
|
||||
{Math.round((stickers.find(s => s.id === activeStickerId)?.scale || 1) * 100)}%
|
||||
</Text>
|
||||
<Text className='tool-value'>{Math.round(activeScale * 100)}%</Text>
|
||||
<View className='tool-btn' onTap={() => handleScale(activeStickerId, 0.1)}>+</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -360,11 +358,11 @@ export default function DIYPage() {
|
||||
</View>
|
||||
|
||||
{/* 底部操作按钮 */}
|
||||
<View className='action-btns mt-20'>
|
||||
<View className='btn-gradient' onTap={() => setPreviewMode(true)}>
|
||||
<View className='action-bar'>
|
||||
<View className='btn-primary' onTap={() => setPreviewMode(true)}>
|
||||
<Text>预览效果</Text>
|
||||
</View>
|
||||
<View className='btn-outline' onTap={addSticker}>
|
||||
<View className='btn-secondary' onTap={addSticker}>
|
||||
<Text>添加贴纸</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -372,8 +370,7 @@ export default function DIYPage() {
|
||||
{/* 预览弹层 */}
|
||||
{previewMode && (
|
||||
<View className='preview-overlay'>
|
||||
<View className='preview-card dashed-card'>
|
||||
<View className='star-badge' />
|
||||
<View className='preview-card surface-card'>
|
||||
<Text className='preview-title'>确认效果</Text>
|
||||
<View className='preview-canvas' style={getMaskStyle()}>
|
||||
{stickers.map(s => (
|
||||
@@ -396,10 +393,10 @@ export default function DIYPage() {
|
||||
{hasOverlap && <Text className='overlap-hint'>贴纸有重叠,不可提交</Text>}
|
||||
</View>
|
||||
<View className='preview-actions'>
|
||||
<View className='btn-outline' onTap={() => setPreviewMode(false)}>
|
||||
<View className='btn-secondary' onTap={() => setPreviewMode(false)}>
|
||||
<Text>返回修改</Text>
|
||||
</View>
|
||||
<View className={`btn-gradient ${hasOverlap ? 'disabled' : ''}`} onTap={handleComplete}>
|
||||
<View className={`btn-primary ${hasOverlap ? 'disabled' : ''}`} onTap={handleComplete}>
|
||||
<Text>确认完成</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -407,7 +404,7 @@ export default function DIYPage() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={{ height: '40px' }} />
|
||||
<View className='safe-bottom-placeholder' />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
.edit-save {
|
||||
font-size: 28px;
|
||||
color: var(--accent-pink);
|
||||
color: var(--accent-primary);
|
||||
font-weight: 600;
|
||||
width: 60px;
|
||||
text-align: right;
|
||||
@@ -93,7 +93,7 @@
|
||||
.slider-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(90deg, #ff9a9e, #fecfef);
|
||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-primary-soft));
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
@@ -104,7 +104,7 @@
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
border: 4px solid var(--accent-pink);
|
||||
border: 4px solid var(--accent-primary);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
@@ -132,7 +132,7 @@
|
||||
font-weight: 500;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-primary);
|
||||
border: 2px dashed var(--line-star);
|
||||
border: 1rpx solid var(--border);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
@@ -142,20 +142,14 @@
|
||||
}
|
||||
|
||||
.quick-btn.active {
|
||||
background: rgba(255, 154, 158, 0.12);
|
||||
border-color: var(--accent-pink);
|
||||
color: var(--accent-pink);
|
||||
background: var(--accent-primary-soft);
|
||||
border-color: var(--accent-primary);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* 底部操作 */
|
||||
.edit-footer {
|
||||
padding: 20px 0 calc(20px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.edit-footer .btn-gradient,
|
||||
.edit-footer .btn-outline {
|
||||
/* 底部操作栏 — 毛玻璃规则由全局 .action-bar 提供 */
|
||||
.edit-footer .btn-primary,
|
||||
.edit-footer .btn-secondary {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
|
||||
@@ -45,13 +45,15 @@ function TouchSlider({ value, min, max, step = 1, onChange, format }: SliderProp
|
||||
|
||||
const handleTouchStart = (e: any) => {
|
||||
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))
|
||||
}
|
||||
|
||||
const handleTouchMove = (e: any) => {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -106,14 +108,15 @@ export default function StickerEditPage() {
|
||||
query.select('#editCanvas')
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res: any) => {
|
||||
const canvas = res?.[0]?.node
|
||||
const resFirst = res && res[0]
|
||||
const canvas = resFirst ? resFirst.node : undefined
|
||||
if (!canvas) {
|
||||
setTimeout(() => initCanvas(st), 300)
|
||||
return
|
||||
}
|
||||
const ctx = canvas.getContext('2d')
|
||||
let cw = res[0]?.width || 300
|
||||
let ch = res[0]?.height || 400
|
||||
let cw = (res[0] && res[0].width) || 300
|
||||
let ch = (res[0] && res[0].height) || 400
|
||||
if (cw <= 0) cw = 300
|
||||
if (ch <= 0) ch = 400
|
||||
const dpr = Taro.getSystemInfoSync().pixelRatio || 1
|
||||
@@ -130,20 +133,21 @@ export default function StickerEditPage() {
|
||||
}, [canvasReady])
|
||||
|
||||
useEffect(() => {
|
||||
const params = Taro.getCurrentInstance().router?.params
|
||||
const dId = params?.designId as string
|
||||
const sId = params?.stickerId as string
|
||||
const router = Taro.getCurrentInstance().router
|
||||
const params = router ? router.params : undefined
|
||||
const dId = params ? params.designId : ''
|
||||
const sId = params ? params.stickerId : ''
|
||||
setDesignId(dId)
|
||||
setStickerId(sId)
|
||||
|
||||
const list = getDesignList()
|
||||
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) {
|
||||
setSticker(st)
|
||||
setBrightness(st.edits?.brightness || 0)
|
||||
setHue(st.edits?.hue || 0)
|
||||
setContrast(st.edits?.contrast || 0)
|
||||
setBrightness((st.edits && st.edits.brightness) || 0)
|
||||
setHue((st.edits && st.edits.hue) || 0)
|
||||
setContrast((st.edits && st.edits.contrast) || 0)
|
||||
initCanvas(st)
|
||||
}
|
||||
}, [initCanvas])
|
||||
@@ -171,9 +175,9 @@ export default function StickerEditPage() {
|
||||
const y = (ch - h) / 2
|
||||
|
||||
// 应用滤镜(小程序 Canvas 2D 支持 filter 属性 2.16.0+)
|
||||
const b = opts?.brightness ?? brightness
|
||||
const hVal = opts?.hue ?? hue
|
||||
const c = opts?.contrast ?? contrast
|
||||
const b = opts ? opts.brightness : brightness
|
||||
const hVal = opts ? opts.hue : hue
|
||||
const c = opts ? opts.contrast : contrast
|
||||
if (b !== 0 || hVal !== 0 || c !== 0) {
|
||||
ctx.filter = `brightness(${100 + b}%) hue-rotate(${hVal}deg) contrast(${100 + c}%)`
|
||||
} else {
|
||||
@@ -217,7 +221,7 @@ export default function StickerEditPage() {
|
||||
const dList = getDesignList()
|
||||
const dIdx = dList.findIndex(d => d.id === designId)
|
||||
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)
|
||||
if (sIdx === -1) { setLoading(false); return }
|
||||
stickers[sIdx] = {
|
||||
@@ -325,7 +329,7 @@ export default function StickerEditPage() {
|
||||
</View>
|
||||
|
||||
{/* 快速操作 */}
|
||||
<View className='edit-tools dashed-card mt-20'>
|
||||
<View className='edit-tools surface-card mt-20'>
|
||||
<View className='edit-row'>
|
||||
<Text className='edit-label'>快速操作</Text>
|
||||
<View className='edit-quick-actions'>
|
||||
@@ -411,6 +415,17 @@ export default function StickerEditPage() {
|
||||
|
||||
<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 && (
|
||||
<View className='edit-loading'>
|
||||
<Text className='edit-loading-text'>处理中…</Text>
|
||||
|
||||
Reference in New Issue
Block a user