diff --git a/src/api/home.ts b/src/api/home.ts index 88899f8..ef5f1d8 100644 --- a/src/api/home.ts +++ b/src/api/home.ts @@ -10,33 +10,34 @@ export interface ApiImage { id: number } -/** 轮播图项 */ -export interface CarouselItem { +/** 首页可点击链接项(轮播图 / 宫格导航共用) */ +export interface HomeLinkItem { id: number title: string image: ApiImage link: string status: number // 0: 启用 sort: number + link_type: number // 0: 跳转 link 链接; 1: 跳转分类页 + category_id: number // link_type = 1 时携带的分类 ID } -/** 宫格导航项 */ -export interface GridNavItem { - id: number - title: string - image: ApiImage - link: string - status: number // 0: 启用 - sort: number +/** 首页活动项(结构与 HomeLinkItem 相同,额外多一个描述字段) */ +export interface HomeActivityItem extends HomeLinkItem { + description: string } /** 首页数据 */ export interface HomeData { - carousel: CarouselItem[] - gridNav: GridNavItem[] + carousel: HomeLinkItem[] + gridNav: HomeLinkItem[] + activity: HomeActivityItem[] site_title: string } +/** 分类链接项(结构与 HomeLinkItem 相同,但不含 link_type / category_id,均为纯链接) */ +export type CategoryLinkItem = Omit + // ==================== API ==================== /** @@ -48,3 +49,14 @@ export async function getHomeData(): Promise> { method: 'GET', }) } + +/** + * 获取指定分类下的链接列表(无需登录) + * @param categoryId - 分类 ID + */ +export async function getCategoryLinks(categoryId: number): Promise> { + return createRequest({ + url: `/api/category/${categoryId}`, + method: 'GET', + }) +} diff --git a/src/app.config.ts b/src/app.config.ts index b4dc6ac..6f5e9f7 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -1,6 +1,7 @@ export default defineAppConfig({ pages: [ 'pages/index/index', + 'pages/category/index', ], window: { backgroundTextStyle: 'light', diff --git a/src/app.ts b/src/app.ts index 3c32fab..f77b176 100644 --- a/src/app.ts +++ b/src/app.ts @@ -13,6 +13,7 @@ class App extends Component { // this.props.children 是将要会渲染的页面 render () { + // @ts-ignore return this.props.children } } diff --git a/src/pages/category/index.config.ts b/src/pages/category/index.config.ts new file mode 100644 index 0000000..c4a69e5 --- /dev/null +++ b/src/pages/category/index.config.ts @@ -0,0 +1,5 @@ +export default definePageConfig({ + navigationBarTitleText: '校园美食', + navigationBarBackgroundColor: '#ff6b35', + navigationBarTextStyle: 'white', +}) \ No newline at end of file diff --git a/src/pages/category/index.less b/src/pages/category/index.less new file mode 100644 index 0000000..ef7bf79 --- /dev/null +++ b/src/pages/category/index.less @@ -0,0 +1,43 @@ +.category { + min-height: 100vh; + background: #f7f8fa; + padding-top: 16px; + + // 加载 / 空状态 + .category-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 120px 0; + color: #999; + + .state-text { + font-size: 26px; + color: #999; + } + } + + // 宫格链接(与首页宫格导航样式一致) + .grid-wrapper { + margin: 16px 32px 20px; + background: rgb(255 255 255); + border-radius: 20px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); + padding-bottom: 24px; + + .van-grid-item__content { + padding-top: 24px; + padding-bottom: 0; + } + + .van-icon--image { + width: 60px !important; + height: 60px !important; + } + + .van-grid-item__text { + font-size: 22px; + } + } +} diff --git a/src/pages/category/index.tsx b/src/pages/category/index.tsx new file mode 100644 index 0000000..51970a7 --- /dev/null +++ b/src/pages/category/index.tsx @@ -0,0 +1,71 @@ +import { View, Text } from '@tarojs/components' +import { Grid, GridItem, Loading } from '@antmjs/vantui' +import { useState, useEffect } from 'react' +import Taro from '@tarojs/taro' +import { getCategoryLinks } from '@/api/home' +import type { CategoryLinkItem } from '@/api/home' +import { navigateToLink } from '@/utils/navigate' +import './index.less' + +export default function Category() { + const router = Taro.useRouter() + const categoryId = router.params.category_id || '' + const [links, setLinks] = useState([]) + const [loading, setLoading] = useState(true) + + // 收到分类 ID 后请求该分类下的所有链接 + useEffect(() => { + async function fetchCategoryLinks() { + try { + const res = await getCategoryLinks(Number(categoryId)) + if (res.success && res.data) { + setLinks( + res.data + .filter((item) => item.status === 0) + .sort((a, b) => a.sort - b.sort) + ) + } + } catch (err) { + console.warn('获取分类链接失败', err) + } finally { + setLoading(false) + } + } + + if (categoryId) { + fetchCategoryLinks() + } else { + setLoading(false) + } + }, [categoryId]) + + return ( + + {/* 加载中 */} + {loading ? ( + + 加载中... + + ) : links.length > 0 ? ( + // 宫格链接(与首页宫格导航样式一致) + + + {links.map((item) => ( + navigateToLink(item.link)} + /> + ))} + + + ) : ( + // 空状态 + + 暂无链接 + + )} + + ) +} diff --git a/src/pages/index/index.less b/src/pages/index/index.less index 995e6bf..0d72ffd 100644 --- a/src/pages/index/index.less +++ b/src/pages/index/index.less @@ -127,6 +127,11 @@ .activity-cards { display: flex; gap: 20px; + + // 只有一条活动时占满整行 + &.single .activity-card { + flex: 1 1 100%; + } } .activity-card { @@ -184,6 +189,12 @@ border-radius: 16px; background: rgba(255, 255, 255, 0.25); margin-bottom: 14px; + overflow: hidden; + + .activity-icon-img { + width: 100%; + height: 100%; + } } .activity-name { @@ -219,58 +230,79 @@ } // ========== 5. 底部推荐卡片 ========== - .bottom-section { - padding: 0 32px; + // section-card 通用样式(活动专区 + 底部推荐共用) + .section-card { + display: flex; + align-items: center; + background: rgba(255, 255, 255, 0.9); + border-radius: 18px; + padding: 24px 28px; + margin-bottom: 16px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); + transition: transform 0.2s ease; - .section-card { + &:active { + transform: scale(0.98); + } + + .card-icon { + font-size: 48px; + width: 72px; + height: 72px; display: flex; align-items: center; - background: rgba(255, 255, 255, 0.9); + justify-content: center; border-radius: 18px; - padding: 24px 28px; - margin-bottom: 16px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06); - transition: transform 0.2s ease; + margin-right: 20px; + flex-shrink: 0; + overflow: hidden; - &:active { - transform: scale(0.98); - } - - .card-icon { - font-size: 48px; - width: 72px; - height: 72px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 18px; - margin-right: 20px; - flex-shrink: 0; - } - - .card-info { - flex: 1; - - .card-title { - font-size: 30px; - font-weight: 600; - color: #333; - margin-bottom: 6px; - } - - .card-desc { - font-size: 24px; - color: #999; - } - } - - .card-arrow { - font-size: 40px; - color: #ccc; - flex-shrink: 0; + .card-icon-img { + width: 100%; + height: 100%; } } + .card-info { + flex: 1; + + .card-title { + font-size: 30px; + font-weight: 600; + color: #333; + margin-bottom: 6px; + } + + .card-desc { + font-size: 24px; + color: #999; + } + } + + .card-arrow { + font-size: 40px; + color: #ccc; + flex-shrink: 0; + } + } + + // 活动专区 section-card 图标底色(循环使用) + .card-ic-0 .card-icon { + background: #fff0ed; + } + .card-ic-1 .card-icon { + background: #fff7e6; + } + .card-ic-2 .card-icon { + background: #e6f7ff; + } + .card-ic-3 .card-icon { + background: #f0edff; + } + + .bottom-section { + padding: 0 32px; + // 每张卡片图标不同底色 .card-hot .card-icon { background: #fff0ed; diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index d8e7301..ff28260 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -3,7 +3,8 @@ import {Search, Swiper, SwiperItem, Grid, GridItem, Icon} 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 type { HomeLinkItem, HomeActivityItem } from '@/api/home' +import { navigateToLink } from '@/utils/navigate' import './index.less' // 背景图 @@ -21,25 +22,23 @@ function prepareItems(items: T[]): T } /** 处理导航跳转 */ -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) - }) - }) +function handleNavigate(item: HomeLinkItem): void { + // link_type = 1:跳转分类页,携带分类 ID + if (item.link_type === 1) { + Taro.navigateTo({ url: `/pages/category/index?category_id=${item.category_id ?? ''}` }) + return } + // link_type = 0:直接跳转 link 链接 + navigateToLink(item.link) } // ==================== 页面组件 ==================== export default function Index() { const [searchValue, setSearchValue] = useState('') - const [carousel, setCarousel] = useState([]) - const [gridNav, setGridNav] = useState([]) + const [carousel, setCarousel] = useState([]) + const [gridNav, setGridNav] = useState([]) + const [activity, setActivity] = useState([]) const [siteTitle, setSiteTitle] = useState('河南省工业大学') const [loading, setLoading] = useState(true) @@ -57,6 +56,10 @@ export default function Index() { if (res.data.gridNav?.length > 0) { setGridNav(prepareItems(res.data.gridNav)) } + // 活动专区:API 有数据则用,否则兜底静态内容 + if (res.data.activity?.length > 0) { + setActivity(prepareItems(res.data.activity)) + } if (res.data.site_title) { setSiteTitle(res.data.site_title) } @@ -116,7 +119,7 @@ export default function Index() { className='swiper-img' src={item.image.preview_url} mode='aspectFill' - onClick={() => handleNavigate(item.link)} + onClick={() => handleNavigate(item)} /> ))} @@ -135,7 +138,7 @@ export default function Index() { key={item.id} icon={item.image.preview_url} text={item.title} - onClick={() => handleNavigate(item.link)} + onClick={() => handleNavigate(item)} /> ))} @@ -157,39 +160,83 @@ export default function Index() { - - {/* 满减优惠 */} - { - // TODO: 活动页面上线后配置跳转链接 - }} - > - - - - 🎫 - 满减优惠 - 满30减5 · 可与券叠加 + {activity.length > 0 ? ( + <> + {/* 前两个:activity-card 渐变卡片 */} + + {activity.slice(0, 2).map((item, index) => ( + handleNavigate(item)} + > + + + + + + + {item.title} + {item.description} + + + ))} - - {/* 新品首发 */} - { - // TODO: 活动页面上线后配置跳转链接 - }} - > - - - - - 新品首发 - 每周三上新 · 限时尝鲜价 + {/* 其余:section-card 样式 */} + {activity.slice(2).map((item, index) => ( + handleNavigate(item)} + > + + + + + {item.title} + {item.description} + + + + ))} + + ) : ( + /* 兜底静态内容 */ + + {/* 满减优惠 */} + { + // TODO: 活动页面上线后配置跳转链接 + }} + > + + + + 🎫 + 满减优惠 + 满30减5 · 可与券叠加 + + + + {/* 新品首发 */} + { + // TODO: 活动页面上线后配置跳转链接 + }} + > + + + + + 新品首发 + 每周三上新 · 限时尝鲜价 + - + )} {/* 5. 底部推荐卡片 */} diff --git a/src/utils/navigate.ts b/src/utils/navigate.ts new file mode 100644 index 0000000..fafb95d --- /dev/null +++ b/src/utils/navigate.ts @@ -0,0 +1,18 @@ +import Taro from '@tarojs/taro' + +/** + * 跳转链接:外部 http(s) 链接直接打开,内部链接 navigateTo 打开, + * navigateTo 失败时降级为 switchTab(兼容 tabBar 页面) + */ +export function navigateToLink(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) + }) + }) + } +}