'首页和商品详情页初稿'

This commit is contained in:
2026-08-21 19:02:35 +08:00
parent 75135effde
commit d53bde57b6
29 changed files with 3694 additions and 3085 deletions
@@ -0,0 +1,62 @@
<template>
<!-- 已选筛选标签 -->
<view class="active-filters" v-if="list.length > 0">
<view class="active-filter-tag" v-for="(item, index) in list" :key="index">
<text>{{ item.label }}</text>
<text class="filter-remove" @tap="onRemove(item.key)"></text>
</view>
<view class="clear-all" @tap="onClearAll">清除全部</view>
</view>
</template>
<script>
export default {
name: 'ActiveFilters',
props: {
list: {
type: Array,
default: () => []
}
},
methods: {
onRemove(key) {
this.$emit('remove', key)
},
onClearAll() {
this.$emit('clear-all')
}
}
}
</script>
<style scoped>
.active-filters {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16rpx;
padding: 20rpx 32rpx;
background: #ffffff;
border-bottom: 2rpx solid #f5f5f5;
}
.active-filter-tag {
display: flex;
align-items: center;
gap: 10rpx;
padding: 8rpx 20rpx;
background: #fff0e5;
border-radius: 24rpx;
font-size: 22rpx;
color: #ff6800;
}
.filter-remove {
font-size: 20rpx;
color: #ff6800;
opacity: 0.7;
}
.clear-all {
margin-left: auto;
font-size: 22rpx;
color: #999999;
}
</style>
@@ -0,0 +1,91 @@
<template>
<!-- 左侧分类侧边栏 -->
<scroll-view scroll-y class="sidebar">
<view
class="sidebar-item"
:class="{ active: activeId === item.id }"
v-for="(item, index) in list"
:key="index"
@tap="selectItem(item.id)"
>
<view class="sidebar-indicator" v-if="activeId === item.id"></view>
<text>{{ item.name }}</text>
</view>
<!-- 促销Banner -->
<view class="sidebar-promo">
<text class="promo-title">0元拿样</text>
<text class="promo-desc">全国包邮</text>
</view>
</scroll-view>
</template>
<script>
export default {
name: 'CategorySidebar',
props: {
list: {
type: Array,
default: () => []
},
activeId: {
type: [String, Number],
default: 'all'
}
},
methods: {
selectItem(id) {
this.$emit('select', id)
}
}
}
</script>
<style scoped>
.sidebar {
width: 160rpx;
height: 100%;
background: #f8f8f8;
}
.sidebar-item {
position: relative;
padding: 28rpx 16rpx;
font-size: 26rpx;
color: #666666;
text-align: center;
line-height: 1.4;
}
.sidebar-item.active {
background: #ffffff;
color: #1a1a1a;
font-weight: 500;
}
.sidebar-indicator {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 32rpx;
background: #ff6800;
border-radius: 0 4rpx 4rpx 0;
}
.sidebar-promo {
margin: 24rpx 16rpx;
padding: 24rpx 16rpx;
background: linear-gradient(135deg, #ff6800 0%, #ff8c3a 100%);
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.promo-title {
font-size: 26rpx;
font-weight: 700;
color: #ffffff;
}
.promo-desc {
margin-top: 8rpx;
font-size: 20rpx;
color: rgba(255, 255, 255, 0.9);
}
</style>
+74
View File
@@ -0,0 +1,74 @@
<template>
<!-- 国家选择条 - 横向滚动 -->
<scroll-view scroll-x class="country-bar" show-scrollbar="false">
<view class="country-inner">
<view
class="country-item"
:class="{ active: selectedId === item.id }"
v-for="(item, index) in list"
:key="index"
@tap="selectItem(item)"
>
<text class="country-flag">{{ item.flag }}</text>
<text class="country-name">{{ item.name }}</text>
</view>
</view>
</scroll-view>
</template>
<script>
export default {
name: 'CountryBar',
props: {
list: {
type: Array,
default: () => []
},
selectedId: {
type: [String, Number],
default: ''
}
},
methods: {
selectItem(item) {
this.$emit('select', item.id)
}
}
}
</script>
<style scoped>
.country-bar {
width: 100%;
white-space: nowrap;
background: #ffffff;
padding: 16rpx 0;
border-bottom: 2rpx solid #f5f5f5;
}
.country-inner {
display: inline-flex;
gap: 16rpx;
padding: 0 32rpx;
}
.country-item {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 10rpx;
padding: 12rpx 24rpx;
background: #f5f5f5;
border-radius: 30rpx;
font-size: 24rpx;
color: #666666;
}
.country-item.active {
background: #ff6800;
color: #ffffff;
}
.country-flag {
font-size: 28rpx;
}
.country-name {
font-size: 24rpx;
}
</style>
+210
View File
@@ -0,0 +1,210 @@
<template>
<!-- 筛选弹窗 -->
<view>
<view
class="filter-overlay"
:class="{ show: visible }"
@tap="onClose"
></view>
<view class="filter-drawer" :class="{ show: visible }">
<view class="filter-drawer-header">
<text class="filter-drawer-title">筛选条件</text>
<text class="filter-drawer-close" @tap="onClose"></text>
</view>
<scroll-view scroll-y class="filter-drawer-body">
<!-- 动态标签筛选组 -->
<view class="filter-group" v-for="(group, gIdx) in groups" :key="gIdx">
<text class="filter-group-title">{{ group.title }}</text>
<view class="filter-options">
<view
class="filter-option"
:class="{ selected: isSelected(group, tag.id) }"
v-for="(tag, tIdx) in group.tags"
:key="tIdx"
@tap="onToggle(gIdx, tag.id)"
>
{{ tag.name }}
</view>
</view>
</view>
<!-- 价格区间 -->
<view class="filter-group">
<text class="filter-group-title">价格区间</text>
<view class="filter-price-range">
<input class="price-input" placeholder="最低价" type="number" />
<text class="price-divider">-</text>
<input class="price-input" placeholder="最高价" type="number" />
</view>
</view>
</scroll-view>
<view class="filter-drawer-footer">
<view class="filter-reset" @tap="onReset">重置</view>
<view class="filter-confirm" @tap="onConfirm">确定</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'FilterDrawer',
props: {
visible: {
type: Boolean,
default: false
},
groups: {
type: Array,
default: () => []
}
},
methods: {
isSelected(group, tagId) {
return (group.selected || []).indexOf(tagId) > -1
},
onToggle(gIdx, tagId) {
this.$emit('toggle-tag', { groupIndex: gIdx, tagId })
},
onReset() {
this.$emit('reset')
},
onConfirm() {
this.$emit('confirm')
},
onClose() {
this.$emit('close')
}
}
}
</script>
<style scoped>
.filter-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 998;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
}
.filter-overlay.show {
opacity: 1;
visibility: visible;
}
.filter-drawer {
position: fixed;
top: 0;
right: -600rpx;
width: 600rpx;
height: 100%;
background: #ffffff;
z-index: 999;
display: flex;
flex-direction: column;
transition: right 0.3s;
}
.filter-drawer.show {
right: 0;
}
.filter-drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 2rpx solid #f5f5f5;
}
.filter-drawer-title {
font-size: 32rpx;
font-weight: 600;
color: #1a1a1a;
}
.filter-drawer-close {
font-size: 32rpx;
color: #999999;
}
.filter-drawer-body {
flex: 1;
padding: 0 32rpx;
}
.filter-group {
padding: 32rpx 0;
border-bottom: 2rpx solid #f5f5f5;
}
.filter-group-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 20rpx;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.filter-option {
padding: 14rpx 28rpx;
font-size: 24rpx;
color: #666666;
background: #f5f5f5;
border-radius: 30rpx;
border: 2rpx solid transparent;
}
.filter-option.selected {
background: #fff0e5;
color: #ff6800;
border-color: #ff6800;
}
.filter-price-range {
display: flex;
align-items: center;
gap: 20rpx;
}
.price-input {
flex: 1;
height: 72rpx;
padding: 0 20rpx;
background: #f5f5f5;
border-radius: 12rpx;
font-size: 26rpx;
color: #333333;
text-align: center;
}
.price-divider {
font-size: 28rpx;
color: #999999;
}
.filter-drawer-footer {
display: flex;
gap: 24rpx;
padding: 24rpx 32rpx;
border-top: 2rpx solid #f5f5f5;
}
.filter-reset {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 40rpx;
font-size: 28rpx;
color: #666666;
}
.filter-confirm {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
background: #ff6800;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
}
</style>
+307
View File
@@ -0,0 +1,307 @@
<template>
<!-- 产品网格列表 + 子分类 + 分页 -->
<scroll-view scroll-y class="product-list-area" @scrolltolower="onLoadMore">
<!-- 子分类 -->
<view class="sub-categories" v-if="subCategories.length > 0">
<view
class="sub-cat-item"
:class="{ active: activeSubId === item.id }"
v-for="(item, index) in subCategories"
:key="index"
@tap="selectSub(item.id)"
>
{{ item.name }}
</view>
</view>
<!-- 产品网格 -->
<view class="product-grid">
<view
class="product-card"
v-for="(item, index) in list"
:key="index"
@tap="onTapProduct(item)"
>
<view class="product-img">
<image
class="product-image"
:src="item.image"
mode="aspectFill"
v-if="item.image"
></image>
<view class="product-badges">
<view
class="product-badge"
:style="{ background: tag.bg, color: tag.color }"
v-for="(tag, tIdx) in item.tags"
:key="tIdx"
>
{{ tag.text }}
</view>
</view>
</view>
<view class="product-info">
<text class="product-name">{{ item.name }}</text>
<view class="product-bottom">
<text class="product-price">
¥{{ item.price }}
<text class="product-price-unit"></text>
</text>
</view>
</view>
</view>
</view>
<!-- 分页 -->
<view class="pagination" v-if="total > 0">
<text class="page-info">显示 {{ start }}-{{ end }} {{ total }} 个产品</text>
<view class="page-controls">
<view
class="page-btn"
:class="{ disabled: current === 1 }"
@tap="onPrev"
>
上一页
</view>
<view
class="page-num"
:class="{ active: current === item }"
v-for="(item, index) in pageNumbers"
:key="index"
@tap="onGoPage(item)"
>
{{ item }}
</view>
<view
class="page-btn"
:class="{ disabled: current === totalPages }"
@tap="onNext"
>
下一页
</view>
</view>
<view class="page-size">
<text>{{ pageSize }}/</text>
</view>
</view>
<!-- 加载更多提示 -->
<view class="loading-more" v-if="loading">
<text>加载中...</text>
</view>
</scroll-view>
</template>
<script>
export default {
name: 'ProductGrid',
props: {
list: {
type: Array,
default: () => []
},
subCategories: {
type: Array,
default: () => []
},
activeSubId: {
type: [String, Number],
default: ''
},
total: {
type: Number,
default: 0
},
current: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
totalPages: {
type: Number,
default: 1
},
pageNumbers: {
type: Array,
default: () => []
},
start: {
type: Number,
default: 1
},
end: {
type: Number,
default: 10
},
loading: {
type: Boolean,
default: false
}
},
methods: {
selectSub(id) {
this.$emit('sub-select', id)
},
onTapProduct(item) {
this.$emit('product-click', item)
},
onPrev() {
if (this.current > 1) {
this.$emit('page-change', this.current - 1)
}
},
onNext() {
if (this.current < this.totalPages) {
this.$emit('page-change', this.current + 1)
}
},
onGoPage(page) {
this.$emit('page-change', page)
},
onLoadMore() {
this.$emit('load-more')
}
}
}
</script>
<style scoped>
.product-list-area {
flex: 1;
height: 100%;
background: #ffffff;
}
.sub-categories {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 20rpx 24rpx;
border-bottom: 2rpx solid #f5f5f5;
}
.sub-cat-item {
padding: 10rpx 24rpx;
font-size: 24rpx;
color: #666666;
background: #f5f5f5;
border-radius: 24rpx;
}
.sub-cat-item.active {
background: #fff0e5;
color: #ff6800;
}
.product-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 20rpx 24rpx;
}
.product-card {
width: calc(50% - 8rpx);
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
border: 2rpx solid #f5f5f5;
}
.product-img {
position: relative;
width: 100%;
height: 260rpx;
background: #f5f5f5;
}
.product-image {
width: 100%;
height: 100%;
}
.product-badges {
position: absolute;
top: 12rpx;
left: 12rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.product-badge {
padding: 4rpx 12rpx;
border-radius: 8rpx;
font-size: 18rpx;
}
.product-info {
padding: 16rpx;
}
.product-name {
display: block;
font-size: 24rpx;
color: #1a1a1a;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-bottom {
margin-top: 10rpx;
}
.product-price {
font-size: 28rpx;
font-weight: 700;
color: #ff6800;
}
.product-price-unit {
font-size: 20rpx;
font-weight: 400;
color: #ff6800;
}
.pagination {
padding: 32rpx 24rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.page-info {
font-size: 22rpx;
color: #999999;
}
.page-controls {
display: flex;
align-items: center;
gap: 12rpx;
}
.page-btn {
padding: 10rpx 20rpx;
font-size: 24rpx;
color: #666666;
background: #f5f5f5;
border-radius: 8rpx;
}
.page-btn.disabled {
color: #cccccc;
}
.page-num {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #666666;
background: #f5f5f5;
border-radius: 8rpx;
}
.page-num.active {
background: #ff6800;
color: #ffffff;
}
.page-size {
font-size: 22rpx;
color: #999999;
}
.loading-more {
padding: 32rpx;
text-align: center;
font-size: 24rpx;
color: #999999;
}
</style>
+85
View File
@@ -0,0 +1,85 @@
<template>
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input-wrap">
<text class="search-icon">🔍</text>
<input
class="search-input"
placeholder="搜索商品"
placeholder-class="search-placeholder"
:value="keyword"
@input="onInput"
/>
</view>
<view class="filter-btn" @tap="onFilter">
<text class="filter-icon"></text>
<text class="filter-text">筛选</text>
</view>
</view>
</template>
<script>
export default {
name: 'SearchBar',
props: {
keyword: {
type: String,
default: ''
}
},
methods: {
onInput(e) {
this.$emit('input', e.detail.value)
},
onFilter() {
this.$emit('filter')
}
}
}
</script>
<style scoped>
.search-bar {
display: flex;
align-items: center;
gap: 20rpx;
padding: 20rpx 32rpx;
background: #ffffff;
border-bottom: 2rpx solid #f5f5f5;
}
.search-input-wrap {
flex: 1;
display: flex;
align-items: center;
height: 72rpx;
padding: 0 24rpx;
background: #f5f5f5;
border-radius: 36rpx;
}
.search-icon {
font-size: 28rpx;
margin-right: 12rpx;
}
.search-input {
flex: 1;
font-size: 26rpx;
color: #333333;
}
.search-placeholder {
color: #999999;
}
.filter-btn {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 20rpx;
font-size: 26rpx;
color: #666666;
}
.filter-icon {
font-size: 28rpx;
}
.filter-text {
font-size: 26rpx;
}
</style>
+146
View File
@@ -0,0 +1,146 @@
// 货盘页 mock 数据(用于离线预览,实际数据从 API 获取)
export const mockCountries = [
{ id: '45', name: '美国', flag: '🇺🇸' },
{ id: '76', name: '日本', flag: '🇯🇵' },
{ id: '484', name: '墨西哥', flag: '🇲🇽' },
{ id: '826', name: '英国', flag: '🇬🇧' },
{ id: '276', name: '德国', flag: '🇩🇪' },
{ id: '250', name: '法国', flag: '🇫🇷' },
{ id: '380', name: '意大利', flag: '🇮🇹' },
{ id: '124', name: '加拿大', flag: '🇨🇦' },
{ id: '36', name: '澳大利亚', flag: '🇦🇺' },
{ id: '410', name: '韩国', flag: '🇰🇷' },
{ id: '152', name: '智利', flag: '🇨🇱' },
{ id: '604', name: '秘鲁', flag: '🇵🇪' }
]
export const mockCategories = [
{ id: 'all', name: '全部商品', children: [], productCount: 0 },
{
id: '1',
name: '男士服装',
productCount: 159,
children: [
{ id: '101', categoryName: 'T恤' },
{ id: '102', categoryName: '卫衣' },
{ id: '103', categoryName: 'POLO衫' }
]
},
{
id: '2',
name: '女士服装',
productCount: 36,
children: [
{ id: '201', categoryName: '连衣裙' },
{ id: '202', categoryName: 'T恤' }
]
},
{
id: '3',
name: '童装',
productCount: 40,
children: [
{ id: '301', categoryName: '儿童T恤' },
{ id: '302', categoryName: '儿童卫衣' }
]
},
{
id: '4',
name: '家居配饰',
productCount: 3,
children: [
{ id: '401', categoryName: '帆布包' },
{ id: '402', categoryName: '马克杯' }
]
}
]
export const mockFilterGroups = [
{
id: '1',
title: '物流渠道',
tags: [
{ id: 't1', name: '包邮', color: '#ff6800', fontColor: '#ffffff', productCount: 84 },
{ id: 't2', name: '不包邮', color: '#999999', fontColor: '#ffffff', productCount: 154 }
],
selected: []
},
{
id: '2',
title: '印刷位置',
tags: [
{ id: 't3', name: '单面印', color: '#00b894', fontColor: '#ffffff', productCount: 12 },
{ id: 't4', name: '双面印', color: '#6c5ce7', fontColor: '#ffffff', productCount: 14 }
],
selected: []
},
{
id: '3',
title: '印刷工艺',
tags: [
{ id: 't5', name: '烫画', color: '#e17055', fontColor: '#ffffff', productCount: 8 },
{ id: 't6', name: '直喷', color: '#00cec9', fontColor: '#ffffff', productCount: 3 }
],
selected: []
}
]
export const mockProducts = [
{
id: 1,
name: '180G牛奶丝T恤 DG120',
price: '28.00',
image: '/static/images/products/1.png',
tags: [
{ text: '包邮', bg: '#ff6800', color: '#ffffff' },
{ text: '烫画', bg: '#e17055', color: '#ffffff' }
]
},
{
id: 2,
name: '纯棉连帽卫衣 HD200',
price: '59.00',
image: '/static/images/products/2.png',
tags: [
{ text: '不包邮', bg: '#999999', color: '#ffffff' }
]
},
{
id: 3,
name: '帆布托特包 BG150',
price: '15.00',
image: '/static/images/products/3.png',
tags: [
{ text: '单面印', bg: '#00b894', color: '#ffffff' }
]
},
{
id: 4,
name: '儿童纯棉T恤 KD100',
price: '22.00',
image: '/static/images/products/4.png',
tags: [
{ text: '包邮', bg: '#ff6800', color: '#ffffff' },
{ text: '直喷', bg: '#00cec9', color: '#ffffff' }
]
},
{
id: 5,
name: '重磅圆领T恤 DG150',
price: '35.00',
image: '/static/images/products/5.png',
tags: [
{ text: '双面印', bg: '#6c5ce7', color: '#ffffff' }
]
},
{
id: 6,
name: '拉链卫衣 HD220',
price: '79.00',
image: '/static/images/products/6.png',
tags: [
{ text: '包邮', bg: '#ff6800', color: '#ffffff' }
]
}
]
+9 -497
View File
@@ -1,502 +1,14 @@
/* 产品列表页样式 */
/* 货盘页 - 页面容器样式 */
/* 各区块样式已移入 pages/products/components/ 下对应组件 */
.products-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
height: 100vh;
background: #f8f8f8;
}
/* 搜索栏 */
.search-bar {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx 24rpx;
background: #ffffff;
}
.search-input-wrap {
flex: 1;
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 32rpx;
padding: 0 24rpx;
height: 64rpx;
}
.search-icon {
font-size: 28rpx;
margin-right: 12rpx;
}
.search-input {
flex: 1;
font-size: 26rpx;
color: #333;
}
.search-placeholder {
color: #ccc;
}
.filter-btn {
display: flex;
align-items: center;
gap: 4rpx;
padding: 0 16rpx;
}
.filter-icon {
font-size: 28rpx;
color: #666;
}
.filter-text {
font-size: 24rpx;
color: #666;
}
/* 国家选择条 */
.country-bar {
white-space: nowrap;
background: #ffffff;
padding: 16rpx 24rpx;
border-bottom: 1rpx solid #eee;
}
.country-item {
display: inline-flex;
align-items: center;
gap: 6rpx;
padding: 8rpx 20rpx;
margin-right: 12rpx;
border-radius: 24rpx;
background: #f5f5f5;
}
.country-item.active {
background: #fff0e5;
}
.country-flag {
font-size: 28rpx;
}
.country-name {
font-size: 22rpx;
color: #666;
}
.country-item.active .country-name {
color: #ff6800;
font-weight: 500;
}
/* 已选筛选标签 */
.active-filters {
display: flex;
align-items: center;
gap: 12rpx;
padding: 12rpx 24rpx;
background: #ffffff;
overflow-x: auto;
white-space: nowrap;
}
.active-filter-tag {
display: inline-flex;
align-items: center;
gap: 8rpx;
background: #f2f2f2;
border-radius: 8rpx;
padding: 6rpx 16rpx;
font-size: 22rpx;
color: #666;
flex-shrink: 0;
}
.filter-remove {
font-size: 20rpx;
color: #ccc;
}
.clear-all {
font-size: 22rpx;
color: #ff6800;
flex-shrink: 0;
}
/* 主体内容 */
.content-body {
flex: 1;
display: flex;
overflow: hidden;
}
/* 左侧分类侧边栏 */
.sidebar {
width: 180rpx;
background: #ffffff;
border-right: 1rpx solid #eee;
height: 100%;
flex-shrink: 0;
}
.sidebar-item {
position: relative;
padding: 28rpx 20rpx;
font-size: 24rpx;
color: #666;
text-align: center;
}
.sidebar-item.active {
background: #f5f5f5;
color: #ff6800;
font-weight: 600;
}
.sidebar-indicator {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 32rpx;
background: #ff6800;
border-radius: 0 4rpx 4rpx 0;
}
.sidebar-promo {
margin: 24rpx 16rpx;
padding: 20rpx 12rpx;
background: linear-gradient(135deg, #ff6800 0%, #ff8c40 100%);
border-radius: 12rpx;
text-align: center;
}
.promo-title {
font-size: 24rpx;
font-weight: 700;
color: #fff;
display: block;
}
.promo-desc {
font-size: 20rpx;
color: rgba(255, 255, 255, 0.9);
display: block;
margin-top: 4rpx;
}
/* 右侧产品列表区 */
.product-list-area {
flex: 1;
height: 100%;
}
/* 子分类 */
.sub-categories {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
padding: 16rpx 20rpx;
background: #ffffff;
}
.sub-cat-item {
font-size: 22rpx;
padding: 8rpx 20rpx;
border-radius: 24rpx;
background: #f5f5f5;
color: #666;
}
.sub-cat-item.active {
background: #ff6800;
color: #fff;
}
/* 产品网格 */
.product-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 16rpx 20rpx;
}
.product-card {
width: calc(50% - 8rpx);
background: #ffffff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.product-img {
width: 100%;
height: 300rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
background: #f5f5f5;
}
.product-image {
width: 100%;
height: 100%;
}
.product-badges {
position: absolute;
top: 12rpx;
left: 12rpx;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.product-badge {
font-size: 18rpx;
padding: 2rpx 8rpx;
border-radius: 4rpx;
}
.product-info {
padding: 16rpx;
}
.product-name {
font-size: 24rpx;
color: #333;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-bottom {
margin-top: 8rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.product-price {
font-size: 30rpx;
font-weight: 700;
color: #ff6800;
}
.product-price-unit {
font-size: 20rpx;
font-weight: 400;
margin-left: 2rpx;
}
/* 分页 */
.pagination {
padding: 24rpx 20rpx;
text-align: center;
}
.page-info {
font-size: 22rpx;
color: #999;
display: block;
margin-bottom: 16rpx;
}
.page-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
}
.page-btn {
padding: 8rpx 20rpx;
font-size: 22rpx;
color: #666;
background: #fff;
border-radius: 8rpx;
border: 1rpx solid #eee;
}
.page-btn.disabled {
color: #ccc;
}
.page-num {
min-width: 48rpx;
height: 48rpx;
line-height: 48rpx;
text-align: center;
font-size: 24rpx;
color: #666;
background: #fff;
border-radius: 8rpx;
border: 1rpx solid #eee;
}
.page-num.active {
background: #ff6800;
color: #fff;
border-color: #ff6800;
}
.page-size {
margin-top: 12rpx;
font-size: 22rpx;
color: #999;
}
.loading-more {
padding: 24rpx;
text-align: center;
font-size: 24rpx;
color: #999;
}
/* 筛选弹窗 */
.filter-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
}
.filter-overlay.show {
opacity: 1;
visibility: visible;
}
.filter-drawer {
position: fixed;
top: 0;
right: 0;
width: 80%;
height: 100%;
background: #ffffff;
z-index: 1001;
transform: translateX(100%);
transition: transform 0.3s;
display: flex;
flex-direction: column;
}
.filter-drawer.show {
transform: translateX(0);
}
.filter-drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx 24rpx;
border-bottom: 1rpx solid #eee;
}
.filter-drawer-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.filter-drawer-close {
font-size: 32rpx;
color: #999;
}
.filter-drawer-body {
flex: 1;
padding: 24rpx;
}
.filter-group {
margin-bottom: 32rpx;
}
.filter-group-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
display: block;
margin-bottom: 16rpx;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.filter-option {
padding: 12rpx 24rpx;
font-size: 24rpx;
color: #666;
background: #f5f5f5;
border-radius: 8rpx;
border: 2rpx solid transparent;
}
.filter-option.selected {
background: #fff0e5;
color: #ff6800;
border-color: #ff6800;
}
.filter-price-range {
display: flex;
align-items: center;
gap: 16rpx;
}
.price-input {
flex: 1;
height: 64rpx;
background: #f5f5f5;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 24rpx;
}
.price-divider {
color: #ccc;
}
.filter-drawer-footer {
display: flex;
gap: 16rpx;
padding: 24rpx;
border-top: 1rpx solid #eee;
}
.filter-reset {
flex: 1;
text-align: center;
padding: 24rpx;
font-size: 28rpx;
color: #666;
background: #f5f5f5;
border-radius: 12rpx;
}
.filter-confirm {
flex: 1;
text-align: center;
padding: 24rpx;
font-size: 28rpx;
color: #fff;
background: #ff6800;
border-radius: 12rpx;
flex: 1;
display: flex;
overflow: hidden;
}
File diff suppressed because it is too large Load Diff