first commit

This commit is contained in:
liu
2026-07-13 14:44:13 +08:00
commit dcd32e9d4d
40 changed files with 12918 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
import { View, Image } from '@tarojs/components'
import { Search, Swiper, SwiperItem, Grid, GridItem } from '@antmjs/vantui'
import { useState, useEffect } from 'react'
import Taro from '@tarojs/taro'
import { getHomeData } from '@/api/home'
import type { CarouselItem, GridNavItem } from '@/api/home'
import './index.less'
// 背景图
import bgImg from '../../../static/img/bg.png'
// ==================== 本地兜底数据 ====================
// ==================== 工具函数 ====================
/** 排序 + 过滤启用的项 */
function prepareItems<T extends { status: number; sort: number }>(items: T[]): T[] {
return items
.filter((item) => item.status === 0)
.sort((a, b) => a.sort - b.sort)
}
/** 处理导航跳转 */
function handleNavigate(link: string): void {
if (!link) return
if(link.startsWith('http')) {
window.location.href = link
} else {
Taro.navigateTo({ url: link }).catch(() => {
Taro.switchTab({ url: link }).catch(() => {
console.warn('导航失败:', link)
})
})
}
}
// ==================== 页面组件 ====================
export default function Index() {
const [searchValue, setSearchValue] = useState('')
const [carousel, setCarousel] = useState<CarouselItem[]>([])
const [gridNav, setGridNav] = useState<GridNavItem[]>([])
const [loading, setLoading] = useState(true)
// 获取首页数据
useEffect(() => {
async function fetchHomeData() {
try {
const res = await getHomeData()
if (res.success && res.data) {
// 轮播图:API 有数据则用,否则兜底
if (res.data.carousel?.length > 0) {
setCarousel(prepareItems(res.data.carousel))
}
// 宫格导航:API 有数据则用,否则兜底
if (res.data.gridNav?.length > 0) {
setGridNav(prepareItems(res.data.gridNav))
}
}
} catch (err) {
console.warn('获取首页数据失败,使用本地兜底数据', err)
} finally {
setLoading(false)
}
}
fetchHomeData()
}, [])
return (
<View className='index'>
{/* 背景图层 */}
<Image className='bg-image' src={bgImg} mode='widthFix' />
{/* 内容层 */}
<View className='index-content'>
{/* 1. 顶部搜索栏 */}
<View className='search-wrapper'>
<Search
value={searchValue}
onChange={(e) => setSearchValue(e.detail)}
shape='round'
placeholder='搜索美食、餐厅...'
background='rgba(255,255,255,0.9)'
onSearch={() => {
// 搜索逻辑
}}
/>
</View>
{/* 2. 中间轮播图 */}
<View className='swiper-wrapper'>
{carousel.length > 0 ? (
<Swiper
autoPlay={3000}
loop
paginationVisible
paginationColor='#ffffff'
duration={500}
height={220}
>
{carousel.map((item) => (
<SwiperItem key={item.id}>
<Image
className='swiper-img'
src={item.image.preview_url}
mode='aspectFill'
onClick={() => handleNavigate(item.link)}
/>
</SwiperItem>
))}
</Swiper>
) : (
<View className='swiper-placeholder'></View>
)}
</View>
{/* 3. 宫格导航 */}
<View className='grid-wrapper'>
{gridNav.length > 0 ? (
<Grid columnNum={5} border={false} gutter={12} iconSize={48}>
{gridNav.map((item) => (
<GridItem
key={item.id}
icon={item.image.preview_url}
text={item.title}
onClick={() => handleNavigate(item.link)}
/>
))}
</Grid>
) : (
<View className='grid-placeholder'></View>
)}
</View>
{/* 4. 底部推荐卡片 */}
<View className='bottom-section'>
<View className='section-card card-hot'>
<View className='card-icon'>🔥</View>
<View className='card-info'>
<View className='card-title'></View>
<View className='card-desc'></View>
</View>
<View className='card-arrow'></View>
</View>
<View className='section-card card-discount'>
<View className='card-icon'>💰</View>
<View className='card-info'>
<View className='card-title'></View>
<View className='card-desc'>9.9</View>
</View>
<View className='card-arrow'></View>
</View>
<View className='section-card card-new'>
<View className='card-icon'></View>
<View className='card-info'>
<View className='card-title'></View>
<View className='card-desc'></View>
</View>
<View className='card-arrow'></View>
</View>
<View className='section-card card-order'>
<View className='card-icon'>📋</View>
<View className='card-info'>
<View className='card-title'></View>
<View className='card-desc'></View>
</View>
<View className='card-arrow'></View>
</View>
</View>
{/* 底部留白 */}
<View className='bottom-spacer' />
</View>
</View>
)
}