Files
xinlong-shop-taro/src/pages/index/index.tsx
赵忠林 c2de1fcd12 fix(search): 修复搜索页商品状态筛选条件错误
- 将搜索页商品筛选条件由 isShow: 1 修改为 status: 0,统一与商城主页规则
- 确认收藏列表和浏览历史不受影响,维护数据一致性

feat(price): 全站商品价格登录后可见脱敏实现

- 将所有硬编码价格替换为支持游客脱敏的 <Price loginMask> 组件
- 覆盖首页热销、收藏列表、秒杀列表、秒杀详情、拼团列表和拼团详情多个页面
- 秒杀详情价格组件支持白色字体,保持视觉效果一致
2026-06-27 21:43:55 +08:00

271 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react'
import { View, Text, ScrollView, Swiper, SwiperItem, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useUser } from '@/hooks/useUser'
import { useScrollHeight } from '@/hooks/useScrollHeight'
import { listCmsAd } from '@/api/cms/cmsAd'
import { listCmsArticle } from '@/api/cms/cmsArticle'
import { pageShopGoods } from '@/api/shop/shopGoods'
import type { CmsAd } from '@/api/cms/cmsAd/model'
import type { CmsArticle } from '@/api/cms/cmsArticle/model'
import type { ShopGoods } from '@/api/shop/shopGoods/model'
import Price from '@/components/common/Price'
definePageConfig({
navigationBarTitleText: '首页',
})
const IndexPage: React.FC = () => {
const { user, isLoggedIn } = useUser()
const [banners, setBanners] = useState<CmsAd[]>([])
const [announcements, setAnnouncements] = useState<CmsArticle[]>([])
const [hotProducts, setHotProducts] = useState<ShopGoods[]>([])
const [loading, setLoading] = useState(true)
const scrollHeight = useScrollHeight(44)
const categories = ['全部', '球拍', '球鞋', '服装', '配件']
// 功能入口8个
const featureEntries = [
{ icon: '🎁', label: '全部商品', url: '/pages/shop/index' },
{ icon: '🎯', label: '限时秒杀', url: '/pages/shop/seckill-list/index' },
{ icon: '🎫', label: '领券中心', url: '/pages/index/coupon-center/index' },
{ icon: '⭐', label: '我的收藏', url: '/pages/user/favorite-list/index' },
{ icon: '👑', label: '会员中心', url: '/pages/user/member/index' },
{ icon: '💰', label: '积分商城', url: '/pages/points/index' },
{ icon: '🏪', label: '门店信息', url: '/pages/store/list/index' },
{ icon: '❓', label: '帮助中心', url: '/pages/user/help-center/index' },
]
// 获取轮播图
useEffect(() => {
const fetchBanners = async () => {
try {
const data = await listCmsAd({ adType: 'banner', status: 0 })
setBanners(data || [])
} catch (e) {
console.error('获取轮播图失败:', e)
}
}
const fetchAnnouncements = async () => {
try {
const data = await listCmsArticle({ category: 'notice', status: 1 })
setAnnouncements(data || [])
} catch (e) {
console.error('获取公告失败:', e)
} finally {
setLoading(false)
}
}
fetchBanners()
fetchAnnouncements()
fetchHotProducts()
}, [])
// 获取热销商品
const fetchHotProducts = async () => {
try {
const res = await pageShopGoods({ page: 1, limit: 4, status: 0, recommend: 1 })
if (res?.list) {
setHotProducts(res.list)
}
} catch (e) {
console.error('获取热销商品失败:', e)
}
}
const handleMessageClick = () => {
if (!isLoggedIn) {
Taro.navigateTo({ url: '/passport/login' })
return
}
Taro.navigateTo({ url: '/pages/index/notification' })
}
// tabBar 页面列表
const tabBarPages = ['/pages/index/index', '/pages/shop/index', '/pages/user/user']
const handleFeatureClick = (url: string) => {
if (tabBarPages.includes(url)) {
Taro.switchTab({ url })
} else {
Taro.navigateTo({ url })
}
}
return (
<View className='min-h-screen bg-gray-50'>
{/* 顶部搜索栏 */}
<View className='flex items-center px-3 py-2 bg-white'>
<View className='flex-1 mx-2'>
<View
className='flex items-center bg-gray-100 rounded-full px-3 py-2'
onClick={() => Taro.navigateTo({ url: '/pages/index/search' })}
>
<Text className='text-gray-400 text-sm mr-2'>🔍</Text>
<Text className='text-gray-400 text-sm flex-1'></Text>
</View>
</View>
<View onClick={handleMessageClick}>
<Text className='text-xl'>🔔</Text>
{isLoggedIn && (
<View className='absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full' />
)}
</View>
</View>
<ScrollView scrollY style={{ height: scrollHeight }}>
{/* 轮播图 */}
<View className='mx-3 mt-2 rounded-lg overflow-hidden'>
{banners.length > 0 ? (
<Swiper
autoplay
interval={3000}
className='rounded-lg'
style={{ height: '160px' }}
>
{banners.map(item => (
<SwiperItem key={item.adId}>
<Image
className='w-full h-full'
src={item.imageList?.[0]?.url || item.image || ''}
mode='aspectFill'
onClick={() => {
if (item.path) Taro.navigateTo({ url: item.path })
}}
/>
</SwiperItem>
))}
</Swiper>
) : (
<View className='w-full bg-green-50 flex items-center justify-center' style={{ height: '160px' }}>
<Text className='text-gray-400 text-sm'></Text>
</View>
)}
</View>
{/* 功能入口网格 */}
<View className='mx-3 mt-3 p-3 bg-white rounded-lg'>
<View className='grid grid-cols-4 gap-2'>
{featureEntries.map(item => (
<View
key={item.label}
className='flex flex-col items-center py-2'
onClick={() => handleFeatureClick(item.url)}
>
<Text className='text-2xl mb-1'>{item.icon}</Text>
<Text className='text-xs text-gray-600'>{item.label}</Text>
</View>
))}
</View>
</View>
{/* 公告栏 */}
{announcements.length > 0 && (
<View className='mx-3 mt-3 px-3 py-2 bg-yellow-50 rounded-lg flex items-center' style={{ display: 'none' }}>
<Text className='text-xs text-yellow-600 font-medium mr-2'></Text>
<Swiper
autoplay
direction='vertical'
interval={3000}
className='flex-1'
style={{ height: '20px' }}
>
{announcements.map(item => (
<SwiperItem key={item.articleId}>
<Text
className='text-xs text-gray-600 truncate'
onClick={() => Taro.navigateTo({ url: `/pages/index/article-detail?id=${item.articleId}` })}
>
{item.title}
</Text>
</SwiperItem>
))}
</Swiper>
</View>
)}
{/* 分类导航 */}
<View className='mx-3 mt-3 p-3 bg-white rounded-lg' style={{ display: 'none' }}>
<Text className='text-base font-medium text-gray-800 mb-3 block'></Text>
<View className='flex justify-around'>
{categories.map((item) => (
<View
key={item}
className='flex flex-col items-center'
onClick={() => Taro.navigateTo({ url: '/pages/shop/category' })}
>
<View className='w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center mb-1'>
<Text className='text-xs text-gray-500'>{item.slice(0, 1)}</Text>
</View>
<Text className='text-xs text-gray-600'>{item}</Text>
</View>
))}
</View>
</View>
{/* 热销推荐 */}
<View className='mx-3 mt-3 mb-4'>
<View className='flex justify-between items-center mb-3'>
<Text className='text-base font-medium text-gray-800 block'></Text>
<Text
className='text-xs text-gray-400 block'
onClick={() => Taro.switchTab({ url: '/pages/shop/index' })}
>
{'>'}
</Text>
</View>
<View className='grid grid-cols-2 gap-2'>
{hotProducts.length > 0 ? (
hotProducts.map((item) => (
<View
key={item.goodsId}
className='bg-white rounded-lg overflow-hidden'
onClick={() => Taro.navigateTo({ url: `/pages/shop/product-detail?id=${item.goodsId}` })}
>
<View className='w-full' style={{ paddingTop: '100%', position: 'relative' }}>
{item.image ? (
<Image
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }}
src={item.image}
mode='aspectFill'
/>
) : (
<View style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} className='bg-gray-100 flex items-center justify-center'>
<Text className='text-xs text-gray-300'></Text>
</View>
)}
</View>
<View className='p-2'>
<Text className='text-sm text-gray-700 truncate block'>{item.goodsName || item.name}</Text>
<View className='flex items-center mt-1'>
<Price
price={item.price || '0'}
original={item.salePrice && item.salePrice !== item.price ? item.salePrice : undefined}
size='small'
loginMask
/>
</View>
{item.sales !== undefined && item.sales > 0 && (
<Text className='text-xs text-gray-400 mt-1'> {item.sales}</Text>
)}
</View>
</View>
))
) : (
<View className='col-span-2 py-8 text-center'>
<Text className='text-sm text-gray-400'></Text>
</View>
)}
</View>
</View>
</ScrollView>
</View>
)
}
export default IndexPage