feat: add scroll top mask, unify page top spacing and docs

This commit is contained in:
2026-08-08 03:57:47 +08:00
parent 38045073b0
commit a3c3005c04
73 changed files with 1350 additions and 169 deletions
+120
View File
@@ -0,0 +1,120 @@
.scroll-top-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 40;
pointer-events: none;
transform: translateZ(0);
}
.scroll-top-blur,
.scroll-top-gradient {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100%;
pointer-events: none;
}
.scroll-top-gradient {
height: 80%;
}
.scroll-top-title {
position: absolute;
left: 0;
right: 0;
bottom: 38%;
text-align: center;
font-size: 36rpx;
font-weight: 600;
color: var(--text-primary);
letter-spacing: 0;
pointer-events: none;
}
.scroll-top-back {
position: absolute;
left: 30rpx;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
pointer-events: auto;
background: rgba(255, 255, 255, 0.55);
backdrop-filter: blur(32px) saturate(1.2);
-webkit-backdrop-filter: blur(32px) saturate(1.2);
border: 1rpx solid rgba(255, 255, 255, 0.55);
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.18), 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.theme-dark .scroll-top-back {
background: rgba(25, 25, 25, 0.55);
border-color: rgba(255, 255, 255, 0.14);
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.5), 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
}
.scroll-top-back-arrow {
font-size: 44rpx;
font-weight: 600;
line-height: 1;
color: var(--text-primary);
}
.scroll-top-blur-mid {
backdrop-filter: blur(12rpx);
-webkit-backdrop-filter: blur(12rpx);
-webkit-mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.8) 42%,
rgba(0, 0, 0, 0) 76%
);
mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.8) 42%,
rgba(0, 0, 0, 0) 76%
);
}
.scroll-top-blur-soft {
backdrop-filter: blur(6rpx);
-webkit-backdrop-filter: blur(6rpx);
-webkit-mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.65) 52%,
rgba(0, 0, 0, 0) 100%
);
mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0.65) 52%,
rgba(0, 0, 0, 0) 100%
);
}
.theme-light .scroll-top-gradient {
background: linear-gradient(
to bottom,
rgba(250, 247, 242, 0.94) 0%,
rgba(250, 247, 242, 0.9) 50%,
rgba(250, 247, 242, 0.58) 78%,
rgba(250, 247, 242, 0) 100%
);
}
.theme-dark .scroll-top-gradient {
background: linear-gradient(
to bottom,
rgba(25, 25, 25, 0.94) 0%,
rgba(25, 25, 25, 0.9) 50%,
rgba(25, 25, 25, 0.58) 78%,
rgba(25, 25, 25, 0) 100%
);
}
+121
View File
@@ -0,0 +1,121 @@
import { View, Text } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useSafeArea } from '../../hooks/useSafeArea'
import { getTopMaskProgress, getTopMaskStartY } from '../../utils/homeTopMask'
import './index.scss'
interface ScrollTopMaskProps {
title: string
targetSelector?: string
showBack?: boolean
}
export default function ScrollTopMask({ title, targetSelector = '.page-header', showBack = false }: ScrollTopMaskProps) {
const safe = useSafeArea()
const [topMaskOpacity, setTopMaskOpacity] = useState(0)
const fallbackTitleMetrics = useMemo(() => ({
startY: Math.max(0, safe.headerPaddingTop - safe.statusBarHeight),
rangeY: Math.max(48, safe.headerPaddingTop * 0.45)
}), [safe.headerPaddingTop, safe.statusBarHeight])
const titleMetricsRef = useRef(fallbackTitleMetrics)
const scrollFrameRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const latestScrollTopRef = useRef(0)
const applyTopMaskProgress = useCallback((scrollTop: number) => {
latestScrollTopRef.current = scrollTop
if (scrollFrameRef.current !== null) {
return
}
scrollFrameRef.current = setTimeout(() => {
scrollFrameRef.current = null
const { startY, rangeY } = titleMetricsRef.current
setTopMaskOpacity(getTopMaskProgress({ scrollTop: latestScrollTopRef.current, startY, rangeY }))
}, 16)
}, [])
useEffect(() => {
let cancelled = false
let measureTimer: ReturnType<typeof setTimeout> | null = null
titleMetricsRef.current = fallbackTitleMetrics
type HeaderRect = Taro.NodesRef.BoundingClientRectCallbackResult
const measureTitle = () => {
if (cancelled) return
Taro.createSelectorQuery()
.select(targetSelector)
.boundingClientRect((rect: HeaderRect | HeaderRect[]) => {
if (cancelled) return
if (!rect || Array.isArray(rect) || typeof rect.bottom !== 'number' || typeof rect.height !== 'number') {
titleMetricsRef.current = fallbackTitleMetrics
applyTopMaskProgress(latestScrollTopRef.current)
return
}
const startY = getTopMaskStartY({
viewportBottom: rect.bottom,
scrollTop: latestScrollTopRef.current,
statusBarHeight: safe.statusBarHeight
})
const rangeY = Math.max(32, rect.height * 0.7)
titleMetricsRef.current = { startY, rangeY }
applyTopMaskProgress(latestScrollTopRef.current)
})
.exec()
}
Taro.nextTick(() => {
if (cancelled) return
measureTimer = setTimeout(measureTitle, 80)
})
return () => {
cancelled = true
if (measureTimer !== null) {
clearTimeout(measureTimer)
}
if (scrollFrameRef.current !== null) {
clearTimeout(scrollFrameRef.current)
}
scrollFrameRef.current = null
}
}, [applyTopMaskProgress, fallbackTitleMetrics, targetSelector])
Taro.usePageScroll((e) => {
applyTopMaskProgress(e.scrollTop)
})
return (
<View
className='scroll-top-mask'
style={{
height: `${Math.max(safe.menuButtonTop + safe.menuButtonHeight + 18, safe.statusBarHeight + 52) + 24}px`,
opacity: topMaskOpacity
}}
>
<View className='scroll-top-blur scroll-top-blur-mid' />
<View className='scroll-top-blur scroll-top-blur-soft' />
<View className='scroll-top-gradient' />
{showBack && (
<View
className='scroll-top-back'
style={{ top: `${safe.menuButtonTop}px` }}
onTap={() => Taro.navigateBack()}
>
<Text className='scroll-top-back-arrow'></Text>
</View>
)}
<Text
className='scroll-top-title'
style={{
opacity: Math.max(0, Math.min(1, (topMaskOpacity - 0.5) / 0.5))
}}
>
{title}
</Text>
</View>
)
}
+3 -1
View File
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import BottomActionBar from '../../components/BottomActionBar'
import { assetUrl } from '../../utils/asset'
@@ -97,9 +98,10 @@ export default function AddressPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="收货地址" targetSelector=".page-header" showBack />
<View className='address-page'>
<View className='page-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='page-header-inner'>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
+3 -1
View File
@@ -4,6 +4,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
export default function AgreementPage() {
const { theme, resolvedTheme } = useThemeContext()
@@ -36,8 +37,9 @@ export default function AgreementPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="定制协议" targetSelector=".agreement-title" />
<View className='agreement-page'>
<View className='agreement-content' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='agreement-content' style={{ marginTop: `${safe.statusBarHeight + 12}px` }}>
<Text className='agreement-title'></Text>
{SECTIONS.map((sec, idx) => (
<View key={idx} className='agreement-section surface-card'>
+5 -2
View File
@@ -7,6 +7,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import BottomActionBar from '../../components/BottomActionBar'
export default function CheckoutPage() {
@@ -67,8 +68,9 @@ export default function CheckoutPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="设计效果确认" targetSelector=".page-header" showBack />
<View className='checkout-page'>
<View className='page-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='page-header-inner'>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
@@ -94,9 +96,10 @@ export default function CheckoutPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="设计效果确认" targetSelector=".page-header" showBack />
<View className='checkout-page'>
<View className='page-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='page-header-inner'>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
+1 -1
View File
@@ -1,5 +1,5 @@
.design-page {
padding: 0 32rpx 24rpx;
padding: 0 32rpx calc(220rpx + env(safe-area-inset-bottom));
min-height: 100vh;
}
+3 -1
View File
@@ -10,6 +10,7 @@ import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import LoginGuard from '../../components/LoginGuard'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
const STATUS_TABS = [
{ code: 'all', label: '全部' },
@@ -120,9 +121,10 @@ export default function DesignListPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="我的设计清单" targetSelector=".page-header.design-header" />
<LoginGuard>
<View className='design-page'>
<View className='page-header design-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header design-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='header-spacer' />
<Text className='header-title'></Text>
<View className='header-manage' onTap={handleManage}>
+4 -1
View File
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import BottomActionBar from '../../components/BottomActionBar'
export default function DIYPage() {
@@ -264,6 +265,7 @@ export default function DIYPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="设计工作台" targetSelector=".page-header.surface-card" showBack />
<View className='diy-page'>
<Text className='page-title'>...</Text>
</View>
@@ -274,9 +276,10 @@ export default function DIYPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="设计工作台" targetSelector=".page-header.surface-card" showBack />
<View className='diy-page'>
<View className='page-header surface-card mt-20' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header surface-card mt-20' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='flex-between' style={{ width: '100%' }}>
<Text className='back-btn' onTap={goBack}></Text>
<Text className='page-title'></Text>
+3 -1
View File
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../../context/ThemeContext'
import { useSafeArea } from '../../../hooks/useSafeArea'
import { useStatusBar } from '../../../hooks/useStatusBar'
import ThemedPageMeta from '../../../components/ThemedPageMeta'
import ScrollTopMask from '../../../components/ScrollTopMask'
import BottomActionBar from '../../../components/BottomActionBar'
/* ============================================================
@@ -311,9 +312,10 @@ export default function StickerEditPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="编辑贴纸" targetSelector=".edit-header" showBack />
<View className='sticker-edit-page'>
{/* Header */}
<View className='edit-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='edit-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<Text className='edit-back' onTap={() => Taro.navigateBack()}></Text>
<Text className='edit-title'></Text>
<Text className='edit-save' onTap={handleSave}></Text>
+10 -34
View File
@@ -1,5 +1,6 @@
.index-page {
padding: 0 32rpx 24rpx;
padding-bottom: calc(220rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
@@ -27,6 +28,7 @@
border-radius: 999rpx;
background: var(--bg-input);
border: 1rpx solid transparent;
box-shadow: var(--shadow-card);
transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
@@ -54,7 +56,12 @@
}
.search-input::placeholder {
color: var(--text-muted);
color: var(--text-secondary);
font-size: 26rpx;
}
.search-input-placeholder {
color: var(--text-secondary);
font-size: 26rpx;
}
@@ -118,10 +125,10 @@
max-width: 78%;
padding: 16rpx 24rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.72);
background: var(--glass);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
border: 1rpx solid rgba(255, 255, 255, 0.6);
border: 1rpx solid var(--glass-line);
box-shadow: var(--shadow-card);
}
@@ -252,34 +259,3 @@
font-size: 24rpx;
color: var(--text-secondary);
}
.home-top-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 40;
pointer-events: none;
transform: translateZ(0);
}
.theme-light .home-top-mask {
background: linear-gradient(
to bottom,
rgba(250, 247, 242, 0.98) 0%,
rgba(250, 247, 242, 0.92) 38%,
rgba(250, 247, 242, 0.48) 70%,
rgba(250, 247, 242, 0) 100%
);
}
.theme-dark .home-top-mask {
background: linear-gradient(
to bottom,
rgba(25, 25, 25, 0.98) 0%,
rgba(25, 25, 25, 0.92) 38%,
rgba(25, 25, 25, 0.48) 70%,
rgba(25, 25, 25, 0) 100%
);
}
+6 -86
View File
@@ -1,6 +1,6 @@
import { View, Text, Image, Input, Button, Swiper, SwiperItem } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useState } from 'react'
import './index.scss'
import { CATEGORIES } from '../../utils/productConfig'
import { assetUrl } from '../../utils/asset'
@@ -8,7 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import { getTopMaskProgress, getTopMaskStartY } from '../../utils/homeTopMask'
import ScrollTopMask from '../../components/ScrollTopMask'
// 成品展示配图(从 img 里取对应实物图)
const SHOWCASE_LIST = [
@@ -71,81 +71,6 @@ export default function Index() {
useStatusBar(resolvedTheme)
const [searchKey, setSearchKey] = useState('')
const [topMaskOpacity, setTopMaskOpacity] = useState(0)
const fallbackTitleMetrics = useMemo(() => ({
startY: Math.max(0, safe.headerPaddingTop - safe.statusBarHeight),
rangeY: Math.max(48, safe.headerPaddingTop * 0.45)
}), [safe.headerPaddingTop, safe.statusBarHeight])
const titleMetricsRef = useRef(fallbackTitleMetrics)
const scrollFrameRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const latestScrollTopRef = useRef(0)
const applyTopMaskProgress = useCallback((scrollTop: number) => {
latestScrollTopRef.current = scrollTop
if (scrollFrameRef.current !== null) {
return
}
scrollFrameRef.current = setTimeout(() => {
scrollFrameRef.current = null
const { startY, rangeY } = titleMetricsRef.current
setTopMaskOpacity(getTopMaskProgress({ scrollTop: latestScrollTopRef.current, startY, rangeY }))
}, 16)
}, [])
useEffect(() => {
let cancelled = false
let measureTimer: ReturnType<typeof setTimeout> | null = null
titleMetricsRef.current = fallbackTitleMetrics
type HomeHeaderRect = Taro.NodesRef.BoundingClientRectCallbackResult
const measureTitle = () => {
if (cancelled) return
Taro.createSelectorQuery()
.select('.home-header')
.boundingClientRect((rect: HomeHeaderRect | HomeHeaderRect[]) => {
if (cancelled) return
if (!rect || Array.isArray(rect) || typeof rect.bottom !== 'number' || typeof rect.height !== 'number') {
titleMetricsRef.current = fallbackTitleMetrics
applyTopMaskProgress(latestScrollTopRef.current)
return
}
const startY = getTopMaskStartY({
viewportBottom: rect.bottom,
scrollTop: latestScrollTopRef.current,
statusBarHeight: safe.statusBarHeight
})
const rangeY = Math.max(32, rect.height * 0.7)
titleMetricsRef.current = { startY, rangeY }
applyTopMaskProgress(latestScrollTopRef.current)
})
.exec()
}
Taro.nextTick(() => {
if (cancelled) return
measureTimer = setTimeout(measureTitle, 80)
})
return () => {
cancelled = true
if (measureTimer !== null) {
clearTimeout(measureTimer)
}
if (scrollFrameRef.current !== null) {
clearTimeout(scrollFrameRef.current)
}
scrollFrameRef.current = null
}
}, [applyTopMaskProgress, fallbackTitleMetrics])
Taro.usePageScroll((e) => {
applyTopMaskProgress(e.scrollTop)
})
const navigateToProduct = (categoryId: string) => {
Taro.navigateTo({ url: `/pages/product/index?id=${categoryId}` })
}
@@ -174,17 +99,11 @@ export default function Index() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<View
className='home-top-mask'
style={{
height: `${Math.max(safe.menuButtonTop + safe.menuButtonHeight + 18, safe.statusBarHeight + 52)}px`,
opacity: topMaskOpacity
}}
/>
<ScrollTopMask title='智绘微刻' targetSelector='.home-header' />
<View className='index-page'>
{/* 页头 */}
<View className='home-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='home-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<Text className='home-title'></Text>
</View>
@@ -194,7 +113,8 @@ export default function Index() {
<Input
className='search-input'
type='text'
placeholder='搜索定制品类笔记本、杯垫、书灯...'
placeholder='搜索定制品类,如笔记本、杯垫、书灯'
placeholderClass='search-input-placeholder'
value={searchKey}
onInput={handleSearch}
confirmType='search'
+5 -2
View File
@@ -6,6 +6,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import { getOrderList, getDefaultAddress, getAddressList, type AddressItem, type OrderItem } from '../../utils/store'
import { PRODUCT_ICON_MAP } from '../../utils/productConfig'
import { assetUrl } from '../../utils/asset'
@@ -55,8 +56,9 @@ export default function OrderDetailPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="订单详情" targetSelector=".page-header" showBack />
<View className='order-detail-page'>
<View className='page-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='page-header-inner'>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
@@ -73,9 +75,10 @@ export default function OrderDetailPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="订单详情" targetSelector=".page-header" showBack />
<View className='order-detail-page'>
<View className='page-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='page-header-inner'>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
+1 -1
View File
@@ -1,5 +1,5 @@
.orders-page {
padding: 0 32rpx 48rpx;
padding: 0 32rpx calc(220rpx + env(safe-area-inset-bottom));
min-height: 100vh;
box-sizing: border-box;
}
+3 -1
View File
@@ -10,6 +10,7 @@ import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import LoginGuard from '../../components/LoginGuard'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
const STATUS_TABS = [
{ code: 'all', label: '全部' },
@@ -84,10 +85,11 @@ export default function OrdersPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="我的订单" targetSelector=".orders-page .page-header" />
<LoginGuard>
<View className='orders-page'>
<View className='page-header' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ marginTop: `${safe.statusBarHeight + 12}px` }}>
<Text className='page-title'></Text>
</View>
+5 -2
View File
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import BottomActionBar from '../../components/BottomActionBar'
import { getProductIconImg } from '../../utils/productConfig'
@@ -27,8 +28,9 @@ export default function ProductPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="商品详情" targetSelector=".page-header" showBack />
<View className='product-page'>
<View className='page-header' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ marginTop: `${safe.statusBarHeight + 12}px` }}>
<View className='page-header-inner'>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
@@ -72,9 +74,10 @@ export default function ProductPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="商品详情" targetSelector=".page-header" showBack />
<View className='product-page'>
<View className='page-header' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header' style={{ marginTop: `${safe.statusBarHeight + 12}px` }}>
<View className='page-header-inner'>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
+1 -1
View File
@@ -1,5 +1,5 @@
.profile-page {
padding: 0 32rpx 48rpx;
padding: 0 32rpx calc(220rpx + env(safe-area-inset-bottom));
min-height: 100vh;
box-sizing: border-box;
}
+3 -1
View File
@@ -7,6 +7,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import { login, getMe, updateProfile, getToken } from '../../utils/api'
import { safeHideLoading } from '../../utils/request'
import { assetUrl } from '../../utils/asset'
@@ -279,10 +280,11 @@ export default function ProfilePage() {
return (
<View className={`theme-${resolvedTheme}`} style={{ minHeight: '100vh' }}>
<ThemedPageMeta />
<ScrollTopMask title="我的" targetSelector=".profile-header" />
<View className='profile-page'>
{/* 用户信息头部 */}
<View className='profile-header surface-card' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='profile-header surface-card' style={{ marginTop: `${safe.statusBarHeight + 12}px` }}>
<View className='user-info'>
{isLoggedIn && userInfo.avatarUrl ? (
<Image className='avatar' src={userInfo.avatarUrl} mode='aspectFill' />
+38 -1
View File
@@ -9,19 +9,56 @@
}
.service-page .page-header.service-header {
display: flex;
align-items: center;
gap: 16rpx;
margin: 0;
padding-left: 0;
padding-right: 0;
padding-bottom: 32rpx;
text-align: center;
}
.service-header .back-btn {
display: flex;
align-items: center;
justify-content: center;
width: 88rpx;
height: 88rpx;
flex: 0 0 auto;
border-radius: 50%;
background: var(--bg-card);
border: 1rpx solid var(--border-strong);
box-shadow: var(--shadow-card);
box-sizing: border-box;
}
.service-header .back-btn:active {
transform: scale(0.96);
}
.service-header .back-arrow {
font-size: 36rpx;
line-height: 1;
color: var(--text-primary);
}
.service-header .page-title {
flex: 1;
min-width: 0;
text-align: center;
font-size: 44rpx;
font-weight: 700;
line-height: 1.2;
color: var(--text-primary);
letter-spacing: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.service-header .header-spacer {
width: 88rpx;
flex: 0 0 auto;
}
/* 客服主入口 */
+7 -1
View File
@@ -5,6 +5,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import { assetUrl } from '../../utils/asset'
const FAQ_ITEMS = [
@@ -22,9 +23,14 @@ export default function ServicePage() {
return (
<View className={`theme-${resolvedTheme}`} style={{ minHeight: '100vh' }}>
<ThemedPageMeta />
<ScrollTopMask title="联系客服" targetSelector=".page-header.service-header" showBack />
<View className='service-page'>
<View className='page-header service-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header service-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
</View>
<Text className='page-title'></Text>
<View className='header-spacer' />
</View>
{/* 客服入口 */}
+3 -1
View File
@@ -6,6 +6,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import { getUserInfo, setUserInfo, clearUserInfo, getAddressList, getDefaultAddress, type AddressItem } from '../../utils/store'
import { assetUrl } from '../../utils/asset'
@@ -69,10 +70,11 @@ export default function SettingsPage() {
return (
<View className={`theme-${resolvedTheme}`} style={{ minHeight: '100vh' }}>
<ThemedPageMeta />
<ScrollTopMask title="设置" targetSelector=".page-header.settings-header" showBack />
<View className='settings-page'>
<View className='settings-area'>
<View className='page-header settings-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header settings-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='back-btn' onTap={() => Taro.navigateBack()}>
<Text className='back-arrow'></Text>
</View>
+3
View File
@@ -8,6 +8,7 @@ import { useSafeArea } from '../../../hooks/useSafeArea'
import { useStatusBar } from '../../../hooks/useStatusBar'
import { useThemeContext } from '../../../context/ThemeContext'
import ThemedPageMeta from '../../../components/ThemedPageMeta'
import ScrollTopMask from '../../../components/ScrollTopMask'
import BottomActionBar from '../../../components/BottomActionBar'
const clamp = (v: number, min: number, max: number) => Math.min(Math.max(v, min), max)
@@ -111,6 +112,7 @@ export default function ShopDetailPage() {
return (
<View className="shop-detail-page theme-dark">
<ThemedPageMeta darkTop />
<ScrollTopMask title="商品详情" targetSelector=".shop-back-btn" showBack />
<Text style={{ color: '#fff', padding: '40rpx' }}></Text>
</View>
)
@@ -119,6 +121,7 @@ export default function ShopDetailPage() {
return (
<View className="shop-detail-page theme-dark">
<ThemedPageMeta darkTop />
<ScrollTopMask title="商品详情" targetSelector=".shop-back-btn" showBack />
{/* Fixed Hero */}
<View className="shop-hero-fixed">
<View
+1 -1
View File
@@ -4,7 +4,7 @@
/* --- List Layer --- */
.shop-list {
padding: 0 32rpx 24rpx;
padding: 0 32rpx calc(220rpx + env(safe-area-inset-bottom));
}
.shop-header {
+3 -1
View File
@@ -4,6 +4,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import './index.scss'
import { PRODUCTS } from '../../utils/productConfig'
@@ -19,8 +20,9 @@ export default function ShopPage() {
return (
<View className={`shop-page theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="智绘精选" targetSelector=".shop-header" />
<View className="shop-list">
<View className="shop-header" style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className="shop-header" style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<Text className="shop-header-title"></Text>
<Text className="shop-header-sub"></Text>
</View>
+5 -2
View File
@@ -14,6 +14,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import { assetUrl } from '../../utils/asset'
export default function UserDatabasePage() {
@@ -86,7 +87,8 @@ export default function UserDatabasePage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<View className='udb-page udb-lock-page' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<ScrollTopMask title="本地用户数据库" targetSelector=".page-header.udb-header" />
<View className='udb-page udb-lock-page' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='udb-lock-card surface-card'>
<Image className='udb-lock-icon' src={assetUrl('/icon/账号管理.png')} mode='aspectFit' />
<Text className='udb-lock-title'></Text>
@@ -114,9 +116,10 @@ export default function UserDatabasePage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="本地用户数据库" targetSelector=".page-header.udb-header" />
<View className='udb-page'>
<View className='page-header udb-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header udb-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<Text className='page-title'></Text>
</View>
+3 -1
View File
@@ -6,6 +6,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import ScrollTopMask from '../../components/ScrollTopMask'
import { assetUrl } from '../../utils/asset'
export default function WordCloudPage() {
@@ -64,9 +65,10 @@ export default function WordCloudPage() {
return (
<View className={`theme-${resolvedTheme}`}>
<ThemedPageMeta />
<ScrollTopMask title="AI词云生成" targetSelector=".page-header.cloud-header" showBack />
<View className='wordcloud-page'>
<View className='page-header cloud-header' style={{ paddingTop: `${safe.headerPaddingTop}px` }}>
<View className='page-header cloud-header' style={{ paddingTop: `${safe.statusBarHeight + 12}px` }}>
<View className='back-btn' onTap={goBack}>
<Text className='back-arrow'></Text>
</View>