首页优化
This commit is contained in:
+135
-73
@@ -1,46 +1,68 @@
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import { Grid, GridItem, Search, Swiper, SwiperItem } from '@antmjs/vantui'
|
||||
import { Grid, GridItem, Icon, Search, Swiper, SwiperItem } from '@antmjs/vantui'
|
||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||
import useCartStore from '@/stores/cart/useCartStore'
|
||||
import { getCategoriesApi, getProductListApi } from '@/services/product'
|
||||
import { getHomeConfigApi } from '@/services/home'
|
||||
import type { HomeConfig } from '@/services/home'
|
||||
import { getProductListApi } from '@/services/product'
|
||||
import { getProductCover } from '@/types/product'
|
||||
import type { Category, Product } from '@/types/product'
|
||||
import type { Product } from '@/types/product'
|
||||
import './index.less'
|
||||
|
||||
/** 首页 → 商品页的本地存储传参 key(switchTab 无法带参) */
|
||||
const PENDING_CATEGORY_KEY = 'product_category_id'
|
||||
const PENDING_KEYWORD_KEY = 'product_keyword'
|
||||
|
||||
/** 分类菜单色块配色 */
|
||||
const MENU_COLORS = ['#ee0a24', '#ff9f43', '#07c160', '#1989fa', '#8a5cf6', '#00b8d9']
|
||||
/** tabBar 页面路径(link 跳转需改用 switchTab) */
|
||||
const TAB_PATHS = [
|
||||
'pages/index/index',
|
||||
'pages/product/index',
|
||||
'pages/cart/index',
|
||||
'pages/message/index',
|
||||
'pages/profile/index',
|
||||
]
|
||||
|
||||
/** 宫格导航无图时的兜底色块配色(生鲜红橙系) */
|
||||
const NAV_COLORS = ['#ee0a24', '#ff7a1a', '#07c160', '#1989fa', '#8a5cf6', '#ff976a', '#00b8d9', '#f56c6c']
|
||||
|
||||
/** 获取状态栏高度(H5 端为 0) */
|
||||
function getStatusBarHeight(): number {
|
||||
try {
|
||||
const info = typeof Taro.getWindowInfo === 'function' ? Taro.getWindowInfo() : Taro.getSystemInfoSync()
|
||||
return info.statusBarHeight || 0
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export default function IndexPage() {
|
||||
const token = useAuthStore(s => s.token)
|
||||
const addItem = useCartStore(s => s.addItem)
|
||||
|
||||
/** 顶级分类(导航菜单前 6 个) */
|
||||
const [categories, setCategories] = useState<Category[]>([])
|
||||
/** 首页配置(轮播图 / 宫格导航 / 促销卡片) */
|
||||
const [config, setConfig] = useState<HomeConfig>({ banners: [], navs: [], promos: [] })
|
||||
/** 推荐商品 */
|
||||
const [products, setProducts] = useState<Product[]>([])
|
||||
/** 搜索框输入 */
|
||||
const [keyword, setKeyword] = useState('')
|
||||
|
||||
const loggedIn = !!token
|
||||
const statusBarHeight = useMemo(() => getStatusBarHeight(), [])
|
||||
|
||||
useDidShow(() => {
|
||||
loadCategories()
|
||||
if (loggedIn) {
|
||||
loadHomeConfig()
|
||||
loadRecommend()
|
||||
}
|
||||
})
|
||||
|
||||
/** 商品分类 */
|
||||
const loadCategories = useCallback(async () => {
|
||||
/** 首页配置聚合数据(需登录) */
|
||||
const loadHomeConfig = useCallback(async () => {
|
||||
try {
|
||||
const res = await getCategoriesApi()
|
||||
setCategories(res.data.filter(c => c.parent_id === 0))
|
||||
const res = await getHomeConfigApi()
|
||||
setConfig(res.data)
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
}
|
||||
@@ -56,17 +78,26 @@ export default function IndexPage() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
/** 轮播数据:取有图商品的前 4 张图 */
|
||||
const bannerImages = useMemo(
|
||||
() => products.filter(p => getProductCover(p)).slice(0, 4),
|
||||
[products],
|
||||
)
|
||||
/**
|
||||
* 后台配置的 link 统一跳转:
|
||||
* - 空字符串不跳转
|
||||
* - tabBar 页面用 switchTab,其余用 navigateTo
|
||||
*/
|
||||
const handleLink = useCallback((link: string) => {
|
||||
if (!link) return
|
||||
const path = link.split('?')[0].replace(/^\//, '')
|
||||
if (TAB_PATHS.includes(path)) {
|
||||
Taro.switchTab({ url: `/${path}` })
|
||||
} else {
|
||||
Taro.navigateTo({ url: link })
|
||||
}
|
||||
}, [])
|
||||
|
||||
/** 跳转商品页并带上分类/关键词 */
|
||||
const goProduct = useCallback((categoryId?: number, kw?: string) => {
|
||||
/** 跳转商品页并带上关键词 */
|
||||
const goProduct = useCallback((kw?: string) => {
|
||||
try {
|
||||
if (categoryId !== undefined) Taro.setStorageSync(PENDING_CATEGORY_KEY, categoryId)
|
||||
if (kw !== undefined) Taro.setStorageSync(PENDING_KEYWORD_KEY, kw)
|
||||
Taro.removeStorageSync(PENDING_CATEGORY_KEY)
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
@@ -75,7 +106,7 @@ export default function IndexPage() {
|
||||
|
||||
/** 搜索框聚焦/提交 → 商品页搜索 */
|
||||
const handleSearchFocus = useCallback(() => {
|
||||
goProduct(undefined, keyword.trim())
|
||||
goProduct(keyword.trim())
|
||||
}, [goProduct, keyword])
|
||||
|
||||
/** 快捷加购 */
|
||||
@@ -94,82 +125,113 @@ export default function IndexPage() {
|
||||
|
||||
return (
|
||||
<View className='home-page'>
|
||||
{/* ========== 顶部搜索 ========== */}
|
||||
<View className='home-search'>
|
||||
<Search
|
||||
value={keyword}
|
||||
placeholder='搜索商品名称/规格'
|
||||
shape='round'
|
||||
background='transparent'
|
||||
onChange={e => setKeyword(String(e.detail))}
|
||||
onFocus={handleSearchFocus}
|
||||
onSearch={handleSearchFocus}
|
||||
/>
|
||||
{/* ========== 自定义顶部导航栏 ========== */}
|
||||
<View className='home-header' style={{ paddingTop: `${statusBarHeight}px` }}>
|
||||
<View className='home-header__bar'>
|
||||
<View className='home-header__brand'>
|
||||
<Text className='home-header__title'>鲜采直供</Text>
|
||||
<Text className='home-header__slogan'>产地直采 · 新鲜直达</Text>
|
||||
</View>
|
||||
<View className='home-header__notice' onClick={() => Taro.switchTab({ url: '/pages/message/index' })}>
|
||||
<Icon name='bell' size='44rpx' color='#ffffff' />
|
||||
</View>
|
||||
</View>
|
||||
<View className='home-header__search'>
|
||||
<Search
|
||||
value={keyword}
|
||||
placeholder='搜索商品名称/规格'
|
||||
shape='round'
|
||||
background='transparent'
|
||||
onChange={e => setKeyword(String(e.detail))}
|
||||
onFocus={handleSearchFocus}
|
||||
onSearch={handleSearchFocus}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ========== 轮播图 ========== */}
|
||||
<View className='home-banner'>
|
||||
<Swiper
|
||||
className='home-banner__swiper'
|
||||
height='320rpx'
|
||||
height='300rpx'
|
||||
autoPlay={3000}
|
||||
loop
|
||||
paginationVisible
|
||||
paginationColor='#ffffff'
|
||||
>
|
||||
{bannerImages.length > 0 ? (
|
||||
bannerImages.map((product, idx) => (
|
||||
<SwiperItem key={product.id}>
|
||||
<Image
|
||||
className='home-banner__image'
|
||||
src={getProductCover(product)}
|
||||
mode='aspectFill'
|
||||
onClick={() => goProduct()}
|
||||
/>
|
||||
</SwiperItem>
|
||||
))
|
||||
{config.banners.length > 0 ? (
|
||||
config.banners.map(banner =>
|
||||
banner.image_url ? (
|
||||
<SwiperItem key={banner.id}>
|
||||
<Image
|
||||
className='home-banner__image'
|
||||
src={banner.image_url}
|
||||
mode='aspectFill'
|
||||
onClick={() => handleLink(banner.link)}
|
||||
/>
|
||||
</SwiperItem>
|
||||
) : null,
|
||||
)
|
||||
) : (
|
||||
<SwiperItem>
|
||||
<View className='home-banner__placeholder' onClick={() => goProduct()}>
|
||||
<Text className='home-banner__placeholder-text'>订货采购</Text>
|
||||
<View className='home-banner__placeholder'>
|
||||
<Text className='home-banner__placeholder-title'>新鲜直采</Text>
|
||||
<Text className='home-banner__placeholder-sub'>每日凌晨采货 · 天亮前到店</Text>
|
||||
</View>
|
||||
</SwiperItem>
|
||||
)}
|
||||
</Swiper>
|
||||
</View>
|
||||
|
||||
{/* ========== 导航菜单 ========== */}
|
||||
<View className='home-menu'>
|
||||
<Grid columnNum={4} border={false} iconSize={52}>
|
||||
{categories.slice(0, 6).map((cat, idx) => (
|
||||
<GridItem
|
||||
key={cat.id}
|
||||
text={cat.name}
|
||||
onClick={() => goProduct(cat.id)}
|
||||
renderIcon={
|
||||
<View className='menu-icon' style={{ background: MENU_COLORS[idx % MENU_COLORS.length] }}>
|
||||
<Text className='menu-icon__text'>{cat.name.slice(0, 1)}</Text>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
{/* ========== 宫格导航(一行四个) ========== */}
|
||||
{config.navs.length > 0 && (
|
||||
<View className='home-menu'>
|
||||
<Grid columnNum={4} border={false} iconSize={88}>
|
||||
{config.navs.map((nav, idx) => (
|
||||
<GridItem
|
||||
key={nav.id}
|
||||
text={nav.name}
|
||||
onClick={() => handleLink(nav.link)}
|
||||
renderIcon={
|
||||
nav.image_url ? (
|
||||
<Image className='menu-icon__image' src={nav.image_url} mode='aspectFit' />
|
||||
) : (
|
||||
<View className='menu-icon' style={{ background: NAV_COLORS[idx % NAV_COLORS.length] }}>
|
||||
<Text className='menu-icon__text'>{nav.name.slice(0, 1)}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ========== 促销推荐卡片 ========== */}
|
||||
{config.promos.length > 0 && (
|
||||
<View className='home-promo'>
|
||||
{config.promos.map(promo => (
|
||||
<View key={promo.id} className='promo-card' onClick={() => handleLink(promo.link)}>
|
||||
{promo.image_url && (
|
||||
<Image className='promo-card__bg' src={promo.image_url} mode='aspectFill' lazyLoad />
|
||||
)}
|
||||
{promo.title && <View className='promo-card__mask' />}
|
||||
<View className='promo-card__content'>
|
||||
<Text className='promo-card__title'>{promo.title}</Text>
|
||||
{promo.sub_title && <Text className='promo-card__sub'>{promo.sub_title}</Text>}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
<GridItem
|
||||
icon='apps-o'
|
||||
text='全部商品'
|
||||
onClick={() => goProduct()}
|
||||
/>
|
||||
<GridItem
|
||||
icon='orders-o'
|
||||
text='我的订单'
|
||||
onClick={() => Taro.navigateTo({ url: '/pages/order-list/index?status=all' })}
|
||||
/>
|
||||
</Grid>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ========== 推荐商品 ========== */}
|
||||
<View className='home-recommend'>
|
||||
<View className='home-recommend__header'>
|
||||
<Text className='home-recommend__title'>推荐商品</Text>
|
||||
<View className='home-recommend__title-wrap'>
|
||||
<View className='home-recommend__title-bar' />
|
||||
<Text className='home-recommend__title'>推荐商品</Text>
|
||||
</View>
|
||||
<Text className='home-recommend__more' onClick={() => goProduct()}>查看更多 ›</Text>
|
||||
</View>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user