feat(商品列表): 实现商品列表吸顶效果和分享功能- 添加 Tabs 粘性布局组件,实现吸顶效果- 新增商品分享功能,支持分享给好友
-优化商品列表展示样式,使用瀑布流布局 - 调整商品图片展示方式和点击跳转逻辑- 添加空状态提示,改善用户体验 -修复部分样式问题,提升页面美观度- 移除旧版订单列表相关代码和依赖- 更新页面结构,提高组件复用性 - 添加系统信息获取,适配不同设备屏幕 -优化页面滚动体验,解决滑动冲突问题
This commit is contained in:
@@ -1,162 +1,174 @@
|
|||||||
import {Avatar, Cell, Space, Tabs, Button, TabPane, Swiper} from '@nutui/nutui-react-taro'
|
import {useEffect, useState} from "react";
|
||||||
import {useEffect, useState, CSSProperties, useRef} from "react";
|
import {Image, Tabs, Empty, Sticky} from '@nutui/nutui-react-taro'
|
||||||
import {BszxPay} from "@/api/bszx/bszxPay/model";
|
import {Share} from '@nutui/icons-react-taro'
|
||||||
import {InfiniteLoading} from '@nutui/nutui-react-taro'
|
import {View, Text} from '@tarojs/components';
|
||||||
import dayjs from "dayjs";
|
import Taro from "@tarojs/taro";
|
||||||
import {pageShopOrder} from "@/api/shop/shopOrder";
|
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
||||||
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
import {pageShopGoods} from "@/api/shop/shopGoods";
|
||||||
import {copyText} from "@/utils/common";
|
|
||||||
|
|
||||||
const InfiniteUlStyle: CSSProperties = {
|
const BestSellers = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
|
||||||
marginTop: '84px',
|
const [tab1value, setTab1value] = useState<string | number>('0')
|
||||||
height: '82vh',
|
const [list, setList] = useState<ShopGoods[]>([])
|
||||||
width: '100%',
|
const [goods, setGoods] = useState<ShopGoods>()
|
||||||
padding: '0',
|
const [stickyStatus, setStickyStatus] = useState<boolean>(false)
|
||||||
overflowY: 'auto',
|
|
||||||
overflowX: 'hidden',
|
|
||||||
}
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
index: 0,
|
|
||||||
key: '全部',
|
|
||||||
title: '全部'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 1,
|
|
||||||
key: '已上架',
|
|
||||||
title: '已上架'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 2,
|
|
||||||
key: '已下架',
|
|
||||||
title: '已下架'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 3,
|
|
||||||
key: '已售罄',
|
|
||||||
title: '已售罄'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 4,
|
|
||||||
key: '警戒库存',
|
|
||||||
title: '警戒库存'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
index: 5,
|
|
||||||
key: '回收站',
|
|
||||||
title: '回收站'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
function GoodsList(props: any) {
|
const reload = () => {
|
||||||
const [list, setList] = useState<ShopOrder[]>([])
|
pageShopGoods({}).then(res => {
|
||||||
const [page, setPage] = useState(1)
|
setList(res?.list || []);
|
||||||
const [hasMore, setHasMore] = useState(true)
|
|
||||||
const swiperRef = useRef<React.ElementRef<typeof Swiper> | null>(null)
|
|
||||||
const [tabIndex, setTabIndex] = useState<string | number>(0)
|
|
||||||
|
|
||||||
console.log(props.statusBarHeight, 'ppp')
|
|
||||||
const reload = async () => {
|
|
||||||
pageShopOrder({page}).then(res => {
|
|
||||||
let newList: BszxPay[] | undefined = []
|
|
||||||
if (res?.list && res?.list.length > 0) {
|
|
||||||
newList = list?.concat(res.list)
|
|
||||||
setHasMore(true)
|
|
||||||
} else {
|
|
||||||
newList = res?.list
|
|
||||||
setHasMore(false)
|
|
||||||
}
|
|
||||||
setList(newList || []);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const reloadMore = async () => {
|
// 处理分享点击
|
||||||
setPage(page + 1)
|
const handleShare = (item: ShopGoods) => {
|
||||||
reload().then();
|
setGoods(item);
|
||||||
|
|
||||||
|
console.log(goods)
|
||||||
|
|
||||||
|
// 显示分享选项菜单
|
||||||
|
Taro.showActionSheet({
|
||||||
|
itemList: ['分享给好友'],
|
||||||
|
success: (res) => {
|
||||||
|
if (res.tapIndex === 0) {
|
||||||
|
// 分享给好友 - 触发转发
|
||||||
|
Taro.showShareMenu({
|
||||||
|
withShareTicket: true,
|
||||||
|
success: () => {
|
||||||
|
// 提示用户点击右上角分享
|
||||||
|
Taro.showToast({
|
||||||
|
title: '请点击右上角分享给好友',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log('显示分享菜单失败', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理粘性布局状态变化
|
||||||
|
const onStickyChange = (isSticky: boolean) => {
|
||||||
|
setStickyStatus(isSticky)
|
||||||
|
// 通知父组件粘性状态变化
|
||||||
|
props.onStickyChange?.(isSticky)
|
||||||
|
console.log('Tabs 粘性状态:', isSticky ? '已固定' : '取消固定')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取小程序系统信息
|
||||||
|
const getSystemInfo = () => {
|
||||||
|
const systemInfo = Taro.getSystemInfoSync()
|
||||||
|
// 状态栏高度 + 导航栏高度 (一般为44px)
|
||||||
|
return (systemInfo.statusBarHeight || 0) + 44
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPage(2)
|
reload()
|
||||||
reload().then()
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<View className={'py-3'} style={{paddingTop: '0'}}>
|
||||||
|
{/* Tabs粘性布局组件 */}
|
||||||
|
<Sticky
|
||||||
|
threshold={getSystemInfo()}
|
||||||
|
onChange={onStickyChange}
|
||||||
|
style={{
|
||||||
|
zIndex: 999,
|
||||||
|
backgroundColor: stickyStatus ? '#ffffff' : 'transparent',
|
||||||
|
boxShadow: stickyStatus ? '0 2px 8px rgba(0,0,0,0.1)' : 'none',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
marginTop: stickyStatus ? '0' : '-12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
align={'left'}
|
value={tab1value}
|
||||||
className={'fixed left-0'}
|
className={'w-full'}
|
||||||
style={{ top: '84px'}}
|
onChange={(value) => {
|
||||||
value={tabIndex}
|
setTab1value(value)
|
||||||
onChange={(page) => {
|
|
||||||
swiperRef.current?.to(page)
|
|
||||||
setTabIndex(page)
|
|
||||||
}}
|
}}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
paddingTop: stickyStatus ? '0' : '8px',
|
||||||
|
paddingBottom: stickyStatus ? '8px' : '0',
|
||||||
|
}}
|
||||||
|
activeType="smile"
|
||||||
>
|
>
|
||||||
{
|
<Tabs.TabPane title="今日主推">
|
||||||
tabs?.map((item, index) => {
|
</Tabs.TabPane>
|
||||||
return <TabPane key={index} title={item.title}></TabPane>
|
<Tabs.TabPane title="即将到期">
|
||||||
})
|
</Tabs.TabPane>
|
||||||
}
|
<Tabs.TabPane title="活动预告">
|
||||||
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div style={InfiniteUlStyle} id="scroll">
|
</Sticky>
|
||||||
<InfiniteLoading
|
|
||||||
target="scroll"
|
|
||||||
hasMore={hasMore}
|
|
||||||
onLoadMore={reloadMore}
|
|
||||||
onScroll={() => {
|
|
||||||
|
|
||||||
}}
|
<View className={'flex flex-col justify-between items-center rounded-lg px-2 mt-2'}>
|
||||||
onScrollToUpper={() => {
|
{/* 今日主推 */}
|
||||||
|
{tab1value == '0' && list?.map((item, index) => {
|
||||||
}}
|
|
||||||
loadingText={
|
|
||||||
<>
|
|
||||||
加载中
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
loadMoreText={
|
|
||||||
<>
|
|
||||||
没有更多了
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{list?.map(item => {
|
|
||||||
return (
|
return (
|
||||||
<Cell style={{padding: '16px'}}>
|
<View key={index} className={'flex flex-col rounded-lg bg-white shadow-sm w-full mb-5'}>
|
||||||
<Space direction={'vertical'} className={'w-full flex flex-col'}>
|
<Image src={item.image} mode={'aspectFit'} lazyLoad={false}
|
||||||
<div className={'order-no flex justify-between'}>
|
radius="10px 10px 0 0" height="180"
|
||||||
<span className={'text-gray-700 font-bold text-sm'}
|
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
|
||||||
onClick={() => copyText(`${item.orderNo}`)}>{item.orderNo}</span>
|
<View className={'flex flex-col p-2 rounded-lg'}>
|
||||||
<span className={'text-orange-500'}>待付款</span>
|
<View>
|
||||||
</div>
|
<View className={'car-no text-sm'}>{item.name}</View>
|
||||||
<div
|
<View className={'flex justify-between text-xs py-1'}>
|
||||||
className={'create-time text-gray-400 text-xs'}>{dayjs(item.createTime).format('YYYY年MM月DD日 HH:mm:ss')}</div>
|
<Text className={'text-orange-500'}>{item.comments}</Text>
|
||||||
<div className={'goods-info'}>
|
<Text className={'text-gray-400'}>已售 {item.sales}</Text>
|
||||||
<div className={'flex items-center'}>
|
</View>
|
||||||
<div className={'flex items-center'}>
|
<View className={'flex justify-between items-center py-2'}>
|
||||||
<Avatar
|
<View className={'flex text-red-500 text-xl items-baseline'}>
|
||||||
src='34'
|
<Text className={'text-xs'}>¥</Text>
|
||||||
size={'45'}
|
<Text className={'font-bold text-2xl'}>{item.price}</Text>
|
||||||
shape={'square'}
|
</View>
|
||||||
/>
|
<View className={'buy-btn'}>
|
||||||
<div className={'ml-2'}>{item.realName}</div>
|
<View className={'cart-icon items-center hidden'}>
|
||||||
</div>
|
<View
|
||||||
<div className={'text-gray-400 text-xs'}>{item.totalNum}件商品</div>
|
className={'flex flex-col justify-center items-center text-white px-3 gap-1 text-nowrap whitespace-nowrap cursor-pointer'}
|
||||||
</div>
|
onClick={() => handleShare(item)}
|
||||||
</div>
|
>
|
||||||
<div className={' w-full text-right'}>实付金额:¥{item.payPrice}</div>
|
<Share size={20}/>
|
||||||
<Space className={'btn flex justify-end'}>
|
</View>
|
||||||
<Button size={'small'}>取消订单</Button>
|
</View>
|
||||||
<Button size={'small'}>发货</Button>
|
<Text className={'text-white pl-4 pr-5'}
|
||||||
</Space>
|
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>购买
|
||||||
</Space>
|
</Text>
|
||||||
</Cell>
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</InfiniteLoading>
|
|
||||||
</div>
|
{/* 即将到期 */}
|
||||||
|
{tab1value == '1' && (
|
||||||
|
<Empty
|
||||||
|
size={'small'}
|
||||||
|
description="暂无即将到期的商品"
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 活动预告 */}
|
||||||
|
{tab1value == '2' && (
|
||||||
|
<Empty
|
||||||
|
size={'small'}
|
||||||
|
description="暂无活动预告"
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
export default BestSellers
|
||||||
export default GoodsList
|
|
||||||
|
|||||||
@@ -152,8 +152,8 @@ const MyPage = () => {
|
|||||||
hotToday?.imageList?.map(item => (
|
hotToday?.imageList?.map(item => (
|
||||||
<View className={'item flex flex-col mr-1'} key={item.url}>
|
<View className={'item flex flex-col mr-1'} key={item.url}>
|
||||||
<Image
|
<Image
|
||||||
width={70}
|
width={60}
|
||||||
height={70}
|
height={60}
|
||||||
src={item.url}
|
src={item.url}
|
||||||
mode={'scaleToFill'}
|
mode={'scaleToFill'}
|
||||||
lazyLoad={false}
|
lazyLoad={false}
|
||||||
|
|||||||
@@ -1,70 +1,39 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {Image, Swiper, SwiperItem, Empty} from '@nutui/nutui-react-taro'
|
import {Image, Tabs, Empty, Sticky} from '@nutui/nutui-react-taro'
|
||||||
import {Share} from '@nutui/icons-react-taro'
|
import {Share} from '@nutui/icons-react-taro'
|
||||||
import {View, Text} from '@tarojs/components';
|
import {View, Text} from '@tarojs/components';
|
||||||
import Taro from "@tarojs/taro";
|
import Taro from "@tarojs/taro";
|
||||||
import {Tabs} from '@nutui/nutui-react-taro'
|
|
||||||
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
||||||
import {pageShopGoods} from "@/api/shop/shopGoods";
|
import {pageShopGoods} from "@/api/shop/shopGoods";
|
||||||
|
|
||||||
|
const BestSellers = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
|
||||||
const BestSellers = () => {
|
|
||||||
const [tab1value, setTab1value] = useState<string | number>('0')
|
const [tab1value, setTab1value] = useState<string | number>('0')
|
||||||
const [list, setList] = useState<ShopGoods[]>([])
|
const [list, setList] = useState<ShopGoods[]>([])
|
||||||
const [goods, setGoods] = useState<ShopGoods | null>(null)
|
const [goods, setGoods] = useState<ShopGoods>()
|
||||||
// 轮播图固定高度,可根据需求调整
|
const [stickyStatus, setStickyStatus] = useState<boolean>(false)
|
||||||
const SWIPER_HEIGHT = 180;
|
|
||||||
|
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
pageShopGoods({}).then(res => {
|
pageShopGoods({}).then(res => {
|
||||||
const processGoodsItem = (item: ShopGoods) => {
|
setList(res?.list || []);
|
||||||
const pics: string[] = [];
|
})
|
||||||
// 添加主图
|
|
||||||
if (item.image) {
|
|
||||||
pics.push(item.image);
|
|
||||||
}
|
|
||||||
// 处理附加图片
|
|
||||||
if (item.files) {
|
|
||||||
try {
|
|
||||||
// 解析文件字符串为对象
|
|
||||||
const files = typeof item.files === "string"
|
|
||||||
? JSON.parse(item.files)
|
|
||||||
: item.files;
|
|
||||||
|
|
||||||
// 收集所有图片URL
|
|
||||||
Object.values(files).forEach(file => {
|
|
||||||
if (file?.url) {
|
|
||||||
pics.push(file.url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解析文件失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 返回新对象,避免直接修改原对象
|
|
||||||
return {...item, pics};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理商品列表
|
|
||||||
const goods = (res?.list || []).map(processGoodsItem);
|
|
||||||
setList(goods);
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('获取商品列表失败:', err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理分享点击
|
// 处理分享点击
|
||||||
const handleShare = (item: ShopGoods) => {
|
const handleShare = (item: ShopGoods) => {
|
||||||
setGoods(item);
|
setGoods(item);
|
||||||
|
|
||||||
|
console.log(goods)
|
||||||
|
|
||||||
// 显示分享选项菜单
|
// 显示分享选项菜单
|
||||||
Taro.showActionSheet({
|
Taro.showActionSheet({
|
||||||
itemList: ['分享给好友'],
|
itemList: ['分享给好友'],
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.tapIndex === 0) {
|
if (res.tapIndex === 0) {
|
||||||
|
// 分享给好友 - 触发转发
|
||||||
Taro.showShareMenu({
|
Taro.showShareMenu({
|
||||||
withShareTicket: true,
|
withShareTicket: true,
|
||||||
success: () => {
|
success: () => {
|
||||||
|
// 提示用户点击右上角分享
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '请点击右上角分享给好友',
|
title: '请点击右上角分享给好友',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
@@ -80,28 +49,40 @@ const BestSellers = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
// 处理粘性布局状态变化
|
||||||
reload();
|
const onStickyChange = (isSticky: boolean) => {
|
||||||
}, []);
|
setStickyStatus(isSticky)
|
||||||
|
// 通知父组件粘性状态变化
|
||||||
// 配置分享内容
|
props.onStickyChange?.(isSticky)
|
||||||
Taro.useShareAppMessage(() => {
|
console.log('Tabs 粘性状态:', isSticky ? '已固定' : '取消固定')
|
||||||
if (goods) {
|
|
||||||
return {
|
|
||||||
title: goods.name,
|
|
||||||
path: `/shop/goodsDetail/index?id=${goods.goodsId}`,
|
|
||||||
imageUrl: goods.image || ''
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
title: '热销商品',
|
// 获取小程序系统信息
|
||||||
path: '/pages/index/index'
|
const getSystemInfo = () => {
|
||||||
};
|
const systemInfo = Taro.getSystemInfoSync()
|
||||||
});
|
// 状态栏高度 + 导航栏高度 (一般为44px)
|
||||||
|
return (systemInfo.statusBarHeight || 0) + 44
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reload()
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={'py-3'}>
|
<>
|
||||||
<View className={'flex flex-col justify-between items-center rounded-lg px-2'}>
|
<View className={'py-3'} style={{paddingTop: '0'}}>
|
||||||
|
{/* Tabs粘性布局组件 */}
|
||||||
|
<Sticky
|
||||||
|
threshold={getSystemInfo()}
|
||||||
|
onChange={onStickyChange}
|
||||||
|
style={{
|
||||||
|
zIndex: 999,
|
||||||
|
backgroundColor: stickyStatus ? '#ffffff' : 'transparent',
|
||||||
|
boxShadow: stickyStatus ? '0 2px 8px rgba(0,0,0,0.1)' : 'none',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
marginTop: stickyStatus ? '0' : '-12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={tab1value}
|
value={tab1value}
|
||||||
className={'w-full'}
|
className={'w-full'}
|
||||||
@@ -109,7 +90,9 @@ const BestSellers = () => {
|
|||||||
setTab1value(value)
|
setTab1value(value)
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: '#fff',
|
backgroundColor: 'transparent',
|
||||||
|
paddingTop: stickyStatus ? '0' : '8px',
|
||||||
|
paddingBottom: stickyStatus ? '8px' : '0',
|
||||||
}}
|
}}
|
||||||
activeType="smile"
|
activeType="smile"
|
||||||
>
|
>
|
||||||
@@ -120,45 +103,16 @@ const BestSellers = () => {
|
|||||||
<Tabs.TabPane title="活动预告">
|
<Tabs.TabPane title="活动预告">
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</Sticky>
|
||||||
|
|
||||||
{tab1value == '0' && list?.map((item) => (
|
<View className={'flex flex-col justify-between items-center rounded-lg px-2 mt-2'}>
|
||||||
<View
|
{/* 今日主推 */}
|
||||||
key={item.goodsId || item.id} // 使用商品唯一ID作为key
|
{tab1value == '0' && list?.map((item, index) => {
|
||||||
className={'flex flex-col rounded-lg bg-white shadow-sm w-full mb-5'}
|
return (
|
||||||
>
|
<View key={index} className={'flex flex-col rounded-lg bg-white shadow-sm w-full mb-5'}>
|
||||||
{/* 轮播图组件 */}
|
<Image src={item.image} mode={'aspectFit'} lazyLoad={false}
|
||||||
{item.pics && item.pics.length > 0 ? (
|
radius="10px 10px 0 0" height="180"
|
||||||
<Swiper
|
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
|
||||||
defaultValue={0}
|
|
||||||
height={SWIPER_HEIGHT}
|
|
||||||
indicator
|
|
||||||
className="swiper-container"
|
|
||||||
autoPlay
|
|
||||||
interval={3000}
|
|
||||||
>
|
|
||||||
{item.pics.map((pic, picIndex) => (
|
|
||||||
<SwiperItem key={picIndex}>
|
|
||||||
<Image
|
|
||||||
radius="12px 12px 0 0"
|
|
||||||
height={SWIPER_HEIGHT}
|
|
||||||
src={pic}
|
|
||||||
mode={'aspectFill'} // 使用aspectFill保持比例并填充容器
|
|
||||||
lazyLoad
|
|
||||||
onClick={() => Taro.navigateTo({
|
|
||||||
url: `/shop/goodsDetail/index?id=${item.goodsId}`
|
|
||||||
})}
|
|
||||||
className="swiper-image"
|
|
||||||
/>
|
|
||||||
</SwiperItem>
|
|
||||||
))}
|
|
||||||
</Swiper>
|
|
||||||
) : (
|
|
||||||
// 没有图片时显示占位图
|
|
||||||
<View className="no-image-placeholder" style={{height: `${SWIPER_HEIGHT}px`}}>
|
|
||||||
<Text className="placeholder-text">暂无图片</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<View className={'flex flex-col p-2 rounded-lg'}>
|
<View className={'flex flex-col p-2 rounded-lg'}>
|
||||||
<View>
|
<View>
|
||||||
<View className={'car-no text-sm'}>{item.name}</View>
|
<View className={'car-no text-sm'}>{item.name}</View>
|
||||||
@@ -172,7 +126,7 @@ const BestSellers = () => {
|
|||||||
<Text className={'font-bold text-2xl'}>{item.price}</Text>
|
<Text className={'font-bold text-2xl'}>{item.price}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={'buy-btn'}>
|
<View className={'buy-btn'}>
|
||||||
<View className={'cart-icon flex items-center hidden'}>
|
<View className={'cart-icon items-center hidden'}>
|
||||||
<View
|
<View
|
||||||
className={'flex flex-col justify-center items-center text-white px-3 gap-1 text-nowrap whitespace-nowrap cursor-pointer'}
|
className={'flex flex-col justify-center items-center text-white px-3 gap-1 text-nowrap whitespace-nowrap cursor-pointer'}
|
||||||
onClick={() => handleShare(item)}
|
onClick={() => handleShare(item)}
|
||||||
@@ -180,36 +134,41 @@ const BestSellers = () => {
|
|||||||
<Share size={20}/>
|
<Share size={20}/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text
|
<Text className={'text-white pl-4 pr-5'}
|
||||||
className={'text-white pl-5 pr-5'}
|
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>购买
|
||||||
onClick={() => Taro.navigateTo({
|
|
||||||
url: `/shop/goodsDetail/index?id=${item.goodsId}`
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
购买
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
{
|
{/* 即将到期 */}
|
||||||
tab1value == '1' && <Empty description="暂无相关商品" style={{
|
{tab1value == '1' && (
|
||||||
|
<Empty
|
||||||
|
size={'small'}
|
||||||
|
description="暂无即将到期的商品"
|
||||||
|
style={{
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
}}/>
|
}}
|
||||||
}
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{
|
{/* 活动预告 */}
|
||||||
tab1value == '2' && <Empty description="暂无相关商品" style={{
|
{tab1value == '2' && (
|
||||||
|
<Empty
|
||||||
|
size={'small'}
|
||||||
|
description="暂无活动预告"
|
||||||
|
style={{
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
}}/>
|
}}
|
||||||
}
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BestSellers
|
export default BestSellers
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {Image} from '@nutui/nutui-react-taro'
|
import {Image, Tabs, Empty, Sticky} from '@nutui/nutui-react-taro'
|
||||||
import {Share} from '@nutui/icons-react-taro'
|
import {View, Text} from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from "@tarojs/taro";
|
||||||
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
||||||
import {pageShopGoods} from "@/api/shop/shopGoods";
|
import {pageShopGoods} from "@/api/shop/shopGoods";
|
||||||
import './GoodsList.scss'
|
import './GoodsList.scss';
|
||||||
|
|
||||||
|
const GoodsList = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
|
||||||
const BestSellers = () => {
|
const [tab1value, setTab1value] = useState<string | number>('0')
|
||||||
const [list, setList] = useState<ShopGoods[]>([])
|
const [list, setList] = useState<ShopGoods[]>([])
|
||||||
|
const [stickyStatus, setStickyStatus] = useState<boolean>(false)
|
||||||
|
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
pageShopGoods({}).then(res => {
|
pageShopGoods({}).then(res => {
|
||||||
@@ -16,52 +17,127 @@ const BestSellers = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理粘性布局状态变化
|
||||||
|
const onStickyChange = (isSticky: boolean) => {
|
||||||
|
setStickyStatus(isSticky)
|
||||||
|
// 通知父组件粘性状态变化
|
||||||
|
props.onStickyChange?.(isSticky)
|
||||||
|
console.log('Tabs 粘性状态:', isSticky ? '已固定' : '取消固定')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取小程序系统信息
|
||||||
|
const getSystemInfo = () => {
|
||||||
|
const systemInfo = Taro.getSystemInfoSync()
|
||||||
|
// 状态栏高度 + 导航栏高度 (一般为44px)
|
||||||
|
return (systemInfo.statusBarHeight || 0) + 44
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload()
|
reload()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={'py-3'}>
|
<View className={'py-3'} style={{paddingTop: '0'}}>
|
||||||
<div className={'flex flex-wrap justify-between items-start rounded-lg px-2'}>
|
{/* Tabs粘性布局组件 */}
|
||||||
|
<Sticky
|
||||||
|
threshold={getSystemInfo()}
|
||||||
|
onChange={onStickyChange}
|
||||||
|
style={{
|
||||||
|
zIndex: 999,
|
||||||
|
backgroundColor: stickyStatus ? '#ffffff' : 'transparent',
|
||||||
|
boxShadow: stickyStatus ? '0 2px 8px rgba(0,0,0,0.1)' : 'none',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
marginTop: stickyStatus ? '0' : '-12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
value={tab1value}
|
||||||
|
className={'w-full'}
|
||||||
|
onChange={(value) => {
|
||||||
|
setTab1value(value)
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
paddingTop: stickyStatus ? '0' : '8px',
|
||||||
|
paddingBottom: stickyStatus ? '8px' : '0',
|
||||||
|
}}
|
||||||
|
activeType="smile"
|
||||||
|
>
|
||||||
|
<Tabs.TabPane title="今日主推">
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane title="即将到期">
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane title="活动预告">
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Sticky>
|
||||||
|
|
||||||
|
<View className={'bg-gray-50'}>
|
||||||
|
{/* 今日主推 - 瀑布流布局 */}
|
||||||
|
{tab1value == '0' && (
|
||||||
|
<View className={'grid grid-cols-2 gap-2 pb-2 p-2 '}>
|
||||||
{list?.map((item, index) => {
|
{list?.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className={'flex flex-col rounded-lg bg-white shadow-sm mb-5'} style={{
|
<View key={index} className={'goods-waterfall-item bg-white'} style={{
|
||||||
width: '48%'
|
borderRadius: '0 0 6px 6px'
|
||||||
}}>
|
}}>
|
||||||
<Image src={item.image} mode={'scaleToFill'} lazyLoad={false}
|
<View className={'goods-card'}>
|
||||||
radius="10px 10px 0 0" height="180"
|
<Image
|
||||||
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
|
src={item.image}
|
||||||
<div className={'flex flex-col p-2 rounded-lg'}>
|
lazyLoad={false}
|
||||||
<div>
|
style={{
|
||||||
<div className={'car-no text-sm'}>{item.name}</div>
|
borderRadius: '6px 6px 0 0',
|
||||||
<div className={'flex justify-between text-xs py-1'}>
|
height: '180px'
|
||||||
<span className={'text-orange-500'}>{item.comments}</span>
|
}}
|
||||||
<span className={'text-gray-400'}>已售 {item.sales}</span>
|
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}
|
||||||
</div>
|
/>
|
||||||
<div className={'flex justify-between items-center py-2'}>
|
<View className={'goods-info p-2 flex flex-col'}>
|
||||||
<div className={'flex text-red-500 text-xl items-baseline'}>
|
<View className={'goods-title text-sm font-bold'}>{item.name}</View>
|
||||||
<span className={'text-xs'}>¥</span>
|
<View className={'goods-meta'}>
|
||||||
<span className={'font-bold text-2xl'}>{item.price}</span>
|
<Text className={'goods-comments text-gray-400 text-xs'}>{item.comments}</Text>
|
||||||
</div>
|
</View>
|
||||||
<div className={'buy-btn'}>
|
<View className={'goods-price-section flex justify-between'}>
|
||||||
<div className={'cart-icon'}>
|
<View className={'goods-price'}>
|
||||||
<Share size={20} className={'mx-4 mt-2'}
|
<Text className={'price-unit text-orange-600 font-bold text-lg'}>¥</Text>
|
||||||
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
|
<Text className={'price-number text-orange-600 font-bold text-lg'}>{item.price}</Text>
|
||||||
</div>
|
</View>
|
||||||
<div className={'text-white pl-4 pr-5'}
|
<View className={'goods-actions'}>
|
||||||
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>购买
|
<Text className={'goods-sales text-gray-400 text-xs'}>已售 {item.sales}</Text>
|
||||||
</div>
|
</View>
|
||||||
</div>
|
</View>
|
||||||
</div>
|
</View>
|
||||||
</div>
|
</View>
|
||||||
</div>
|
</View>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</View>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{/* 即将到期 */}
|
||||||
|
{tab1value == '1' && (
|
||||||
|
<Empty
|
||||||
|
size={'small'}
|
||||||
|
description="暂无即将到期的商品"
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 活动预告 */}
|
||||||
|
{tab1value == '2' && (
|
||||||
|
<Empty
|
||||||
|
size={'small'}
|
||||||
|
description="暂无活动预告"
|
||||||
|
style={{
|
||||||
|
background: 'transparent',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default BestSellers
|
export default GoodsList
|
||||||
|
|||||||
@@ -14,3 +14,10 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 吸顶状态下的样式 */
|
||||||
|
.nutui-sticky--fixed {
|
||||||
|
.header-bg {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import {Button, Space} from '@nutui/nutui-react-taro'
|
import {Button, Space, Sticky} from '@nutui/nutui-react-taro'
|
||||||
import {TriangleDown} from '@nutui/icons-react-taro'
|
import {TriangleDown} from '@nutui/icons-react-taro'
|
||||||
import {Avatar, NavBar} from '@nutui/nutui-react-taro'
|
import {Avatar, NavBar} from '@nutui/nutui-react-taro'
|
||||||
import {getUserInfo, getWxOpenId} from "@/api/layout";
|
import {getUserInfo, getWxOpenId} from "@/api/layout";
|
||||||
@@ -13,7 +13,7 @@ import {View,Text} from '@tarojs/components'
|
|||||||
import MySearch from "./MySearch";
|
import MySearch from "./MySearch";
|
||||||
import './Header.scss';
|
import './Header.scss';
|
||||||
|
|
||||||
const Header = (props: any) => {
|
const Header = (_: any) => {
|
||||||
// 使用新的useShopInfo Hook
|
// 使用新的useShopInfo Hook
|
||||||
const {
|
const {
|
||||||
getWebsiteName,
|
getWebsiteName,
|
||||||
@@ -22,6 +22,7 @@ const Header = (props: any) => {
|
|||||||
|
|
||||||
const [IsLogin, setIsLogin] = useState<boolean>(true)
|
const [IsLogin, setIsLogin] = useState<boolean>(true)
|
||||||
const [statusBarHeight, setStatusBarHeight] = useState<number>()
|
const [statusBarHeight, setStatusBarHeight] = useState<number>()
|
||||||
|
const [stickyStatus, setStickyStatus] = useState<boolean>(false)
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
Taro.getSystemInfo({
|
Taro.getSystemInfo({
|
||||||
@@ -166,21 +167,47 @@ const Header = (props: any) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理粘性布局状态变化
|
||||||
|
const onStickyChange = (isSticky: boolean) => {
|
||||||
|
setStickyStatus(isSticky)
|
||||||
|
console.log('Header 粘性状态:', isSticky ? '已固定' : '取消固定')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取小程序系统信息
|
||||||
|
// const getSystemInfo = () => {
|
||||||
|
// const systemInfo = Taro.getSystemInfoSync()
|
||||||
|
// // 状态栏高度 + 导航栏高度 (一般为44px)
|
||||||
|
// return (systemInfo.statusBarHeight || 0) + 44
|
||||||
|
// }
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload().then()
|
reload().then()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View className={'fixed top-0 header-bg'} style={{
|
<Sticky
|
||||||
height: !props.stickyStatus ? '180px' : 'auto',
|
threshold={0}
|
||||||
paddingBottom: '12px'
|
onChange={onStickyChange}
|
||||||
|
style={{
|
||||||
|
zIndex: 1000,
|
||||||
|
backgroundColor: stickyStatus ? '#03605c' : 'transparent',
|
||||||
|
transition: 'background-color 0.3s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View className={'header-bg'} style={{
|
||||||
|
height: !stickyStatus ? '180px' : `${(statusBarHeight || 0) + 44}px`,
|
||||||
|
paddingBottom: !stickyStatus ? '12px' : '0px'
|
||||||
}}>
|
}}>
|
||||||
<MySearch statusBarHeight={statusBarHeight} />
|
{/* 只在非吸顶状态下显示搜索框 */}
|
||||||
{/*{!props.stickyStatus && <MySearch done={reload}/>}*/}
|
{!stickyStatus && <MySearch statusBarHeight={statusBarHeight} />}
|
||||||
</View>
|
</View>
|
||||||
<NavBar
|
<NavBar
|
||||||
style={{marginTop: `${statusBarHeight}px`, marginBottom: '0px', backgroundColor: 'transparent'}}
|
style={{
|
||||||
|
marginTop: `${statusBarHeight}px`,
|
||||||
|
marginBottom: '0px',
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}}
|
||||||
onBackClick={() => {
|
onBackClick={() => {
|
||||||
}}
|
}}
|
||||||
left={
|
left={
|
||||||
@@ -209,6 +236,7 @@ const Header = (props: any) => {
|
|||||||
)}>
|
)}>
|
||||||
{/*<QRLoginButton />*/}
|
{/*<QRLoginButton />*/}
|
||||||
</NavBar>
|
</NavBar>
|
||||||
|
</Sticky>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,3 +18,59 @@ page {
|
|||||||
height: 70px;
|
height: 70px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 轮播图容器样式,确保支持两种滑动操作 */
|
||||||
|
.banner-swiper-container {
|
||||||
|
touch-action: pan-y !important; /* 允许垂直滑动 */
|
||||||
|
|
||||||
|
.nut-swiper {
|
||||||
|
touch-action: pan-y !important; /* 允许垂直滑动 */
|
||||||
|
|
||||||
|
.nut-swiper-item {
|
||||||
|
touch-action: pan-x pan-y !important; /* 允许水平和垂直滑动 */
|
||||||
|
|
||||||
|
image {
|
||||||
|
pointer-events: auto; /* 确保图片点击事件正常 */
|
||||||
|
touch-action: manipulation; /* 优化触摸操作 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 为Swiper容器添加特殊处理 */
|
||||||
|
.nut-swiper--horizontal {
|
||||||
|
touch-action: pan-y !important; /* 允许垂直滑动 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 吸顶状态下的样式 */
|
||||||
|
.nutui-sticky--fixed {
|
||||||
|
.header-bg {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 为Swiper添加更精确的触摸控制 */
|
||||||
|
.nut-swiper {
|
||||||
|
touch-action: pan-y !important;
|
||||||
|
|
||||||
|
.nut-swiper-inner {
|
||||||
|
touch-action: pan-x pan-y !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义Swiper样式 */
|
||||||
|
.custom-swiper {
|
||||||
|
touch-action: pan-y !important;
|
||||||
|
|
||||||
|
.nut-swiper-item {
|
||||||
|
touch-action: pan-x pan-y !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保Swiper内部元素不会阻止页面滚动 */
|
||||||
|
.banner-swiper-container,
|
||||||
|
.custom-swiper,
|
||||||
|
.nut-swiper,
|
||||||
|
.nut-swiper-item {
|
||||||
|
-webkit-overflow-scrolling: touch; /* iOS平台启用硬件加速滚动 */
|
||||||
|
}
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import BestSellers from './BestSellers';
|
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import {useShareAppMessage} from "@tarojs/taro"
|
import {useShareAppMessage} from "@tarojs/taro"
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {getShopInfo} from "@/api/layout";
|
import {getShopInfo} from "@/api/layout";
|
||||||
import {Sticky} from '@nutui/nutui-react-taro'
|
|
||||||
import Menu from "./Menu";
|
import Menu from "./Menu";
|
||||||
import Banner from "./Banner";
|
import Banner from "./Banner";
|
||||||
import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
|
import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
import GoodsList from './GoodsList';
|
||||||
// import GoodsList from "./GoodsList";
|
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
// 吸顶状态
|
// 吸顶状态
|
||||||
const [stickyStatus, setStickyStatus] = useState<boolean>(false)
|
// const [stickyStatus, setStickyStatus] = useState<boolean>(false)
|
||||||
|
// Tabs粘性状态
|
||||||
|
const [_, setTabsStickyStatus] = useState<boolean>(false)
|
||||||
|
|
||||||
useShareAppMessage(() => {
|
useShareAppMessage(() => {
|
||||||
// 获取当前用户ID,用于生成邀请链接
|
// 获取当前用户ID,用于生成邀请链接
|
||||||
const userId = Taro.getStorageSync('UserId');
|
const userId = Taro.getStorageSync('UserId');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: '网宿小店 - 网宿软件',
|
title: '🏠 首页 🏠',
|
||||||
path: userId ? `/pages/index/index?inviter=${userId}&source=share&t=${Date.now()}` : `/pages/index/index`,
|
path: userId ? `/pages/index/index?inviter=${userId}&source=share&t=${Date.now()}` : `/pages/index/index`,
|
||||||
success: function () {
|
success: function () {
|
||||||
console.log('首页分享成功');
|
console.log('首页分享成功');
|
||||||
@@ -79,10 +78,15 @@ function Home() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSticky = (item: IArguments) => {
|
// const onSticky = (item: IArguments) => {
|
||||||
if(item){
|
// if(item){
|
||||||
setStickyStatus(!stickyStatus)
|
// setStickyStatus(!stickyStatus)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 处理Tabs粘性状态变化
|
||||||
|
const handleTabsStickyChange = (isSticky: boolean) => {
|
||||||
|
setTabsStickyStatus(isSticky)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
@@ -150,14 +154,13 @@ function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sticky threshold={0} onChange={() => onSticky(arguments)}>
|
{/* Header区域 - 现在由Header组件内部处理吸顶逻辑 */}
|
||||||
<Header stickyStatus={stickyStatus}/>
|
<Header />
|
||||||
</Sticky>
|
|
||||||
<div className={'flex flex-col mt-12'}>
|
<div className={'flex flex-col mt-12'}>
|
||||||
<Menu/>
|
<Menu/>
|
||||||
<Banner/>
|
<Banner/>
|
||||||
<BestSellers/>
|
<GoodsList onStickyChange={handleTabsStickyChange}/>
|
||||||
{/*<GoodsList/>*/}
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,3 +15,11 @@ rich-text img {
|
|||||||
.no-margin {
|
.no-margin {
|
||||||
margin: 0 !important; /* 使用 !important 来确保覆盖默认样式 */
|
margin: 0 !important; /* 使用 !important 来确保覆盖默认样式 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文本截断样式 */
|
||||||
|
.truncate {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {Image, Divider, Badge} from "@nutui/nutui-react-taro";
|
import {Image, Badge, Popup, CellGroup, Cell} from "@nutui/nutui-react-taro";
|
||||||
import {ArrowLeft, Headphones, Share, Cart} from "@nutui/icons-react-taro";
|
import {ArrowLeft, Headphones, Share, Cart, ArrowRight} from "@nutui/icons-react-taro";
|
||||||
import Taro, {useShareAppMessage} from "@tarojs/taro";
|
import Taro, {useShareAppMessage} from "@tarojs/taro";
|
||||||
import {RichText, View} from '@tarojs/components'
|
import {RichText, View, Text} from '@tarojs/components'
|
||||||
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
||||||
import {getShopGoods} from "@/api/shop/shopGoods";
|
import {getShopGoods} from "@/api/shop/shopGoods";
|
||||||
import {listShopGoodsSpec} from "@/api/shop/shopGoodsSpec";
|
import {listShopGoodsSpec} from "@/api/shop/shopGoodsSpec";
|
||||||
@@ -14,21 +14,30 @@ import navTo, {wxParse} from "@/utils/common";
|
|||||||
import SpecSelector from "@/components/SpecSelector";
|
import SpecSelector from "@/components/SpecSelector";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import {useCart} from "@/hooks/useCart";
|
import {useCart} from "@/hooks/useCart";
|
||||||
|
import {useConfig} from "@/hooks/useConfig";
|
||||||
|
|
||||||
const GoodsDetail = () => {
|
const GoodsDetail = () => {
|
||||||
|
const [statusBarHeight, setStatusBarHeight] = useState<number>(44);
|
||||||
|
const [windowWidth, setWindowWidth] = useState<number>(390)
|
||||||
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
||||||
const [files, setFiles] = useState<any[]>([]);
|
const [files, setFiles] = useState<any[]>([]);
|
||||||
const [specs, setSpecs] = useState<ShopGoodsSpec[]>([]);
|
const [specs, setSpecs] = useState<ShopGoodsSpec[]>([]);
|
||||||
const [skus, setSkus] = useState<ShopGoodsSku[]>([]);
|
const [skus, setSkus] = useState<ShopGoodsSku[]>([]);
|
||||||
const [showSpecSelector, setShowSpecSelector] = useState(false);
|
const [showSpecSelector, setShowSpecSelector] = useState(false);
|
||||||
const [specAction, setSpecAction] = useState<'cart' | 'buy'>('cart');
|
const [specAction, setSpecAction] = useState<'cart' | 'buy'>('cart');
|
||||||
|
const [showBottom, setShowBottom] = useState(false)
|
||||||
|
const [bottomItem, setBottomItem] = useState<any>({
|
||||||
|
title: '',
|
||||||
|
content: ''
|
||||||
|
})
|
||||||
// const [selectedSku, setSelectedSku] = useState<ShopGoodsSku | null>(null);
|
// const [selectedSku, setSelectedSku] = useState<ShopGoodsSku | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const router = Taro.getCurrentInstance().router;
|
const router = Taro.getCurrentInstance().router;
|
||||||
const goodsId = router?.params?.id;
|
const goodsId = router?.params?.id;
|
||||||
|
|
||||||
// 使用购物车Hook
|
// 使用购物车Hook
|
||||||
const {cartCount, addToCart} = useCart();
|
const {cartCount, addToCart} = useCart()
|
||||||
|
const {config} = useConfig()
|
||||||
|
|
||||||
// 处理加入购物车
|
// 处理加入购物车
|
||||||
const handleAddToCart = () => {
|
const handleAddToCart = () => {
|
||||||
@@ -117,7 +126,21 @@ const GoodsDetail = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openBottom = (title: string, content: string) => {
|
||||||
|
setBottomItem({
|
||||||
|
title,
|
||||||
|
content
|
||||||
|
})
|
||||||
|
setShowBottom(true)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
Taro.getSystemInfo({
|
||||||
|
success: (res) => {
|
||||||
|
setWindowWidth(res.windowWidth)
|
||||||
|
setStatusBarHeight(Number(res.statusBarHeight) + 5)
|
||||||
|
},
|
||||||
|
});
|
||||||
if (goodsId) {
|
if (goodsId) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
@@ -187,12 +210,12 @@ const GoodsDetail = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!goods || loading) {
|
if (!goods || loading) {
|
||||||
return <div>加载中...</div>;
|
return <View>加载中...</View>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"py-0"}>
|
<View className={"py-0"}>
|
||||||
<div
|
<View
|
||||||
className={
|
className={
|
||||||
"fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-70"
|
"fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-70"
|
||||||
}
|
}
|
||||||
@@ -200,36 +223,36 @@ const GoodsDetail = () => {
|
|||||||
borderRadius: "100%",
|
borderRadius: "100%",
|
||||||
width: "32px",
|
width: "32px",
|
||||||
height: "32px",
|
height: "32px",
|
||||||
top: "50px",
|
top: statusBarHeight + 'px',
|
||||||
left: "10px",
|
left: "10px",
|
||||||
}}
|
}}
|
||||||
onClick={() => Taro.navigateBack()}
|
onClick={() => Taro.navigateBack()}
|
||||||
>
|
>
|
||||||
<ArrowLeft size={14}/>
|
<ArrowLeft size={14}/>
|
||||||
</div>
|
</View>
|
||||||
<div className={
|
<View className={
|
||||||
"fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-90"
|
"fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-90"
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: "100%",
|
borderRadius: "100%",
|
||||||
width: "32px",
|
width: "32px",
|
||||||
height: "32px",
|
height: "32px",
|
||||||
top: "50px",
|
top: statusBarHeight + 'px',
|
||||||
right: "110px",
|
right: "110px",
|
||||||
}}
|
}}
|
||||||
onClick={() => Taro.switchTab({url: `/pages/cart/cart`})}>
|
onClick={() => Taro.switchTab({url: `/pages/cart/cart`})}>
|
||||||
<Badge value={cartCount} top="-2" right="2">
|
<Badge value={cartCount} top="-2" right="2">
|
||||||
<div style={{display: 'flex', alignItems: 'center'}}>
|
<View style={{display: 'flex', alignItems: 'center'}}>
|
||||||
<Cart size={16}/>
|
<Cart size={16}/>
|
||||||
</div>
|
</View>
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</View>
|
||||||
{
|
{
|
||||||
files.length > 0 && (
|
files.length > 0 && (
|
||||||
<Swiper defaultValue={0} indicator height={'350px'}>
|
<Swiper defaultValue={0} indicator height={windowWidth}>
|
||||||
{files.map((item) => (
|
{files.map((item) => (
|
||||||
<Swiper.Item key={item}>
|
<Swiper.Item key={item}>
|
||||||
<Image width="100%" height={'100%'} src={item.url} mode={'scaleToFill'} lazyLoad={false}/>
|
<Image width={windowWidth} height={windowWidth} src={item.url} mode={'scaleToFill'} lazyLoad={false}/>
|
||||||
</Swiper.Item>
|
</Swiper.Item>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
@@ -245,69 +268,116 @@ const GoodsDetail = () => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div
|
<View
|
||||||
className={"flex flex-col justify-between items-center rounded-lg px-2"}
|
className={"flex flex-col justify-between items-center"}
|
||||||
>
|
>
|
||||||
<div
|
<View
|
||||||
className={
|
className={
|
||||||
"flex flex-col rounded-lg bg-white shadow-sm w-full mt-2"
|
"flex flex-col bg-white w-full"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={"flex flex-col p-2 rounded-lg"}>
|
<View className={"flex flex-col p-3 rounded-lg"}>
|
||||||
<>
|
<>
|
||||||
<div className={'flex justify-between'}>
|
<View className={'flex justify-between'}>
|
||||||
<div className={'flex text-red-500 text-xl items-baseline'}>
|
<View className={'flex text-red-500 text-xl items-baseline'}>
|
||||||
<span className={'text-xs'}>¥</span>
|
<span className={'text-xs'}>¥</span>
|
||||||
<span className={'font-bold text-2xl'}>{goods.price}</span>
|
<span className={'font-bold text-2xl'}>{goods.price}</span>
|
||||||
</div>
|
</View>
|
||||||
<span className={"text-gray-400 text-xs"}>已售 {goods.sales}</span>
|
<span className={"text-gray-400 text-xs"}>已售 {goods.sales}</span>
|
||||||
</div>
|
</View>
|
||||||
<div className={'flex justify-between items-center'}>
|
<View className={'flex justify-between items-center'}>
|
||||||
<div className={'goods-info'}>
|
<View className={'goods-info'}>
|
||||||
<div className={"car-no text-lg"}>
|
<View className={"car-no text-lg"}>
|
||||||
{goods.name}
|
{goods.name}
|
||||||
</div>
|
</View>
|
||||||
<div className={"flex justify-between text-xs py-1"}>
|
<View className={"flex justify-between text-xs py-1"}>
|
||||||
<span className={"text-orange-500"}>
|
<span className={"text-orange-500"}>
|
||||||
{goods.comments}
|
{goods.comments}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</View>
|
||||||
</div>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<button
|
<button
|
||||||
className={'flex flex-col justify-center items-center text-gray-500 px-1 gap-1 text-nowrap whitespace-nowrap'}
|
className={'flex flex-col justify-center items-center bg-white text-gray-500 px-1 gap-1 text-nowrap whitespace-nowrap'}
|
||||||
open-type="share"><Share
|
open-type="share">
|
||||||
size={20}/>
|
<Share size={20}/>
|
||||||
<span className={'text-xs'}>分享</span>
|
<span className={'text-xs'}>分享</span>
|
||||||
</button>
|
</button>
|
||||||
</View>
|
</View>
|
||||||
</div>
|
</View>
|
||||||
</>
|
</>
|
||||||
</div>
|
</View>
|
||||||
</div>
|
</View>
|
||||||
<div className={"mt-2 py-2"}>
|
<View className={'w-full'}>
|
||||||
<Divider>商品详情</Divider>
|
{
|
||||||
|
config?.deliveryText && (
|
||||||
|
<CellGroup>
|
||||||
|
<Cell title={'配送'} extra={
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className={'truncate max-w-56 inline-block'}>{config?.deliveryText || '14:30下单,明天配送'}</Text>
|
||||||
|
<ArrowRight color="#cccccc" size={15}/>
|
||||||
|
</View>
|
||||||
|
} onClick={() => openBottom('配送', `${config?.deliveryText}`)}/>
|
||||||
|
<Cell title={'保障'} extra={
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className={'truncate max-w-56 inline-block'}>{config?.guaranteeText || '支持7天无理由退货'}</Text>
|
||||||
|
<ArrowRight color="#cccccc" size={15}/>
|
||||||
|
</View>
|
||||||
|
} onClick={() => openBottom('保障', `${config?.guaranteeText}`)}/>
|
||||||
|
</CellGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{config?.openComments == '1' && (
|
||||||
|
<CellGroup>
|
||||||
|
<Cell title={'用户评价 (0)'} extra={
|
||||||
|
<>
|
||||||
|
<Text>查看全部</Text>
|
||||||
|
<ArrowRight color="#cccccc" size={15}/>
|
||||||
|
</>
|
||||||
|
} onClick={() => navTo(`/shop/comments/index`)}/>
|
||||||
|
<Cell className={'flex h-32 bg-white p-4'}>
|
||||||
|
暂无评价
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<View className={'w-full'}>
|
||||||
<RichText nodes={goods.content || '内容详情'}/>
|
<RichText nodes={goods.content || '内容详情'}/>
|
||||||
</div>
|
<View className={'h-24'}></View>
|
||||||
</div>
|
</View>
|
||||||
|
</View>
|
||||||
|
{/*底部弹窗*/}
|
||||||
|
<Popup
|
||||||
|
visible={showBottom}
|
||||||
|
position="bottom"
|
||||||
|
onClose={() => {
|
||||||
|
setShowBottom(false)
|
||||||
|
}}
|
||||||
|
lockScroll
|
||||||
|
>
|
||||||
|
<View className={'flex flex-col p-4'}>
|
||||||
|
<Text className={'font-bold text-sm'}>{bottomItem.title}</Text>
|
||||||
|
<Text className={'text-gray-500 my-2'}>{bottomItem.content}</Text>
|
||||||
|
</View>
|
||||||
|
</Popup>
|
||||||
{/*底部购买按钮*/}
|
{/*底部购买按钮*/}
|
||||||
<div className={'fixed bg-white w-full bottom-0 left-0 pt-4 pb-10'}>
|
<View className={'fixed bg-white w-full bottom-0 left-0 pt-4 pb-8'}>
|
||||||
<View className={'btn-bar flex justify-between items-center'}>
|
<View className={'btn-bar flex justify-between items-center'}>
|
||||||
<div className={'flex justify-center items-center mx-4'}>
|
<View className={'flex justify-center items-center mx-4'}>
|
||||||
<button open-type="contact" className={'flex items-center'}>
|
<button open-type="contact" className={'flex items-center'}>
|
||||||
<Headphones size={18} style={{marginRight: '4px'}}/>咨询
|
<Headphones size={18} style={{marginRight: '4px'}}/>咨询
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
<div className={'buy-btn mx-4'}>
|
|
||||||
<div className={'cart-add px-4 text-sm'}
|
|
||||||
onClick={() => handleAddToCart()}>加入购物车
|
|
||||||
</div>
|
|
||||||
<div className={'cart-buy pl-4 pr-5 text-sm'}
|
|
||||||
onClick={() => handleBuyNow()}>立即购买
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</View>
|
</View>
|
||||||
</div>
|
<View className={'buy-btn mx-4'}>
|
||||||
|
<View className={'cart-add px-4 text-sm'}
|
||||||
|
onClick={() => handleAddToCart()}>加入购物车
|
||||||
|
</View>
|
||||||
|
<View className={'cart-buy pl-4 pr-5 text-sm'}
|
||||||
|
onClick={() => handleBuyNow()}>立即购买
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* 规格选择器 */}
|
{/* 规格选择器 */}
|
||||||
{showSpecSelector && (
|
{showSpecSelector && (
|
||||||
@@ -320,7 +390,7 @@ const GoodsDetail = () => {
|
|||||||
onClose={() => setShowSpecSelector(false)}
|
onClose={() => setShowSpecSelector(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user