chore(config): 更新项目名称为 xinlong-taro
- 修改 config/index.ts 中的 projectName 字段 - 更新 package.json 的 name 字段 - 调整 project.config.json 中 description 和 projectname 为新名称 - 修改 README.md 中项目目录名称为 xinlong-taro
This commit is contained in:
@@ -1,54 +1,83 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { View, Text, ScrollView, Image } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory'
|
||||
import { pageShopGoods } from '@/api/shop/shopGoods'
|
||||
import type { ShopGoodsCategory, ShopGoods } from '@/api/shop/shopGoodsCategory/model'
|
||||
import type { ShopGoodsParam } from '@/api/shop/shopGoods/model'
|
||||
import ProductCard from '@/components/common/ProductCard'
|
||||
import { addToCart } from '@/api/shop/shopCart'
|
||||
import type { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model'
|
||||
import type { ShopGoods, ShopGoodsParam } from '@/api/shop/shopGoods/model'
|
||||
import { isGuest } from '@/utils/auth'
|
||||
import { isVipMember } from '@/utils/vip'
|
||||
import { requireLogin } from '@/utils/login-guard'
|
||||
import EmptyState from '@/components/common/EmptyState'
|
||||
import LoadMore from '@/components/common/LoadMore'
|
||||
|
||||
const categories = [
|
||||
definePageConfig({
|
||||
navigationBarTitleText: '商品分类',
|
||||
})
|
||||
|
||||
// 预置分类
|
||||
const PRESET_CATEGORIES = [
|
||||
{ id: 0, title: '全部' },
|
||||
{ id: -1, title: '推荐' },
|
||||
]
|
||||
|
||||
// ─── 排序选项 ──────────────────────────────────────────────────
|
||||
type SortKey = 'default' | 'sales' | 'newest' | 'price'
|
||||
|
||||
const SORT_TABS: { key: SortKey; label: string }[] = [
|
||||
{ key: 'default', label: '默认' },
|
||||
{ key: 'sales', label: '销量' },
|
||||
{ key: 'newest', label: '最新' },
|
||||
{ key: 'price', label: '价格' },
|
||||
]
|
||||
|
||||
const ShopPage: React.FC = () => {
|
||||
const [categoryList, setCategoryList] = useState<ShopGoodsCategory[]>(categories)
|
||||
const [categoryList, setCategoryList] = useState<any[]>(PRESET_CATEGORIES)
|
||||
const [activeCategory, setActiveCategory] = useState(0)
|
||||
|
||||
// 商品列表
|
||||
const [goodsList, setGoodsList] = useState<ShopGoods[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
const [finished, setFinished] = useState(false)
|
||||
const loadingRef = useRef(false)
|
||||
|
||||
// 排序
|
||||
const [sortKey, setSortKey] = useState<SortKey>('default')
|
||||
const [priceAsc, setPriceAsc] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
loadCategories()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1)
|
||||
setFinished(false)
|
||||
setGoodsList([])
|
||||
loadGoods(1, activeCategory)
|
||||
}, [activeCategory])
|
||||
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
const list = await listShopGoodsCategory()
|
||||
if (list && list.length > 0) {
|
||||
setCategoryList([...categories, ...list])
|
||||
const filtered = list.filter((c: any) => c.status === 1)
|
||||
setCategoryList([...PRESET_CATEGORIES, ...filtered])
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
const loadGoods = async (p: number, categoryId?: number) => {
|
||||
if (loading) return
|
||||
/** 构建排序参数 */
|
||||
const buildSortParams = useCallback((): Partial<ShopGoodsParam> => {
|
||||
if (sortKey === 'sales') return { sort: 'sales', order: 'desc' }
|
||||
if (sortKey === 'newest') return { sort: 'create_time', order: 'desc' }
|
||||
if (sortKey === 'price') return { sort: 'price', order: priceAsc ? 'asc' : 'desc' }
|
||||
return {}
|
||||
}, [sortKey, priceAsc])
|
||||
|
||||
/** 加载商品列表 */
|
||||
const loadGoods = useCallback(async (p: number) => {
|
||||
if (loadingRef.current) return
|
||||
loadingRef.current = true
|
||||
setLoading(true)
|
||||
try {
|
||||
const params: ShopGoodsParam = { page: p, limit: 10, status: 0 }
|
||||
if (categoryId && categoryId > 0) params.categoryId = categoryId
|
||||
if (categoryId === -1) params.recommend = 1
|
||||
const params: ShopGoodsParam = { page: p, limit: 10, status: 0, ...buildSortParams() }
|
||||
if (activeCategory > 0) params.categoryId = activeCategory
|
||||
if (activeCategory === -1) params.recommend = 1
|
||||
const res = await pageShopGoods(params)
|
||||
const newList = res?.list || []
|
||||
if (p === 1) {
|
||||
@@ -59,19 +88,67 @@ const ShopPage: React.FC = () => {
|
||||
setFinished(newList.length < 10)
|
||||
setPage(p)
|
||||
} catch { /* ignore */ }
|
||||
loadingRef.current = false
|
||||
setLoading(false)
|
||||
}
|
||||
}, [activeCategory, buildSortParams])
|
||||
|
||||
// 分类或排序变化时重新加载
|
||||
useEffect(() => {
|
||||
setGoodsList([])
|
||||
setFinished(false)
|
||||
setPage(1)
|
||||
loadGoods(1)
|
||||
}, [activeCategory, sortKey, priceAsc])
|
||||
|
||||
const handleLoadMore = () => {
|
||||
if (!finished && !loading) {
|
||||
loadGoods(page + 1, activeCategory)
|
||||
loadGoods(page + 1)
|
||||
}
|
||||
}
|
||||
|
||||
/** 排序切换 */
|
||||
const handleSortChange = (key: SortKey) => {
|
||||
if (key === 'price') {
|
||||
if (sortKey === 'price') {
|
||||
setPriceAsc(!priceAsc)
|
||||
} else {
|
||||
setSortKey('price')
|
||||
setPriceAsc(true)
|
||||
}
|
||||
} else {
|
||||
setSortKey(key)
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取显示价格 */
|
||||
const getDisplayPrice = (product: ShopGoods) => {
|
||||
if (isVipMember() && product.dealerPrice) return product.dealerPrice
|
||||
return product.price || '0'
|
||||
}
|
||||
|
||||
/** 加入购物车 */
|
||||
const handleAddToCart = (e: any, product: ShopGoods) => {
|
||||
e.stopPropagation()
|
||||
if (!requireLogin({ action: 'addToCart' })) return
|
||||
addToCart({
|
||||
goodsId: product.goodsId!,
|
||||
num: product.step || 1,
|
||||
}).then(() => {
|
||||
Taro.showToast({ title: '已加入购物车', icon: 'success' })
|
||||
}).catch(err => {
|
||||
Taro.showToast({ title: err.message || '添加失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
/** 跳转商品详情 */
|
||||
const goDetail = (product: ShopGoods) => {
|
||||
Taro.navigateTo({ url: `/pages/shop/product-detail?id=${product.goodsId}` })
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='flex min-h-screen bg-gray-50'>
|
||||
{/* 左侧分类栏 */}
|
||||
<ScrollView scrollY className='w-20 bg-gray-100 h-screen'>
|
||||
<ScrollView scrollY className='w-20 bg-gray-100 h-screen flex-shrink-0'>
|
||||
{categoryList.map(cat => (
|
||||
<View
|
||||
key={cat.categoryId || cat.id}
|
||||
@@ -87,26 +164,125 @@ const ShopPage: React.FC = () => {
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
{/* 右侧商品列表 */}
|
||||
{/* 右侧:排序栏 + 商品列表 */}
|
||||
<View className='flex-1 flex flex-col h-screen min-w-0'>
|
||||
{/* 排序栏 */}
|
||||
<View className='flex items-center bg-white py-2 border-b border-gray-100 flex-shrink-0'>
|
||||
{SORT_TABS.map(tab => (
|
||||
<View
|
||||
key={tab.key}
|
||||
className='flex-1 flex items-center justify-center'
|
||||
onClick={() => handleSortChange(tab.key)}
|
||||
>
|
||||
<Text
|
||||
className={`text-xs ${
|
||||
sortKey === tab.key ? 'text-green-600 font-medium' : 'text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</Text>
|
||||
{tab.key === 'price' && sortKey === 'price' && (
|
||||
<Text className='text-xs text-green-600 ml-0.5'>
|
||||
{priceAsc ? '↑' : '↓'}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
<View className='flex-1 flex items-center justify-center'>
|
||||
<Text className='text-xs text-gray-400'>筛选</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 商品列表 */}
|
||||
<ScrollView
|
||||
scrollY
|
||||
className='flex-1 h-screen'
|
||||
className='flex-1'
|
||||
onScrollToLower={handleLoadMore}
|
||||
lowerThreshold={100}
|
||||
>
|
||||
<View className='p-2'>
|
||||
<View className='grid grid-cols-2 gap-2'>
|
||||
{goodsList.length > 0 ? (
|
||||
<View className='flex flex-col gap-2'>
|
||||
{goodsList.map(item => (
|
||||
<ProductCard key={item.goodsId} product={item} />
|
||||
<View
|
||||
key={item.goodsId}
|
||||
className='bg-white rounded-lg p-2 flex gap-2'
|
||||
onClick={() => goDetail(item)}
|
||||
>
|
||||
{/* 商品图片 */}
|
||||
<View className='w-20 h-20 rounded-lg bg-gray-100 overflow-hidden flex-shrink-0'>
|
||||
<Image
|
||||
className='w-full h-full'
|
||||
src={item.image || item.files || ''}
|
||||
mode='aspectFill'
|
||||
lazyLoad
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 商品信息 */}
|
||||
<View className='flex-1 flex flex-col justify-between min-w-0'>
|
||||
{/* 标题 + 积分标签 */}
|
||||
<View>
|
||||
<Text className='text-sm text-gray-800 line-clamp-2 leading-5'>
|
||||
{item.name || item.goodsName}
|
||||
</Text>
|
||||
{item.gainIntegral && item.gainIntegral > 0 ? (
|
||||
<View className='inline-block mt-1'>
|
||||
<Text className='text-xs text-orange-500 bg-orange-50 px-1 py-0.5 rounded'>
|
||||
+{item.gainIntegral}积分
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{/* 价格 + 销量 + 加购 */}
|
||||
<View className='flex items-end justify-between'>
|
||||
<View className='flex-1 min-w-0'>
|
||||
<Text className='text-sm text-red-500 font-medium'>
|
||||
¥{getDisplayPrice(item)}
|
||||
</Text>
|
||||
{item.unitName ? (
|
||||
<Text className='text-xs text-gray-400'>/{item.unitName}</Text>
|
||||
) : null}
|
||||
{/* VIP 划掉原价 */}
|
||||
{isVipMember() && item.dealerPrice ? (
|
||||
<Text className='text-xs text-gray-400 line-through ml-1'>
|
||||
¥{item.price}
|
||||
</Text>
|
||||
) : item.salePrice && item.salePrice !== item.price ? (
|
||||
isGuest() ? null : (
|
||||
<Text className='text-xs text-gray-400 line-through ml-1'>
|
||||
¥{item.salePrice}
|
||||
</Text>
|
||||
)
|
||||
) : null}
|
||||
{/* 销量 */}
|
||||
{item.sales !== undefined && item.sales > 0 && (
|
||||
<Text className='text-xs text-gray-400 ml-2'>
|
||||
已售{item.sales}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{/* 加购按钮 */}
|
||||
<View
|
||||
className='w-7 h-7 rounded-full bg-green-500 flex items-center justify-center flex-shrink-0'
|
||||
onClick={(e) => handleAddToCart(e, item)}
|
||||
>
|
||||
<Text className='text-white text-lg leading-none'>+</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{goodsList.length === 0 && !loading && (
|
||||
<EmptyState text='暂无商品' />
|
||||
) : (
|
||||
!loading && <EmptyState text='暂无商品' />
|
||||
)}
|
||||
<LoadMore loading={loading} finished={finished} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user