init
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '首页'
|
||||
})
|
||||
navigationBarTitleText: '首页',
|
||||
})
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
.home-page {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding: 20rpx 24rpx 140rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
// ===== 门店信息卡片 =====
|
||||
.store-card {
|
||||
background: linear-gradient(135deg, #ee0a24, #ff4d4f);
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx;
|
||||
color: #fff;
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
flex-shrink: 0;
|
||||
|
||||
&--text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.85;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__identity {
|
||||
margin-top: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
font-size: 24rpx;
|
||||
padding: 6rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
|
||||
&--level {
|
||||
background: #ffd21e;
|
||||
color: #7a5b00;
|
||||
}
|
||||
}
|
||||
|
||||
&__btn {
|
||||
font-size: 26rpx;
|
||||
padding: 12rpx 30rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #fff;
|
||||
color: #ee0a24;
|
||||
font-weight: 600;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 汇总卡片 =====
|
||||
.summary-card {
|
||||
margin-top: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__period {
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__body {
|
||||
margin-top: 20rpx;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #323233;
|
||||
|
||||
&--strong {
|
||||
color: #ee0a24;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 通用区块卡片 =====
|
||||
.section-card {
|
||||
margin-top: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__more {
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
|
||||
&__empty {
|
||||
padding: 40rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__empty-text {
|
||||
font-size: 26rpx;
|
||||
color: #c8c9cc;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 分类横向滚动 =====
|
||||
.category-scroll {
|
||||
white-space: nowrap;
|
||||
margin: 0 -28rpx;
|
||||
padding: 0 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.category-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 14rpx 30rpx;
|
||||
margin-right: 16rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #f7f8fa;
|
||||
border: 1rpx solid #ebedf0;
|
||||
|
||||
&__name {
|
||||
font-size: 26rpx;
|
||||
color: #323233;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 通知 =====
|
||||
.notice-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f2f3f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
background: #ee0a24;
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 28rpx;
|
||||
color: #323233;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
margin-top: 6rpx;
|
||||
font-size: 24rpx;
|
||||
color: #969799;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+229
-27
@@ -1,34 +1,236 @@
|
||||
import { View } from '@tarojs/components'
|
||||
import { Button } from '@antmjs/vantui'
|
||||
import { useCallback, useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
||||
import { Grid, GridItem } from '@antmjs/vantui'
|
||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||
import useCartStore from '@/stores/cart/useCartStore'
|
||||
import { getOrderSummaryApi } from '@/services/order'
|
||||
import { getCategoriesApi } from '@/services/product'
|
||||
import { getNoticeListApi, readNoticeApi } from '@/services/notice'
|
||||
import { resolveAvatarUrl } from '@/utils/format'
|
||||
import type { Category } from '@/types/product'
|
||||
import type { SummaryGroup } from '@/types/order'
|
||||
import type { Notice } from '@/types/notice'
|
||||
import './index.less'
|
||||
|
||||
export default function Index() {
|
||||
export default function IndexPage() {
|
||||
const user = useAuthStore(s => s.user)
|
||||
const token = useAuthStore(s => s.token)
|
||||
const fetchCart = useCartStore(s => s.fetchCart)
|
||||
|
||||
/** 本月订货汇总(取当前月份分组,无则取最近一组) */
|
||||
const [summary, setSummary] = useState<SummaryGroup | null>(null)
|
||||
/** 顶级分类快捷入口 */
|
||||
const [categories, setCategories] = useState<Category[]>([])
|
||||
/** 最新通知 */
|
||||
const [notices, setNotices] = useState<Notice[]>([])
|
||||
const [unreadCount, setUnreadCount] = useState(0)
|
||||
|
||||
const loggedIn = !!token && !!user
|
||||
|
||||
useDidShow(() => {
|
||||
if (!loggedIn) return
|
||||
// 静默同步购物车角标
|
||||
fetchCart().catch(() => {})
|
||||
loadSummary()
|
||||
loadCategories()
|
||||
loadNotices()
|
||||
})
|
||||
|
||||
/** 本月订货汇总 */
|
||||
const loadSummary = useCallback(async () => {
|
||||
try {
|
||||
const res = await getOrderSummaryApi('month')
|
||||
const groups = res.data.groups
|
||||
if (!groups.length) {
|
||||
setSummary(null)
|
||||
return
|
||||
}
|
||||
const now = new Date()
|
||||
const thisMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
|
||||
setSummary(groups.find(g => g.period_label === thisMonth) ?? groups[groups.length - 1])
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
}
|
||||
}, [])
|
||||
|
||||
/** 商品分类(顶级) */
|
||||
const loadCategories = useCallback(async () => {
|
||||
try {
|
||||
const res = await getCategoriesApi()
|
||||
setCategories(res.data.filter(c => c.parent_id === 0))
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
}
|
||||
}, [])
|
||||
|
||||
/** 最新通知 + 未读数 */
|
||||
const loadNotices = useCallback(async () => {
|
||||
try {
|
||||
const res = await getNoticeListApi({ page: 1, pageSize: 5 })
|
||||
setNotices(res.data.data)
|
||||
setUnreadCount(res.data.unread_count)
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
}
|
||||
}, [])
|
||||
|
||||
/** 点击分类 → 商品页(switchTab 无法传参,经本地存储传递) */
|
||||
const handleCategoryTap = useCallback((id: number) => {
|
||||
try {
|
||||
Taro.setStorageSync('product_category_id', id)
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
Taro.switchTab({ url: '/pages/product/index' })
|
||||
}, [])
|
||||
|
||||
/** 点击通知 → 标记已读 */
|
||||
const handleNoticeTap = useCallback(
|
||||
async (notice: Notice) => {
|
||||
if (notice.is_read === 1) return
|
||||
try {
|
||||
await readNoticeApi(notice.id)
|
||||
setNotices(prev => prev.map(n => (n.id === notice.id ? { ...n, is_read: 1 } : n)))
|
||||
setUnreadCount(count => Math.max(0, count - 1))
|
||||
} catch {
|
||||
// 错误已由 request 层 toast
|
||||
}
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const goLogin = useCallback(() => {
|
||||
Taro.navigateTo({ url: '/pages/login/index' })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className='index'>
|
||||
<View><Button type='primary'>Hello world!</Button></View>
|
||||
<View>上面的按钮的颜色已经通过全局主题重写覆盖了,参见src/styles/index.less</View>
|
||||
<View className='home-page'>
|
||||
{/* ========== 门店信息卡片 ========== */}
|
||||
<View className='store-card'>
|
||||
{!loggedIn ? (
|
||||
<View className='store-card__row'>
|
||||
<View className='store-card__info'>
|
||||
<Text className='store-card__name'>欢迎使用订货采购系统</Text>
|
||||
<Text className='store-card__desc'>登录后即可查看门店与价格信息</Text>
|
||||
</View>
|
||||
<View className='store-card__btn' onClick={goLogin}>去登录</View>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View className='store-card__row'>
|
||||
{user?.avatar ? (
|
||||
<Image className='store-card__avatar' src={resolveAvatarUrl(user.avatar)} mode='aspectFill' />
|
||||
) : (
|
||||
<View className='store-card__avatar store-card__avatar--text'>{user?.nickname?.[0] || '用'}</View>
|
||||
)}
|
||||
<View className='store-card__info'>
|
||||
<Text className='store-card__name'>{user?.nickname}</Text>
|
||||
{user?.phone ? (
|
||||
<Text className='store-card__desc'>{user.phone}</Text>
|
||||
) : (
|
||||
<Text className='store-card__desc' onClick={goLogin}>未绑定手机号,点击去绑定</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View className='store-card__identity'>
|
||||
{user?.type === 0 ? (
|
||||
<View className='store-card__btn' onClick={goLogin}>绑定手机号</View>
|
||||
) : user?.store ? (
|
||||
<View className='store-card__tags'>
|
||||
<Text className='store-card__tag'>门店 · {user.store.name}</Text>
|
||||
{user.store.level && <Text className='store-card__tag store-card__tag--level'>{user.store.level.name}</Text>}
|
||||
</View>
|
||||
) : user?.supplier ? (
|
||||
<View className='store-card__tags'>
|
||||
<Text className='store-card__tag'>供应商 · {user.supplier.name}</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='store-card__btn' onClick={goLogin}>绑定身份</View>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* ========== 本月订货汇总 ========== */}
|
||||
<View className='summary-card'>
|
||||
<View className='summary-card__header'>
|
||||
<Text className='summary-card__title'>订货汇总</Text>
|
||||
<Text className='summary-card__period'>{summary?.period_label ?? '—'}</Text>
|
||||
</View>
|
||||
<View className='summary-card__body'>
|
||||
<View className='summary-card__item'>
|
||||
<Text className='summary-card__value summary-card__value--strong'>¥{summary?.total_amount ?? '0.00'}</Text>
|
||||
<Text className='summary-card__label'>订货金额</Text>
|
||||
</View>
|
||||
<View className='summary-card__item'>
|
||||
<Text className='summary-card__value'>{summary?.total_quantity ?? '0.00'}</Text>
|
||||
<Text className='summary-card__label'>订货数量</Text>
|
||||
</View>
|
||||
<View className='summary-card__item'>
|
||||
<Text className='summary-card__value'>{summary?.order_count ?? 0}</Text>
|
||||
<Text className='summary-card__label'>订单笔数</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* ========== 快捷入口 ========== */}
|
||||
<View className='section-card'>
|
||||
<Grid columnNum={4} iconSize={52} border={false}>
|
||||
<GridItem icon='apps-o' text='商品中心' onClick={() => Taro.switchTab({ url: '/pages/product/index' })} />
|
||||
<GridItem icon='shopping-cart-o' text='购物车' onClick={() => Taro.switchTab({ url: '/pages/cart/index' })} />
|
||||
<GridItem icon='orders-o' text='我的订单' onClick={() => Taro.switchTab({ url: '/pages/profile/index' })} />
|
||||
<GridItem icon='balance-list-o' text='对账单' onClick={() => Taro.switchTab({ url: '/pages/profile/index' })} />
|
||||
</Grid>
|
||||
</View>
|
||||
|
||||
{/* ========== 商品分类 ========== */}
|
||||
{categories.length > 0 && (
|
||||
<View className='section-card'>
|
||||
<View className='section-card__header'>
|
||||
<Text className='section-card__title'>商品分类</Text>
|
||||
<Text className='section-card__more' onClick={() => Taro.switchTab({ url: '/pages/product/index' })}>查看全部 ›</Text>
|
||||
</View>
|
||||
<ScrollX items={categories} onTap={handleCategoryTap} />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* ========== 通知 ========== */}
|
||||
<View className='section-card'>
|
||||
<View className='section-card__header'>
|
||||
<Text className='section-card__title'>通知</Text>
|
||||
{unreadCount > 0 && <Text className='section-card__more'>未读 {unreadCount} 条</Text>}
|
||||
</View>
|
||||
{notices.length === 0 ? (
|
||||
<View className='section-card__empty'>
|
||||
<Text className='section-card__empty-text'>暂无通知</Text>
|
||||
</View>
|
||||
) : (
|
||||
notices.map(notice => (
|
||||
<View key={notice.id} className='notice-item' onClick={() => handleNoticeTap(notice)}>
|
||||
{notice.is_read === 0 && <View className='notice-item__dot' />}
|
||||
<View className='notice-item__content'>
|
||||
<Text className='notice-item__title'>{notice.title}</Text>
|
||||
<Text className='notice-item__desc' numberOfLines={1}>{notice.content}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
</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>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
/** 横向滚动的分类入口 */
|
||||
function ScrollX({ items, onTap }: { items: Category[]; onTap: (id: number) => void }) {
|
||||
return (
|
||||
<ScrollView scrollX className='category-scroll'>
|
||||
{items.map(item => (
|
||||
<View key={item.id} className='category-chip' onClick={() => onTap(item.id)}>
|
||||
<Text className='category-chip__name'>{item.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user