首次提交

This commit is contained in:
2026-08-20 18:29:49 +08:00
commit 76827aedfe
77 changed files with 24937 additions and 0 deletions
+500
View File
@@ -0,0 +1,500 @@
/* 产品列表页样式 */
.products-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 搜索栏 */
.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;
}
.product-emoji {
font-size: 80rpx;
}
.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;
}
+760
View File
@@ -0,0 +1,760 @@
<template>
<!-- 产品列表页 - 印美达 Inkreach -->
<view class="products-page">
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input-wrap">
<text class="search-icon">🔍</text>
<input class="search-input" placeholder="搜索商品" placeholder-class="search-placeholder" @input="onSearchInput" :value="searchKeyword" />
</view>
<view class="filter-btn" @tap="toggleFilter">
<text class="filter-icon"></text>
<text class="filter-text">筛选</text>
</view>
</view>
<!-- 国家选择条 -->
<scroll-view class="country-bar" :scroll-x="true" :enhanced="true" :show-scrollbar="false">
<view :class="'country-item ' + (selectedCountry === item.id ? 'active' : '')" @tap="selectCountry" :data-id="item.id" v-for="(item, index) in countries" :key="index">
<text class="country-flag">{{ item.flag }}</text>
<text class="country-name">{{ item.name }}</text>
</view>
</scroll-view>
<!-- 已选筛选标签 -->
<view class="active-filters" v-if="activeFilters.length > 0">
<view class="active-filter-tag" v-for="(item, index) in activeFilters" :key="index">
<text>{{ item.label }}</text>
<text class="filter-remove" @tap="removeFilter" :data-key="item.key"></text>
</view>
<view class="clear-all" @tap="clearAllFilters">清除全部</view>
</view>
<!-- 主体内容侧边栏 + 产品列表 -->
<view class="content-body">
<!-- 左侧分类侧边栏 -->
<scroll-view class="sidebar" :scroll-y="true">
<view
:class="'sidebar-item ' + (activeCategory === item.id ? 'active' : '')"
@tap="selectCategory"
:data-id="item.id"
v-for="(item, index) in categories"
:key="index"
>
<view class="sidebar-indicator" v-if="activeCategory === 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>
<!-- 右侧产品列表 -->
<scroll-view class="product-list-area" :scroll-y="true" @scrolltolower="loadMore">
<!-- 子分类 -->
<view class="sub-categories" v-if="subCategories.length > 0">
<view
:class="'sub-cat-item ' + (activeSubCategory === item.id ? 'active' : '')"
@tap="selectSubCategory"
:data-id="item.id"
v-for="(item, index) in subCategories"
:key="index"
>
{{ item.name }}
</view>
</view>
<!-- 产品网格 -->
<view class="product-grid">
<view class="product-card" @tap="goDetail" :data-id="item.id" v-for="(item, index) in products" :key="index">
<view class="product-img" :style="'background: ' + item.bgColor + ';'">
<text class="product-emoji">{{ item.emoji }}</text>
<view class="product-badges">
<view class="product-badge" :style="'background: ' + tag.bg + '; color: ' + tag.color + ';'" v-for="(tag, index1) in item.tags" :key="index1">
{{ 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">
<text class="page-info">显示 {{ pagination.start }}-{{ pagination.end }} {{ pagination.total }} 个产品</text>
<view class="page-controls">
<view :class="'page-btn ' + (pagination.current === 1 ? 'disabled' : '')" @tap="changePage" data-action="prev">上一页</view>
<view
:class="'page-num ' + (pagination.current === item ? 'active' : '')"
@tap="goToPage"
:data-page="item"
v-for="(item, index) in pageNumbers"
:key="index"
>
{{ item }}
</view>
<view :class="'page-btn ' + (pagination.current === pagination.totalPages ? 'disabled' : '')" @tap="changePage" data-action="next">下一页</view>
</view>
<view class="page-size">
<text>{{ pagination.pageSize }}/</text>
</view>
</view>
<!-- 加载更多提示 -->
<view class="loading-more" v-if="loadingMore">
<text>加载中...</text>
</view>
</scroll-view>
</view>
<!-- 筛选弹窗 -->
<view :class="'filter-overlay ' + (showFilter ? 'show' : '')" @tap="closeFilter"></view>
<view :class="'filter-drawer ' + (showFilter ? 'show' : '')">
<view class="filter-drawer-header">
<text class="filter-drawer-title">筛选条件</text>
<text class="filter-drawer-close" @tap="closeFilter"></text>
</view>
<scroll-view class="filter-drawer-body" :scroll-y="true">
<!-- 工艺 -->
<view class="filter-group">
<text class="filter-group-title">工艺</text>
<view class="filter-options">
<view
:class="'filter-option ' + (selectedCrafts.indexOf(item) > -1 ? 'selected' : '')"
@tap="toggleCraft"
:data-value="item"
v-for="(item, index) in craftOptions"
:key="index"
>
{{ item }}
</view>
</view>
</view>
<!-- 材质 -->
<view class="filter-group">
<text class="filter-group-title">材质</text>
<view class="filter-options">
<view
:class="'filter-option ' + (selectedMaterials.indexOf(item) > -1 ? 'selected' : '')"
@tap="toggleMaterial"
:data-value="item"
v-for="(item, index) in materialOptions"
:key="index"
>
{{ item }}
</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="resetFilters">重置</view>
<view class="filter-confirm" @tap="confirmFilters">确定</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
searchKeyword: '',
selectedCountry: 'us',
countries: [
{
id: 'us',
name: '美国',
flag: '🇺🇸'
},
{
id: 'uk',
name: '英国',
flag: '🇬🇧'
},
{
id: 'jp',
name: '日本',
flag: '🇯🇵'
},
{
id: 'mx',
name: '墨西哥',
flag: '🇲🇽'
},
{
id: 'br',
name: '巴西',
flag: '🇧🇷'
},
{
id: 'ae',
name: '中东',
flag: '🇦🇪'
},
{
id: 'pl',
name: '波兰',
flag: '🇵🇱'
},
{
id: 'es',
name: '西班牙',
flag: '🇪🇸'
},
{
id: 'de',
name: '德国',
flag: '🇩🇪'
},
{
id: 'it',
name: '意大利',
flag: '🇮🇹'
},
{
id: 'fr',
name: '法国',
flag: '🇫🇷'
},
{
id: 'ca',
name: '加拿大',
flag: '🇨🇦'
},
{
id: 'au',
name: '澳大利亚',
flag: '🇦🇺'
},
{
id: 'kr',
name: '韩国',
flag: '🇰🇷'
}
],
activeCategory: 'all',
categories: [
{
id: 'all',
name: '全部商品'
},
{
id: 'men',
name: '男士服装'
},
{
id: 'women',
name: '女士服装'
},
{
id: 'kids',
name: '儿童服装'
},
{
id: 'home',
name: '家居配饰'
},
{
id: 'bag',
name: '包包配饰'
}
],
subCategories: [],
activeSubCategory: '',
activeFilters: [
{
key: 'country',
label: '美国'
},
{
key: 'craft',
label: '烫画'
},
{
key: 'material',
label: '棉麻'
}
],
showFilter: false,
selectedCrafts: ['烫画'],
selectedMaterials: ['棉麻'],
craftOptions: ['烫画', '刺绣', '丝印', '数码直喷', '热转印'],
materialOptions: ['棉麻', '纯棉', '涤纶', '混纺', '牛奶丝'],
pagination: {
start: 1,
end: 10,
total: 96,
current: 1,
pageSize: 10,
totalPages: 10
},
pageNumbers: [1, 2, 3, 4, 5],
loadingMore: false,
products: [
{
id: 1,
name: '180G牛奶丝T恤 DG120',
price: '28.00',
bgColor: '#F5F5F5',
emoji: '👕',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
},
{
text: '现货',
bg: '#E5F5FF',
color: '#1890FF'
}
]
},
{
id: 2,
name: '纯棉连帽卫衣 HD200',
price: '59.00',
bgColor: '#E8E8E8',
emoji: '🧥',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
},
{
text: '快返',
bg: '#F0FFE5',
color: '#52C41A'
}
]
},
{
id: 3,
name: '260G重装T恤 TS260',
price: '35.00',
bgColor: '#F0F0F0',
emoji: '👕',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
}
]
},
{
id: 4,
name: '精梳棉Polo衫 PL100',
price: '45.00',
bgColor: '#F8F8F8',
emoji: '👔',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
},
{
text: '现货',
bg: '#E5F5FF',
color: '#1890FF'
}
]
},
{
id: 5,
name: '抓绒卫裤 PT150',
price: '42.00',
bgColor: '#F5F5F5',
emoji: '👖',
tags: [
{
text: '快返',
bg: '#F0FFE5',
color: '#52C41A'
}
]
},
{
id: 6,
name: '运动速干T恤 ST200',
price: '32.00',
bgColor: '#E8E8E8',
emoji: '👕',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
}
]
},
{
id: 7,
name: '纯棉背包 BP300',
price: '25.00',
bgColor: '#F0F0F0',
emoji: '🎒',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
},
{
text: '现货',
bg: '#E5F5FF',
color: '#1890FF'
}
]
},
{
id: 8,
name: '儿童纯棉T恤 KD100',
price: '22.00',
bgColor: '#F8F8F8',
emoji: '👶',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
}
]
},
{
id: 9,
name: '帆布托特包 BG150',
price: '15.00',
bgColor: '#F5F5F5',
emoji: '👜',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
},
{
text: '快返',
bg: '#F0FFE5',
color: '#52C41A'
}
]
},
{
id: 10,
name: '针织开衫 CR180',
price: '55.00',
bgColor: '#E8E8E8',
emoji: '🧶',
tags: [
{
text: '可定制',
bg: '#FFF0E5',
color: '#FF6800'
}
]
}
],
tag: {
bg: '',
color: '',
text: ''
}
};
},
onLoad() {
this.loadSubCategories('all');
},
methods: {
loadSubCategories(catId) {
const subCatMap = {
all: [],
men: [
{
id: 'tops',
name: '上装'
},
{
id: 'hoodie',
name: '卫衣'
},
{
id: 'tshirt',
name: 'T恤'
},
{
id: 'shirt',
name: '衬衫'
},
{
id: 'pants',
name: '裤装'
},
{
id: 'underwear',
name: '内衣'
}
],
women: [
{
id: 'dress',
name: '连衣裙'
},
{
id: 'tops',
name: '上装'
},
{
id: 'bottoms',
name: '下装'
}
],
kids: [
{
id: 'baby',
name: '婴儿装'
},
{
id: 'kids',
name: '儿童装'
}
],
home: [
{
id: 'blanket',
name: '毛毯'
},
{
id: 'pillow',
name: '抱枕'
}
],
bag: [
{
id: 'tote',
name: '托特包'
},
{
id: 'backpack',
name: '背包'
}
]
};
this.setData({
subCategories: subCatMap[catId] || [],
activeSubCategory: subCatMap[catId]?.[0]?.id || ''
});
},
onSearchInput(e) {
this.setData({
searchKeyword: e.detail.value
});
},
selectCountry(e) {
const id = e.currentTarget.dataset.id;
const country = this.countries.find((c) => c.id === id);
const filters = this.activeFilters.filter((f) => f.key !== 'country');
filters.push({
key: 'country',
label: country.name
});
this.setData({
selectedCountry: id,
activeFilters: filters
});
},
selectCategory(e) {
const id = e.currentTarget.dataset.id;
this.setData({
activeCategory: id
});
this.loadSubCategories(id);
},
selectSubCategory(e) {
this.setData({
activeSubCategory: e.currentTarget.dataset.id
});
},
toggleFilter() {
this.setData({
showFilter: !this.showFilter
});
},
closeFilter() {
this.setData({
showFilter: false
});
},
toggleCraft(e) {
const value = e.currentTarget.dataset.value;
let crafts = [...this.selectedCrafts];
const idx = crafts.indexOf(value);
if (idx > -1) {
crafts.splice(idx, 1);
} else {
crafts.push(value);
}
this.setData({
selectedCrafts: crafts
});
},
toggleMaterial(e) {
const value = e.currentTarget.dataset.value;
let materials = [...this.selectedMaterials];
const idx = materials.indexOf(value);
if (idx > -1) {
materials.splice(idx, 1);
} else {
materials.push(value);
}
this.setData({
selectedMaterials: materials
});
},
removeFilter(e) {
const key = e.currentTarget.dataset.key;
const filters = this.activeFilters.filter((f) => f.key !== key);
this.setData({
activeFilters: filters
});
},
clearAllFilters() {
this.setData({
activeFilters: []
});
},
resetFilters() {
this.setData({
selectedCrafts: [],
selectedMaterials: []
});
},
confirmFilters() {
const filters = [];
if (this.selectedCrafts.length > 0) {
filters.push({
key: 'craft',
label: this.selectedCrafts.join(',')
});
}
if (this.selectedMaterials.length > 0) {
filters.push({
key: 'material',
label: this.selectedMaterials.join(',')
});
}
this.setData({
activeFilters: filters,
showFilter: false
});
},
goDetail(e) {
const id = e.currentTarget.dataset.id;
uni.navigateTo({
url: `/pages/product-detail/product-detail?id=${id}`
});
},
changePage(e) {
const action = e.currentTarget.dataset.action;
let current = this.pagination.current;
if (action === 'prev' && current > 1) {
current--;
}
if (action === 'next' && current < this.pagination.totalPages) {
current++;
}
this.updatePagination(current);
},
goToPage(e) {
this.updatePagination(e.currentTarget.dataset.page);
},
updatePagination(current) {
const total = this.pagination.total;
const pageSize = this.pagination.pageSize;
const totalPages = Math.ceil(total / pageSize);
let pageNumbers = [];
for (let i = Math.max(1, current - 2); i <= Math.min(totalPages, current + 2); i++) {
pageNumbers.push(i);
}
this.setData({
pagination: {
...this.pagination,
current,
start: (current - 1) * pageSize + 1,
end: Math.min(current * pageSize, total),
totalPages
},
pageNumbers
});
},
loadMore() {
if (this.loadingMore) {
return;
}
this.setData({
loadingMore: true
});
setTimeout(() => {
this.setData({
loadingMore: false
});
}, 1000);
}
}
};
</script>
<style>
@import './products.css';
</style>