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