首页
This commit is contained in:
@@ -72,3 +72,58 @@ Pages must be registered in `src/app.config.ts` under the `pages` array.
|
|||||||
- `src/app.ts` — class-based App component, renders `this.props.children` (the current page)
|
- `src/app.ts` — class-based App component, renders `this.props.children` (the current page)
|
||||||
- `src/app.config.ts` — defines routes (`pages` array), window config, and app-level settings
|
- `src/app.config.ts` — defines routes (`pages` array), window config, and app-level settings
|
||||||
- `src/index.html` — H5 entry HTML template
|
- `src/index.html` — H5 entry HTML template
|
||||||
|
|
||||||
|
## VantUI Component API Reference (v3.7)
|
||||||
|
|
||||||
|
This project uses `@antmjs/vantui` v3.7. The official docs: https://antmjs.github.io/vantui/main/
|
||||||
|
|
||||||
|
**Always check the actual type definitions** in `node_modules/@antmjs/vantui/types/` when unsure about props. Key API notes for commonly used components:
|
||||||
|
|
||||||
|
### Search
|
||||||
|
- `shape`: `'square'` (default) | `'round'`
|
||||||
|
- `background`: background color string (default `#FFFFFF`)
|
||||||
|
- `onFocus`, `onBlur`, `onChange`, `onSearch`, `onClear`, `onCancel` — event callbacks
|
||||||
|
- `leftIcon`, `rightIcon` — icon name or image URL
|
||||||
|
- `showAction` + `actionText` — right-side action button
|
||||||
|
|
||||||
|
### Swiper (NOT the same as Taro's native Swiper)
|
||||||
|
- `autoPlay` — auto-play interval in ms (0 = disabled). **Not** `autoplay`
|
||||||
|
- `loop` — infinite loop mode. **Not** `circular`
|
||||||
|
- `paginationColor` — indicator dot color. **Not** `indicatorColor`
|
||||||
|
- `paginationVisible` — show/hide pagination dots
|
||||||
|
- `duration` — animation duration (default 500)
|
||||||
|
- `width` / `height` — card dimensions
|
||||||
|
- **No** `indicatorActiveColor` prop — only one `paginationColor`
|
||||||
|
- `onChange` callback: `(currPage: number) => void` (not an event object)
|
||||||
|
|
||||||
|
### SwiperItem
|
||||||
|
- Minimal component, only `className` and `children`. No special props.
|
||||||
|
|
||||||
|
### Grid / GridItem
|
||||||
|
- `Grid`: `columnNum`, `border`, `gutter`, `square`, `clickable`, `center`, `iconSize`
|
||||||
|
- `GridItem`: `icon` (string), `text` (ReactNode), `iconColor`, `dot`, `info`/`badge`, `url` + `linkType`
|
||||||
|
|
||||||
|
### Tabs / Tab (v3.7)
|
||||||
|
- `Tabs`:
|
||||||
|
- `active` — current tab index (number | string)
|
||||||
|
- `sticky` — enable sticky mode
|
||||||
|
- `offsetTop` — sticky offset in px. **Not** `stickyOffsetTop`
|
||||||
|
- `color`, `titleActiveColor`, `titleInactiveColor` — color props
|
||||||
|
- `type`: `'line'` | `'card'`
|
||||||
|
- `swipeable` — enable swipe gesture switching
|
||||||
|
- `animated` — enable tab switch animation
|
||||||
|
- `ellipsis` — text overflow ellipsis (default true)
|
||||||
|
- **No** `lineWidth` or `lineHeight` props
|
||||||
|
- `onChange`: `(e: { detail: { index: number; name?: string; title?: string } }) => void`
|
||||||
|
- `Tab`: `title` (ReactNode), `name` (identifier), `dot`, `info`, `disabled`, `titleStyle`
|
||||||
|
|
||||||
|
### Image (VantUI Image, not Taro's native Image)
|
||||||
|
- `src`, `fit` (`'contain' | 'cover' | 'fill' | 'widthFix' | 'heightFix' | 'none'`), `width`, `height`, `radius`
|
||||||
|
- `round` — circular image (boolean)
|
||||||
|
- `lazyLoad`, `showError`, `showLoading`
|
||||||
|
- `renderLoading`, `renderError` — custom render slots
|
||||||
|
|
||||||
|
### Common VantUI pattern: Taro's native components vs VantUI components
|
||||||
|
- Use `@tarojs/components` for: `View`, `Text`, `ScrollView`, `Image` (native, simpler)
|
||||||
|
- Use `@antmjs/vantui` for: styled UI components (Search, Swiper, Tabs, Grid, Button, etc.)
|
||||||
|
- VantUI component props extend Taro's `ViewProps`, so `onClick`/`onTap` and style props are always available
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
|
@import '@antmjs/vantui/es/style/var.less';
|
||||||
@import '@antmjs/vantui/lib/index.less';
|
@import '@antmjs/vantui/lib/index.less';
|
||||||
|
|
||||||
page {
|
page {
|
||||||
background: @pageBack;
|
background: @page-back;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-family: @base-font-family;
|
font-family: @base-font-family;
|
||||||
color: @black;
|
color: @black;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
.image-grid-card {
|
||||||
|
.card-title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-images {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.grid-image {
|
||||||
|
flex: 1;
|
||||||
|
height: 160px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #969799;
|
||||||
|
|
||||||
|
.meta-source {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-time {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-read {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Article } from '@/types/article'
|
||||||
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
|
import './index.less'
|
||||||
|
|
||||||
|
interface ImageGridCardProps {
|
||||||
|
article: Article
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImageGridCard({ article }: ImageGridCardProps) {
|
||||||
|
const images = article.images.slice(0, 3)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className='article-card image-grid-card'>
|
||||||
|
<View className='card-title'>{article.title}</View>
|
||||||
|
<View className='card-images'>
|
||||||
|
{images.map((img, index) => (
|
||||||
|
<Image
|
||||||
|
key={index}
|
||||||
|
className='grid-image'
|
||||||
|
src={img}
|
||||||
|
mode='aspectFill'
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View className='card-meta'>
|
||||||
|
<Text className='meta-source'>{article.source}</Text>
|
||||||
|
<Text className='meta-time'>{article.time}</Text>
|
||||||
|
<Text className='meta-read'>{article.readCount}阅读</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
.image-text-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.card-thumb {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-excerpt {
|
||||||
|
font-size: 26px;
|
||||||
|
color: #646566;
|
||||||
|
line-height: 1.5;
|
||||||
|
flex: 1;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #969799;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.meta-source {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-time {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-read {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { Article } from '@/types/article'
|
||||||
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
|
import './index.less'
|
||||||
|
|
||||||
|
interface ImageTextCardProps {
|
||||||
|
article: Article
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImageTextCard({ article }: ImageTextCardProps) {
|
||||||
|
return (
|
||||||
|
<View className='article-card image-text-card'>
|
||||||
|
<Image
|
||||||
|
className='card-thumb'
|
||||||
|
src={article.images[0] || ''}
|
||||||
|
mode='aspectFill'
|
||||||
|
/>
|
||||||
|
<View className='card-body'>
|
||||||
|
<View className='card-title'>{article.title}</View>
|
||||||
|
<View className='card-excerpt'>{article.excerpt}</View>
|
||||||
|
<View className='card-meta'>
|
||||||
|
<Text className='meta-source'>{article.source}</Text>
|
||||||
|
<Text className='meta-time'>{article.time}</Text>
|
||||||
|
<Text className='meta-read'>{article.readCount}阅读</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
.text-card {
|
||||||
|
.card-title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a1a1a;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-excerpt {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #646566;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #969799;
|
||||||
|
|
||||||
|
.meta-source {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-time {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-read {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { Article } from '@/types/article'
|
||||||
|
import { View, Text } from '@tarojs/components'
|
||||||
|
import './index.less'
|
||||||
|
|
||||||
|
interface TextCardProps {
|
||||||
|
article: Article
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TextCard({ article }: TextCardProps) {
|
||||||
|
return (
|
||||||
|
<View className='article-card text-card'>
|
||||||
|
<View className='card-title'>{article.title}</View>
|
||||||
|
<View className='card-excerpt'>{article.excerpt}</View>
|
||||||
|
<View className='card-meta'>
|
||||||
|
<Text className='meta-source'>{article.source}</Text>
|
||||||
|
<Text className='meta-time'>{article.time}</Text>
|
||||||
|
<Text className='meta-read'>{article.readCount}阅读</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Article } from '@/types/article'
|
||||||
|
import TextCard from './TextCard'
|
||||||
|
import ImageTextCard from './ImageTextCard'
|
||||||
|
import ImageGridCard from './ImageGridCard'
|
||||||
|
|
||||||
|
interface ArticleCardProps {
|
||||||
|
article: Article
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据文章 type 自动选择对应卡片组件 */
|
||||||
|
export default function ArticleCard({ article }: ArticleCardProps) {
|
||||||
|
switch (article.type) {
|
||||||
|
case 'image-text':
|
||||||
|
return <ImageTextCard article={article} />
|
||||||
|
case 'image-grid':
|
||||||
|
return <ImageGridCard article={article} />
|
||||||
|
case 'text':
|
||||||
|
default:
|
||||||
|
return <TextCard article={article} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TextCard, ImageTextCard, ImageGridCard }
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import type { Article, BannerItem, NavItem } from '@/types/article'
|
||||||
|
|
||||||
|
/** 轮播图数据 */
|
||||||
|
export const banners: BannerItem[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
image: 'https://picsum.photos/seed/banner1/750/350',
|
||||||
|
title: '校园新学期开学通知',
|
||||||
|
link: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
image: 'https://picsum.photos/seed/banner2/750/350',
|
||||||
|
title: '2024年度优秀学生表彰',
|
||||||
|
link: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
image: 'https://picsum.photos/seed/banner3/750/350',
|
||||||
|
title: '校园文化艺术节开幕',
|
||||||
|
link: '',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 导航组数据 (2行 x 5列 = 10项) */
|
||||||
|
export const navItems: NavItem[] = [
|
||||||
|
{ id: '1', label: '课程表', icon: 'orders-o', path: '' },
|
||||||
|
{ id: '2', label: '通知公告', icon: 'bullhorn-o', path: '' },
|
||||||
|
{ id: '3', label: '成绩查询', icon: 'chart-trending-o', path: '' },
|
||||||
|
{ id: '4', label: '作业提交', icon: 'edit', path: '' },
|
||||||
|
{ id: '5', label: '考试安排', icon: 'description', path: '' },
|
||||||
|
{ id: '6', label: '校园活动', icon: 'friends-o', path: '' },
|
||||||
|
{ id: '7', label: '图书馆', icon: 'bookmark-o', path: '' },
|
||||||
|
{ id: '8', label: '一卡通', icon: 'credit-pay', path: '' },
|
||||||
|
{ id: '9', label: '校园地图', icon: 'map-marked', path: '' },
|
||||||
|
{ id: '10', label: '更多服务', icon: 'more-o', path: '' },
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 文章模拟数据 */
|
||||||
|
export const articles: Article[] = [
|
||||||
|
// === 最新 (latest) ===
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: '关于2024年秋季学期选课通知',
|
||||||
|
excerpt: '各位同学,2024年秋季学期选课将于8月25日上午10:00正式开始,请同学们提前查看培养方案,合理规划选课时间...',
|
||||||
|
source: '教务处',
|
||||||
|
time: '2小时前',
|
||||||
|
readCount: 1523,
|
||||||
|
images: [],
|
||||||
|
type: 'text',
|
||||||
|
category: 'latest',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: '校园道路施工临时封闭公告',
|
||||||
|
excerpt: '因校园管网改造工程需要,东门至图书馆路段将于8月20日至9月5日进行封闭施工...',
|
||||||
|
source: '后勤管理处',
|
||||||
|
time: '3小时前',
|
||||||
|
readCount: 892,
|
||||||
|
images: ['https://picsum.photos/seed/news2/200/200'],
|
||||||
|
type: 'image-text',
|
||||||
|
category: 'latest',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: '迎新志愿者招募启动,期待你的加入',
|
||||||
|
excerpt: '新的学期即将开始,为迎接2024级新生入学,现面向全校招募迎新志愿者...',
|
||||||
|
source: '学生工作部',
|
||||||
|
time: '5小时前',
|
||||||
|
readCount: 2341,
|
||||||
|
images: [
|
||||||
|
'https://picsum.photos/seed/vol1/200/200',
|
||||||
|
'https://picsum.photos/seed/vol2/200/200',
|
||||||
|
'https://picsum.photos/seed/vol3/200/200',
|
||||||
|
],
|
||||||
|
type: 'image-grid',
|
||||||
|
category: 'latest',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
title: '关于调整校园班车运行时间的通知',
|
||||||
|
excerpt: '根据师生出行需求调研结果,自8月22日起,校园班车将调整早晚高峰时段发车频次...',
|
||||||
|
source: '后勤管理处',
|
||||||
|
time: '昨天',
|
||||||
|
readCount: 678,
|
||||||
|
images: [],
|
||||||
|
type: 'text',
|
||||||
|
category: 'latest',
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 推荐 (recommend) ===
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
title: '优秀学长分享:大学四年如何规划学习与生活',
|
||||||
|
excerpt: '来自计算机学院的张同学,GPA排名年级前5%,获得多项国家级竞赛奖项,他将分享自己的大学规划经验...',
|
||||||
|
source: '学工在线',
|
||||||
|
time: '1天前',
|
||||||
|
readCount: 4521,
|
||||||
|
images: ['https://picsum.photos/seed/share1/200/200'],
|
||||||
|
type: 'image-text',
|
||||||
|
category: 'recommend',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
title: '图书馆新书上架:2024年暑期推荐阅读书单',
|
||||||
|
excerpt: '图书馆最新上架图书2000余册,涵盖文学、历史、科技、艺术等多个领域,欢迎师生前来借阅...',
|
||||||
|
source: '图书馆',
|
||||||
|
time: '1天前',
|
||||||
|
readCount: 1892,
|
||||||
|
images: [
|
||||||
|
'https://picsum.photos/seed/book1/200/200',
|
||||||
|
'https://picsum.photos/seed/book2/200/200',
|
||||||
|
'https://picsum.photos/seed/book3/200/200',
|
||||||
|
],
|
||||||
|
type: 'image-grid',
|
||||||
|
category: 'recommend',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
title: '考研经验交流会活动预告',
|
||||||
|
excerpt: '为帮助同学们更好地备战考研,学校将举办考研经验交流会,邀请2024届考研成功的学长学姐分享备考心得...',
|
||||||
|
source: '学生会',
|
||||||
|
time: '2天前',
|
||||||
|
readCount: 3156,
|
||||||
|
images: [],
|
||||||
|
type: 'text',
|
||||||
|
category: 'recommend',
|
||||||
|
},
|
||||||
|
|
||||||
|
// === 热点 (hot) ===
|
||||||
|
{
|
||||||
|
id: '8',
|
||||||
|
title: '我校在2024年全国大学生数学建模竞赛中再创佳绩',
|
||||||
|
excerpt: '近日,2024年全国大学生数学建模竞赛结果揭晓,我校参赛队伍共获得国家一等奖3项、国家二等奖7项...',
|
||||||
|
source: '数学学院',
|
||||||
|
time: '1小时前',
|
||||||
|
readCount: 8934,
|
||||||
|
images: [
|
||||||
|
'https://picsum.photos/seed/award1/200/200',
|
||||||
|
'https://picsum.photos/seed/award2/200/200',
|
||||||
|
'https://picsum.photos/seed/award3/200/200',
|
||||||
|
],
|
||||||
|
type: 'image-grid',
|
||||||
|
category: 'hot',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '9',
|
||||||
|
title: '关于2024年国家奖学金评选结果的公示',
|
||||||
|
excerpt: '根据教育部相关文件精神,经学生个人申请、学院推荐、学校评审,现将2024年国家奖学金拟获奖名单予以公示...',
|
||||||
|
source: '学生工作部',
|
||||||
|
time: '3小时前',
|
||||||
|
readCount: 6789,
|
||||||
|
images: [],
|
||||||
|
type: 'text',
|
||||||
|
category: 'hot',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '10',
|
||||||
|
title: '学校食堂推出新菜品,舌尖上的校园美食',
|
||||||
|
excerpt: '新学期伊始,学校各食堂纷纷推出新菜品,从川菜到粤菜、从面食到甜点,满足不同地域同学的口味需求...',
|
||||||
|
source: '后勤管理处',
|
||||||
|
time: '6小时前',
|
||||||
|
readCount: 5678,
|
||||||
|
images: ['https://picsum.photos/seed/food1/200/200'],
|
||||||
|
type: 'image-text',
|
||||||
|
category: 'hot',
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '首页'
|
navigationBarTitleText: '首页',
|
||||||
})
|
navigationStyle: 'custom',
|
||||||
|
enablePullDownRefresh: false,
|
||||||
|
})
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/* ========================================
|
||||||
|
首页样式
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
.home-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 自定义导航栏 (毛玻璃) ========== */
|
||||||
|
.custom-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 24px;
|
||||||
|
|
||||||
|
.navbar-search {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 滚动区域 ========== */
|
||||||
|
.page-scroll {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 轮播图区域 ========== */
|
||||||
|
.swiper-section {
|
||||||
|
padding: 20px 24px 0;
|
||||||
|
|
||||||
|
.banner-swiper {
|
||||||
|
height: 320px;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-card {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.banner-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-title {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 24px 20px;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.5) 100%);
|
||||||
|
border-radius: 0 0 12px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 导航组 ========== */
|
||||||
|
.nav-section {
|
||||||
|
margin: 24px 24px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px 12px 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 标签区域 ========== */
|
||||||
|
.tabs-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 24px 20px;
|
||||||
|
|
||||||
|
.van-tabs__wrap {
|
||||||
|
border-radius: 16px 16px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 文章列表 ========== */
|
||||||
|
.article-list {
|
||||||
|
padding: 0 24px;
|
||||||
|
|
||||||
|
.article-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
padding: 80px 0;
|
||||||
|
font-size: 28px;
|
||||||
|
color: #969799;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部安全距离 */
|
||||||
|
.bottom-safe {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 文章卡片通用样式 ========== */
|
||||||
|
.article-card {
|
||||||
|
.card-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #969799;
|
||||||
|
|
||||||
|
.meta-source {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-time {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-read {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+161
-27
@@ -1,34 +1,168 @@
|
|||||||
import { View } from '@tarojs/components'
|
import { useState, useMemo } from 'react'
|
||||||
import { Button } from '@antmjs/vantui'
|
import Taro from '@tarojs/taro'
|
||||||
|
import { View, ScrollView } from '@tarojs/components'
|
||||||
|
import { Search, Swiper, SwiperItem, Grid, GridItem, Tabs, Tab, Image as VantImage } from '@antmjs/vantui'
|
||||||
|
import ArticleCard from '@/components/ArticleCard'
|
||||||
|
import { banners, navItems, articles } from '@/mock/articles'
|
||||||
|
import type { ArticleCategory } from '@/types/article'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
|
/** 获取状态栏和导航栏高度 */
|
||||||
|
function getNavBarHeight() {
|
||||||
|
const systemInfo = Taro.getSystemInfoSync()
|
||||||
|
const statusBarHeight = systemInfo.statusBarHeight || 20
|
||||||
|
// 微信小程序导航栏高度一般为 44px,总高度 = 状态栏 + 导航栏
|
||||||
|
const navBarHeight = 44
|
||||||
|
return {
|
||||||
|
statusBarHeight,
|
||||||
|
navBarHeight,
|
||||||
|
totalHeight: statusBarHeight + navBarHeight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 分类标签映射 */
|
||||||
|
const TAB_LIST = [
|
||||||
|
{ title: '最新', value: 'latest' as ArticleCategory },
|
||||||
|
{ title: '推荐', value: 'recommend' as ArticleCategory },
|
||||||
|
{ title: '热点', value: 'hot' as ArticleCategory },
|
||||||
|
]
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
const [activeTab, setActiveTab] = useState(0)
|
||||||
|
const navBarHeight = useMemo(() => getNavBarHeight(), [])
|
||||||
|
|
||||||
|
/** 根据当前 tab 筛选文章 */
|
||||||
|
const filteredArticles = useMemo(() => {
|
||||||
|
const category = TAB_LIST[activeTab]?.value || 'latest'
|
||||||
|
return articles.filter((a) => a.category === category)
|
||||||
|
}, [activeTab])
|
||||||
|
|
||||||
|
/** 搜索 */
|
||||||
|
const handleSearch = () => {
|
||||||
|
Taro.navigateTo({ url: '/pages/search/index' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导航项点击 */
|
||||||
|
const handleNavClick = (item: { path: string; label: string }) => {
|
||||||
|
if (item.path) {
|
||||||
|
Taro.navigateTo({ url: item.path })
|
||||||
|
} else {
|
||||||
|
Taro.showToast({ title: `${item.label}即将上线`, icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 文章点击 */
|
||||||
|
const handleArticleClick = (id: string) => {
|
||||||
|
Taro.navigateTo({ url: `/pages/article/index?id=${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='index'>
|
<View className='home-page'>
|
||||||
<View><Button type='primary'>Hello world!</Button></View>
|
{/* ========== 自定义导航栏 (毛玻璃) ========== */}
|
||||||
<View>上面的按钮的颜色已经通过全局主题重写覆盖了,参见src/styles/index.less</View>
|
<View
|
||||||
|
className='custom-navbar'
|
||||||
|
style={{ paddingTop: `${navBarHeight.statusBarHeight}px` }}
|
||||||
|
>
|
||||||
|
<View className='navbar-content' style={{ height: `${navBarHeight.navBarHeight}px` }}>
|
||||||
|
<View className='navbar-search'>
|
||||||
|
<Search
|
||||||
|
placeholder='搜索课程、资讯、活动...'
|
||||||
|
onFocus={handleSearch}
|
||||||
|
shape='round'
|
||||||
|
background='transparent'
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 占位,补偿固定导航栏高度 */}
|
||||||
|
<View style={{ height: `${navBarHeight.totalHeight}px` }} />
|
||||||
|
|
||||||
|
{/* ========== 可滚动内容区 ========== */}
|
||||||
|
<ScrollView
|
||||||
|
className='page-scroll'
|
||||||
|
scrollY
|
||||||
|
enhanced
|
||||||
|
showScrollbar={false}
|
||||||
|
>
|
||||||
|
{/* ========== 卡片轮播图 ========== */}
|
||||||
|
<View className='swiper-section'>
|
||||||
|
<Swiper
|
||||||
|
className='banner-swiper'
|
||||||
|
paginationColor='rgba(255,255,255,0.5)'
|
||||||
|
height={220}
|
||||||
|
autoPlay={3000}
|
||||||
|
duration={500}
|
||||||
|
loop
|
||||||
|
>
|
||||||
|
{banners.map((banner) => (
|
||||||
|
<SwiperItem key={banner.id}>
|
||||||
|
<View className='swiper-card'>
|
||||||
|
<VantImage
|
||||||
|
className='banner-image'
|
||||||
|
src={banner.image}
|
||||||
|
fit='cover'
|
||||||
|
width='100%'
|
||||||
|
height='100%'
|
||||||
|
radius={12}
|
||||||
|
/>
|
||||||
|
<View className='banner-title'>{banner.title}</View>
|
||||||
|
</View>
|
||||||
|
</SwiperItem>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ========== 导航组 2行 x 5列 ========== */}
|
||||||
|
<View className='nav-section'>
|
||||||
|
<Grid columnNum={5} border={false} gutter={8}>
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<GridItem
|
||||||
|
key={item.id}
|
||||||
|
icon={item.icon}
|
||||||
|
text={item.label}
|
||||||
|
onClick={() => handleNavClick(item)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ========== 文章标签导航 (最新 / 推荐 / 热点) ========== */}
|
||||||
|
<View className='tabs-section'>
|
||||||
|
<Tabs
|
||||||
|
active={activeTab}
|
||||||
|
onChange={(e) => setActiveTab(e.detail.index)}
|
||||||
|
sticky
|
||||||
|
offsetTop={navBarHeight.totalHeight}
|
||||||
|
color='#1989fa'
|
||||||
|
titleActiveColor='#1989fa'
|
||||||
|
titleInactiveColor='#646566'
|
||||||
|
>
|
||||||
|
{TAB_LIST.map((tab) => (
|
||||||
|
<Tab key={tab.value} title={tab.title} />
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ========== 文章列表 ========== */}
|
||||||
|
<View className='article-list'>
|
||||||
|
{filteredArticles.map((article) => (
|
||||||
|
<View
|
||||||
|
key={article.id}
|
||||||
|
className='article-item'
|
||||||
|
onClick={() => handleArticleClick(article.id)}
|
||||||
|
>
|
||||||
|
<ArticleCard article={article} />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{filteredArticles.length === 0 && (
|
||||||
|
<View className='empty-tip'>暂无文章</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 底部安全距离 */}
|
||||||
|
<View className='bottom-safe' />
|
||||||
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// export default class Index extends Component {
|
|
||||||
|
|
||||||
// componentWillMount () { }
|
|
||||||
|
|
||||||
// componentDidMount () { }
|
|
||||||
|
|
||||||
// componentWillUnmount () { }
|
|
||||||
|
|
||||||
// componentDidShow () { }
|
|
||||||
|
|
||||||
// componentDidHide () { }
|
|
||||||
|
|
||||||
// render () {
|
|
||||||
// return (
|
|
||||||
// <View className='index'>
|
|
||||||
// <View><Button type='primary'>Hello world!</Button></View>
|
|
||||||
// <View>上面的按钮的颜色已经通过全局主题重写覆盖了,参见src/style/index.less</View>
|
|
||||||
// </View>
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/** 文章卡片类型 */
|
||||||
|
export type ArticleCardType = 'text' | 'image-text' | 'image-grid'
|
||||||
|
|
||||||
|
/** 文章分类 */
|
||||||
|
export type ArticleCategory = 'latest' | 'recommend' | 'hot'
|
||||||
|
|
||||||
|
/** 文章数据接口 */
|
||||||
|
export interface Article {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
excerpt: string
|
||||||
|
source: string
|
||||||
|
time: string
|
||||||
|
readCount: number
|
||||||
|
images: string[]
|
||||||
|
type: ArticleCardType
|
||||||
|
category: ArticleCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导航项 */
|
||||||
|
export interface NavItem {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
icon: string
|
||||||
|
path: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 轮播项 */
|
||||||
|
export interface BannerItem {
|
||||||
|
id: string
|
||||||
|
image: string
|
||||||
|
title: string
|
||||||
|
link: string
|
||||||
|
}
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user