14 changed files with 1202 additions and 44 deletions
@ -0,0 +1,333 @@ |
|||||
|
// pages/goods/index.js
|
||||
|
const API = require('../../utils/api.js') |
||||
|
|
||||
|
Page({ |
||||
|
// 分享给朋友/群聊
|
||||
|
onShareAppMessage() { |
||||
|
return { |
||||
|
title: '内部货源管理 - 鸡蛋贸易平台', |
||||
|
path: '/pages/goods/index', |
||||
|
imageUrl: '/images/你有好蛋.png' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 分享到朋友圈
|
||||
|
onShareTimeline() { |
||||
|
return { |
||||
|
title: '内部货源管理 - 鸡蛋贸易平台', |
||||
|
query: '', |
||||
|
imageUrl: '/images/你有好蛋.png' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面的初始数据 |
||||
|
*/ |
||||
|
data: { |
||||
|
goodsList: [], |
||||
|
isLoading: false, |
||||
|
isRefreshing: false, // 下拉刷新状态
|
||||
|
currentPage: 1, |
||||
|
pageSize: 100, |
||||
|
hasMore: true, |
||||
|
searchKeyword: '', |
||||
|
activeFilter: 'all', // 当前筛选条件:all, small, large
|
||||
|
filterConfig: { |
||||
|
small: ['何佳芹', '李真音'], // 小品种创建者
|
||||
|
large: ['吴海燕', '陈骏', '刘琴', '汤敏'] // 大贸易创建者
|
||||
|
}, |
||||
|
total: 0 // 总数据条数
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面加载 |
||||
|
*/ |
||||
|
onLoad(options) { |
||||
|
this.loadGoodsList() |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面初次渲染完成 |
||||
|
*/ |
||||
|
onReady() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面显示 |
||||
|
*/ |
||||
|
onShow() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面隐藏 |
||||
|
*/ |
||||
|
onHide() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 生命周期函数--监听页面卸载 |
||||
|
*/ |
||||
|
onUnload() { |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面相关事件处理函数--监听用户下拉动作 |
||||
|
*/ |
||||
|
onPullDownRefresh() { |
||||
|
// 这里的onPullDownRefresh是页面级的,但我们使用的是scroll-view内置的下拉刷新,所以不需要实现这个方法
|
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* scroll-view下拉刷新事件处理 |
||||
|
*/ |
||||
|
onRefresherRefresh() { |
||||
|
this.setData({ |
||||
|
isRefreshing: true, |
||||
|
currentPage: 1, |
||||
|
goodsList: [], |
||||
|
hasMore: true |
||||
|
}) |
||||
|
this.loadGoodsList() |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 页面上拉触底事件的处理函数 |
||||
|
*/ |
||||
|
onReachBottom() { |
||||
|
if (this.data.hasMore && !this.data.isLoading) { |
||||
|
this.setData({ |
||||
|
currentPage: this.data.currentPage + 1 |
||||
|
}) |
||||
|
this.loadGoodsList() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 处理搜索输入 |
||||
|
*/ |
||||
|
onSearchInput(e) { |
||||
|
this.setData({ |
||||
|
searchKeyword: e.detail.value |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 执行搜索 |
||||
|
*/ |
||||
|
searchGoods() { |
||||
|
this.setData({ |
||||
|
currentPage: 1, |
||||
|
goodsList: [], |
||||
|
hasMore: true |
||||
|
}) |
||||
|
this.loadGoodsList() |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 清除搜索 |
||||
|
*/ |
||||
|
clearSearch() { |
||||
|
this.setData({ |
||||
|
searchKeyword: '', |
||||
|
currentPage: 1, |
||||
|
goodsList: [], |
||||
|
hasMore: true |
||||
|
}) |
||||
|
this.loadGoodsList() |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 筛选条件改变 |
||||
|
*/ |
||||
|
onFilterChange(e) { |
||||
|
const filter = e.currentTarget.dataset.filter |
||||
|
this.setData({ |
||||
|
activeFilter: filter, |
||||
|
currentPage: 1, |
||||
|
goodsList: [], |
||||
|
hasMore: true |
||||
|
}) |
||||
|
this.loadGoodsList() |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 根据筛选条件过滤数据 |
||||
|
*/ |
||||
|
filterGoodsList(goodsList) { |
||||
|
const { activeFilter, filterConfig } = this.data |
||||
|
|
||||
|
if (activeFilter === 'all') { |
||||
|
return goodsList |
||||
|
} |
||||
|
|
||||
|
const allowedCreators = filterConfig[activeFilter] || [] |
||||
|
return goodsList.filter(item => { |
||||
|
return allowedCreators.includes(item.creatorName) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 格式化时间 |
||||
|
*/ |
||||
|
formatDateTime(dateString) { |
||||
|
if (!dateString) return '未知时间' |
||||
|
|
||||
|
// 检查是否已经是格式化好的字符串(如:2026-01-03 10:04:29)
|
||||
|
if (typeof dateString === 'string' && /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateString)) { |
||||
|
// 直接返回格式化好的字符串,只保留到分钟
|
||||
|
return dateString.slice(0, 16) |
||||
|
} |
||||
|
|
||||
|
// 检查是否是其他格式的字符串
|
||||
|
const date = new Date(dateString) |
||||
|
if (isNaN(date.getTime())) { |
||||
|
// 如果转换失败,直接返回原字符串
|
||||
|
return dateString |
||||
|
} |
||||
|
|
||||
|
const year = date.getFullYear() |
||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0') |
||||
|
const day = date.getDate().toString().padStart(2, '0') |
||||
|
const hours = date.getHours().toString().padStart(2, '0') |
||||
|
const minutes = date.getMinutes().toString().padStart(2, '0') |
||||
|
return `${year}-${month}-${day} ${hours}:${minutes}` |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* 加载货源列表 |
||||
|
*/ |
||||
|
async loadGoodsList() { |
||||
|
if (this.data.isLoading) return |
||||
|
|
||||
|
this.setData({ |
||||
|
isLoading: true |
||||
|
}) |
||||
|
|
||||
|
console.log('开始加载货源列表,参数:', { |
||||
|
page: this.data.currentPage, |
||||
|
pageSize: this.data.pageSize, |
||||
|
keyword: this.data.searchKeyword, |
||||
|
activeFilter: this.data.activeFilter |
||||
|
}) |
||||
|
|
||||
|
// 调用API获取货源列表
|
||||
|
try { |
||||
|
const res = await API.getGoodsList({ |
||||
|
page: this.data.currentPage, |
||||
|
pageSize: this.data.pageSize, |
||||
|
keyword: this.data.searchKeyword |
||||
|
}) |
||||
|
|
||||
|
console.log('API返回结果:', res) |
||||
|
|
||||
|
// 检查API返回的状态
|
||||
|
if (res.success) { |
||||
|
console.log('API调用成功,products:', res.products) |
||||
|
console.log('products长度:', res.products.length) |
||||
|
console.log('API返回total:', res.total) |
||||
|
|
||||
|
// 更新总数据条数
|
||||
|
const total = res.total || 0 |
||||
|
|
||||
|
if (res.products && res.products.length > 0) { |
||||
|
// 显示第一个产品的详细信息
|
||||
|
console.log('第一个产品:', res.products[0]) |
||||
|
console.log('第一个产品的seller:', res.products[0].seller) |
||||
|
console.log('第一个产品的seller.nickName:', res.products[0].seller?.nickName) |
||||
|
console.log('第一个产品的seller.name:', res.products[0].seller?.name) |
||||
|
console.log('第一个产品的seller完整结构:', JSON.stringify(res.products[0].seller)) |
||||
|
|
||||
|
let newGoodsList = res.products || [] |
||||
|
|
||||
|
// 格式化创建时间并处理创建者信息
|
||||
|
newGoodsList = newGoodsList.map((item, index) => { |
||||
|
// 详细日志,查看每个产品的seller信息
|
||||
|
console.log(`产品${index}的seller信息:`) |
||||
|
console.log(`- seller对象:`, item.seller) |
||||
|
console.log(`- seller.nickName:`, item.seller?.nickName) |
||||
|
console.log(`- seller.name:`, item.seller?.name) |
||||
|
console.log(`- seller完整结构:`, JSON.stringify(item.seller)) |
||||
|
|
||||
|
// 确定creatorName - 只使用nickName,不使用name字段
|
||||
|
const creatorName = item.seller?.nickName || '未知' |
||||
|
console.log(`- 最终确定的creatorName:`, creatorName) |
||||
|
|
||||
|
return { |
||||
|
...item, |
||||
|
formattedCreatedAt: this.formatDateTime(item.created_at || item.createTime), |
||||
|
creatorName: creatorName |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
// 应用筛选条件
|
||||
|
const originalList = [...newGoodsList] |
||||
|
const filteredList = this.filterGoodsList(newGoodsList) |
||||
|
|
||||
|
console.log('处理并筛选后的产品列表:', filteredList) |
||||
|
console.log('筛选前后数量对比:', originalList.length, '->', filteredList.length) |
||||
|
|
||||
|
// 处理分页逻辑
|
||||
|
const updatedGoodsList = this.data.currentPage === 1 ? filteredList : [...this.data.goodsList, ...filteredList] |
||||
|
|
||||
|
// 判断是否还有更多数据
|
||||
|
// 正确逻辑:如果API返回的原始数据数量小于pageSize,说明没有更多数据
|
||||
|
// 即使筛选后的数据量小于pageSize,只要API返回的原始数据数量等于pageSize,就应该继续尝试加载更多数据
|
||||
|
const hasMore = res.products.length >= this.data.pageSize |
||||
|
|
||||
|
console.log('分页判断:', { |
||||
|
updatedGoodsListLength: updatedGoodsList.length, |
||||
|
originalDataLength: res.products.length, |
||||
|
pageSize: this.data.pageSize, |
||||
|
hasMore: hasMore |
||||
|
}) |
||||
|
|
||||
|
this.setData({ |
||||
|
goodsList: updatedGoodsList, |
||||
|
hasMore: hasMore, |
||||
|
total: total |
||||
|
}) |
||||
|
|
||||
|
// 如果是第一页,且筛选后的数据不足,尝试加载下一页
|
||||
|
if (this.data.currentPage === 1 && hasMore && filteredList.length < this.data.pageSize) { |
||||
|
console.log('筛选后数据不足,继续加载下一页') |
||||
|
this.setData({ |
||||
|
currentPage: this.data.currentPage + 1 |
||||
|
}) |
||||
|
// 递归调用loadGoodsList,继续加载下一页
|
||||
|
this.loadGoodsList() |
||||
|
return |
||||
|
} |
||||
|
} else { |
||||
|
console.log('没有产品数据返回') |
||||
|
this.setData({ |
||||
|
goodsList: [], |
||||
|
hasMore: false, |
||||
|
total: 0 |
||||
|
}) |
||||
|
} |
||||
|
} else { |
||||
|
console.error('API调用失败:', res.message) |
||||
|
wx.showToast({ |
||||
|
title: res.message || '获取货源列表失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
} catch (err) { |
||||
|
console.error('获取货源列表失败:', err) |
||||
|
wx.showToast({ |
||||
|
title: '获取货源列表失败: ' + err.message, |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} finally { |
||||
|
// 结束下拉刷新和加载状态
|
||||
|
this.setData({ |
||||
|
isLoading: false, |
||||
|
isRefreshing: false |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"usingComponents": {}, |
||||
|
"navigationBarTitleText": "内部货源管理", |
||||
|
"navigationBarBackgroundColor": "#ffffff", |
||||
|
"navigationBarTextStyle": "black", |
||||
|
"backgroundColor": "#f5f5f5" |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
<view class="container"> |
||||
|
<!-- 页面标题 --> |
||||
|
<view class="page-title"> |
||||
|
<text>内部货源管理</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 搜索框 --> |
||||
|
<view class="search-container"> |
||||
|
<view class="search-box"> |
||||
|
<input |
||||
|
class="search-input" |
||||
|
placeholder="搜索货源名称或创建人" |
||||
|
bindinput="onSearchInput" |
||||
|
bindconfirm="searchGoods" |
||||
|
value="{{searchKeyword}}" |
||||
|
/> |
||||
|
<view wx:if="{{searchKeyword}}" class="clear-icon" bindtap="clearSearch">✘</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 筛选按钮 --> |
||||
|
<view class="filter-container"> |
||||
|
<view class="filter-buttons"> |
||||
|
<view |
||||
|
class="filter-btn {{activeFilter === 'all' ? 'active' : ''}}" |
||||
|
bindtap="onFilterChange" |
||||
|
data-filter="all" |
||||
|
> |
||||
|
全部 |
||||
|
</view> |
||||
|
<view |
||||
|
class="filter-btn {{activeFilter === 'small' ? 'active' : ''}}" |
||||
|
bindtap="onFilterChange" |
||||
|
data-filter="small" |
||||
|
> |
||||
|
小品种 |
||||
|
</view> |
||||
|
<view |
||||
|
class="filter-btn {{activeFilter === 'large' ? 'active' : ''}}" |
||||
|
bindtap="onFilterChange" |
||||
|
data-filter="large" |
||||
|
> |
||||
|
大贸易 |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 骨架屏加载 --> |
||||
|
<view wx:if="{{isLoading && goodsList.length === 0}}" class="skeleton-container"> |
||||
|
<view class="skeleton-grid"> |
||||
|
<view class="skeleton-grid-item" wx:for="{{[1,2,3,4,5,6,7,8]}}" wx:key="index"> |
||||
|
<view class="skeleton-image"></view> |
||||
|
<view class="skeleton-title"></view> |
||||
|
<view class="skeleton-title short"></view> |
||||
|
<view class="skeleton-footer"> |
||||
|
<view class="skeleton-price"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品列表区域 --> |
||||
|
<view class="goods-section"> |
||||
|
<scroll-view |
||||
|
class="goods-list" |
||||
|
bindscrolltolower="onReachBottom" |
||||
|
scroll-y="true" |
||||
|
refresher-enabled="{{true}}" |
||||
|
refresher-triggered="{{isRefreshing}}" |
||||
|
refresher-default-style="black" |
||||
|
refresher-background="transparent" |
||||
|
bindrefresherrefresh="onRefresherRefresh" |
||||
|
> |
||||
|
<view class="goods-list-container"> |
||||
|
<!-- 网格布局 --> |
||||
|
<view class="grid-container"> |
||||
|
<view |
||||
|
class="grid-item" |
||||
|
wx:for="{{goodsList}}" |
||||
|
wx:key="id" |
||||
|
data-item="{{item}}" |
||||
|
> |
||||
|
<view class="product-card"> |
||||
|
<view class="product-image-wrapper"> |
||||
|
<image |
||||
|
class="product-image" |
||||
|
src="{{item.imageUrls && item.imageUrls.length > 0 ? item.imageUrls[0] : '/images/default-avatar.png'}}" |
||||
|
mode="aspectFill" |
||||
|
lazy-load="true" |
||||
|
></image> |
||||
|
<view wx:if="{{item.supplyStatus === '预售'}}" class="promo-tag presale">预售</view> |
||||
|
<view wx:if="{{item.supplyStatus === '现货'}}" class="promo-tag in-stock">现货</view> |
||||
|
</view> |
||||
|
<view class="product-info"> |
||||
|
<view class="product-title-row"> |
||||
|
<view class="product-title">{{item.name}}</view> |
||||
|
<view class="stock-count">库存:{{item.quantity || item.minOrder || '充足'}}</view> |
||||
|
</view> |
||||
|
<view class="product-spec">{{item.specification || item.spec || '无'}}<text wx:if="{{item.yolk}}"> | {{item.yolk}}</text></view> |
||||
|
<view class="product-meta"> |
||||
|
<text class="product-price">¥{{item.costprice}}</text> |
||||
|
<text class="product-location">{{item.region || ''}}</text> |
||||
|
</view> |
||||
|
<view class="product-description">{{item.description || ''}}</view> |
||||
|
<view class="creator-info"> |
||||
|
<text class="creator-name">{{item.creatorName || '未知'}}</text> |
||||
|
<text class="create-time">{{item.formattedCreatedAt || item.created_at || '未知'}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 加载更多 --> |
||||
|
<view wx:if="{{isLoading && goodsList.length > 0}}" class="loading-more"> |
||||
|
<view class="loading-spinner"></view> |
||||
|
<text class="loading-text">加载中...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 无更多数据 --> |
||||
|
<view wx:if="{{!hasMore && goodsList.length > 0}}" class="no-more-data"> |
||||
|
<text>货源正在快马加鞭的赶来</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 空状态 --> |
||||
|
<view wx:if="{{goodsList.length === 0 && !isLoading}}" class="empty-container"> |
||||
|
<text class="empty-text">暂无货源数据</text> |
||||
|
<text class="empty-hint">下拉刷新试试</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,492 @@ |
|||||
|
.container { |
||||
|
width: 100%; |
||||
|
padding: 20rpx; |
||||
|
box-sizing: border-box; |
||||
|
background-color: #f5f5f5; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.page-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
text-align: center; |
||||
|
margin: 20rpx 0 30rpx; |
||||
|
} |
||||
|
|
||||
|
/* 搜索框样式 */ |
||||
|
.search-container { |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.search-box { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background-color: white; |
||||
|
border-radius: 40rpx; |
||||
|
padding: 0 20rpx; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
/* 筛选样式 */ |
||||
|
.filter-container { |
||||
|
margin: 0 auto 24rpx; |
||||
|
padding: 0 30rpx; |
||||
|
box-sizing: border-box; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.filter-buttons { |
||||
|
display: flex; |
||||
|
background-color: white; |
||||
|
border-radius: 50rpx; |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1); |
||||
|
border: 2rpx solid transparent; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.filter-btn { |
||||
|
flex: 1; |
||||
|
text-align: center; |
||||
|
padding: 18rpx 0; |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
background-color: transparent; |
||||
|
border: none; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
||||
|
position: relative; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.filter-btn.active { |
||||
|
color: #1677ff; |
||||
|
font-weight: bold; |
||||
|
background: linear-gradient(135deg, #e6f0ff 0%, #f0f7ff 100%); |
||||
|
box-shadow: inset 0 2rpx 8rpx rgba(22, 119, 255, 0.15); |
||||
|
} |
||||
|
|
||||
|
.filter-btn::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
top: 50%; |
||||
|
transform: translateY(-50%); |
||||
|
width: 2rpx; |
||||
|
height: 48rpx; |
||||
|
background: linear-gradient(180deg, transparent 0%, #e8e8e8 50%, transparent 100%); |
||||
|
} |
||||
|
|
||||
|
.filter-btn:last-child::after { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.filter-btn:active { |
||||
|
opacity: 0.8; |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
|
|
||||
|
/* 第一个和最后一个按钮特殊样式 */ |
||||
|
.filter-btn:first-child { |
||||
|
border-radius: 50rpx 0 0 50rpx; |
||||
|
} |
||||
|
|
||||
|
.filter-btn:last-child { |
||||
|
border-radius: 0 50rpx 50rpx 0; |
||||
|
} |
||||
|
|
||||
|
/* 激活状态下的按钮动画效果 */ |
||||
|
.filter-btn.active::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 10rpx; |
||||
|
left: 50%; |
||||
|
transform: translateX(-50%); |
||||
|
width: 12rpx; |
||||
|
height: 6rpx; |
||||
|
background: #1677ff; |
||||
|
border-radius: 3rpx; |
||||
|
animation: pulse 1.5s infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes pulse { |
||||
|
0%, 100% { |
||||
|
opacity: 1; |
||||
|
transform: translateX(-50%) scale(1); |
||||
|
} |
||||
|
50% { |
||||
|
opacity: 0.7; |
||||
|
transform: translateX(-50%) scale(1.1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.search-input { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
padding: 0 20rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.search-input::placeholder { |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.clear-icon { |
||||
|
font-size: 30rpx; |
||||
|
color: #999; |
||||
|
padding: 10rpx; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
/* 加载中样式 */ |
||||
|
.loading-container { |
||||
|
text-align: center; |
||||
|
padding: 60rpx 0; |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 货源列表样式 - 网格布局 */ |
||||
|
.goods-section { |
||||
|
width: 100%; |
||||
|
margin-top: 20rpx; |
||||
|
height: calc(100vh - 300rpx); /* 设置容器高度,确保scroll-view能正常滚动 */ |
||||
|
} |
||||
|
|
||||
|
/* 滚动视图样式 */ |
||||
|
.goods-list { |
||||
|
height: 100%; /* 设置scroll-view高度为100%,确保能正常滚动 */ |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.goods-list-container { |
||||
|
width: 100%; |
||||
|
padding: 0 10rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 网格容器 */ |
||||
|
.grid-container { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 20rpx; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 网格商品项 */ |
||||
|
.grid-item { |
||||
|
width: calc((100% - 20rpx) / 2); |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
/* 商品卡片样式 */ |
||||
|
.product-card { |
||||
|
background-color: white; |
||||
|
border: 2rpx solid #e0e0e0; |
||||
|
border-radius: 16rpx; |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
transition: all 0.3s ease; |
||||
|
padding: 16rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.product-card:active { |
||||
|
transform: scale(0.98); |
||||
|
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
/* 商品图片区域 */ |
||||
|
.product-image-wrapper { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 200rpx; |
||||
|
background: #f5f5f5; |
||||
|
border-radius: 12rpx; |
||||
|
overflow: hidden; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.product-image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: block; |
||||
|
object-fit: cover; |
||||
|
object-position: center; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
/* 促销标签 */ |
||||
|
.promo-tag { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
padding: 6rpx 12rpx; |
||||
|
font-size: 20rpx; |
||||
|
color: #fff; |
||||
|
border-radius: 0 0 12rpx 0; |
||||
|
z-index: 1; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.promo-tag.presale { |
||||
|
background: linear-gradient(135deg, #ff6b00 0%, #ff8c00 100%); |
||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.promo-tag.in-stock { |
||||
|
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%); |
||||
|
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* 商品信息区域 */ |
||||
|
.product-info { |
||||
|
padding: 0; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 8rpx; |
||||
|
} |
||||
|
|
||||
|
/* 商品标题行 - 包含商品名称和库存 */ |
||||
|
.product-title-row { |
||||
|
display: flex; |
||||
|
justify-content: flex-start; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 8rpx; |
||||
|
} |
||||
|
|
||||
|
/* 商品标题 */ |
||||
|
.product-title { |
||||
|
font-size: 26rpx; |
||||
|
color: #000000; |
||||
|
line-height: 1.4; |
||||
|
height: auto; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
display: -webkit-box; |
||||
|
-webkit-line-clamp: 1; |
||||
|
-webkit-box-orient: vertical; |
||||
|
font-weight: 700; |
||||
|
flex: 1; |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
|
||||
|
/* 库存计数 */ |
||||
|
.stock-count { |
||||
|
font-size: 20rpx; |
||||
|
padding: 2rpx 10rpx; |
||||
|
border-radius: 12rpx; |
||||
|
background: rgba(82, 196, 26, 0.15); |
||||
|
color: #389e0d; |
||||
|
border: 1rpx solid rgba(82, 196, 26, 0.5); |
||||
|
font-weight: 600; |
||||
|
align-self: center; |
||||
|
margin-top: 0; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
/* 商品规格 */ |
||||
|
.product-spec { |
||||
|
font-size: 22rpx; |
||||
|
color: #333333; |
||||
|
height: auto; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
line-height: 1.3; |
||||
|
margin-bottom: 2rpx; |
||||
|
margin-top: 2rpx; |
||||
|
} |
||||
|
|
||||
|
/* 商品元信息 */ |
||||
|
.product-meta { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin: 2rpx 0 4rpx; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
.product-price { |
||||
|
font-size: 28rpx; |
||||
|
color: #ff4d4f; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.product-location { |
||||
|
font-size: 20rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
/* 商品描述 */ |
||||
|
.product-description { |
||||
|
font-size: 22rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.4; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
display: -webkit-box; |
||||
|
-webkit-line-clamp: 2; |
||||
|
-webkit-box-orient: vertical; |
||||
|
margin: 6rpx 0; |
||||
|
background: #fafafa; |
||||
|
padding: 6rpx; |
||||
|
border-radius: 8rpx; |
||||
|
border: 1rpx solid #f0f0f0; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
min-height: 50rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
/* 创建人信息 */ |
||||
|
.creator-info { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-top: 8rpx; |
||||
|
padding-top: 12rpx; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
font-size: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 加载更多样式 */ |
||||
|
.loading-more { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 40rpx 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
width: 32rpx; |
||||
|
height: 32rpx; |
||||
|
border: 3rpx solid #e5e5e5; |
||||
|
border-top-color: #1677ff; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 0.8s linear infinite; |
||||
|
margin-right: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
to { |
||||
|
transform: rotate(360deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 无更多数据 */ |
||||
|
.no-more-data { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 40rpx 0; |
||||
|
color: #999; |
||||
|
font-size: 26rpx; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.no-more-data::before, |
||||
|
.no-more-data::after { |
||||
|
content: ''; |
||||
|
flex: 1; |
||||
|
height: 1rpx; |
||||
|
background: #e5e5e5; |
||||
|
max-width: 80rpx; |
||||
|
} |
||||
|
|
||||
|
/* 空状态样式 */ |
||||
|
.empty-container { |
||||
|
text-align: center; |
||||
|
padding: 100rpx 0; |
||||
|
color: #999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
/* 骨架屏样式 */ |
||||
|
.skeleton-container { |
||||
|
padding: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.skeleton-grid { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 20rpx; |
||||
|
width: 100%; |
||||
|
padding: 0 10rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.skeleton-grid-item { |
||||
|
width: calc((100% - 20rpx) / 2); |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
overflow: hidden; |
||||
|
padding: 16rpx; |
||||
|
box-sizing: border-box; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
} |
||||
|
|
||||
|
.skeleton-image { |
||||
|
width: 100%; |
||||
|
height: 200rpx; |
||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%); |
||||
|
background-size: 200% 100%; |
||||
|
animation: skeleton-loading 1.5s infinite; |
||||
|
border-radius: 12rpx; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.skeleton-title { |
||||
|
height: 32rpx; |
||||
|
margin-bottom: 12rpx; |
||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%); |
||||
|
background-size: 200% 100%; |
||||
|
animation: skeleton-loading 1.5s infinite; |
||||
|
border-radius: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.skeleton-title.short { |
||||
|
width: 60%; |
||||
|
height: 24rpx; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.skeleton-footer { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.skeleton-price { |
||||
|
width: 80rpx; |
||||
|
height: 28rpx; |
||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%); |
||||
|
background-size: 200% 100%; |
||||
|
animation: skeleton-loading 1.5s infinite; |
||||
|
border-radius: 8rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes skeleton-loading { |
||||
|
0% { |
||||
|
background-position: 200% 0; |
||||
|
} |
||||
|
100% { |
||||
|
background-position: -200% 0; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue