forked from Qi/Oroqen-Vue
582 lines
13 KiB
Vue
582 lines
13 KiB
Vue
|
<template>
|
|||
|
<div class="products">
|
|||
|
<!-- 页面头部 -->
|
|||
|
<section class="page-header">
|
|||
|
<div class="container">
|
|||
|
<div class="header-content">
|
|||
|
<h1 class="page-title">特色产品</h1>
|
|||
|
<p class="page-subtitle">精选鄂伦春族传统手工艺品与文创产品</p>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</section>
|
|||
|
|
|||
|
<!-- 产品筛选 -->
|
|||
|
<section class="filters-section">
|
|||
|
<div class="container">
|
|||
|
<div class="filters">
|
|||
|
<div class="filter-group">
|
|||
|
<label>产品分类:</label>
|
|||
|
<div class="filter-buttons">
|
|||
|
<button
|
|||
|
v-for="category in categories"
|
|||
|
:key="category.id"
|
|||
|
:class="['filter-btn', { active: selectedCategory === category.id }]"
|
|||
|
@click="selectedCategory = category.id"
|
|||
|
>
|
|||
|
{{ category.name }}
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="filter-group">
|
|||
|
<label>价格范围:</label>
|
|||
|
<el-select v-model="priceRange" placeholder="选择价格范围" style="width: 200px;">
|
|||
|
<el-option label="全部价格" value="all" />
|
|||
|
<el-option label="100元以下" value="0-100" />
|
|||
|
<el-option label="100-300元" value="100-300" />
|
|||
|
<el-option label="300-500元" value="300-500" />
|
|||
|
<el-option label="500元以上" value="500+" />
|
|||
|
</el-select>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="filter-group">
|
|||
|
<label>排序方式:</label>
|
|||
|
<el-select v-model="sortBy" placeholder="排序方式" style="width: 150px;">
|
|||
|
<el-option label="默认排序" value="default" />
|
|||
|
<el-option label="价格从低到高" value="price-asc" />
|
|||
|
<el-option label="价格从高到低" value="price-desc" />
|
|||
|
<el-option label="最新上架" value="newest" />
|
|||
|
</el-select>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</section>
|
|||
|
|
|||
|
<!-- 产品列表 -->
|
|||
|
<section class="products-section section">
|
|||
|
<div class="container">
|
|||
|
<div class="products-grid">
|
|||
|
<div
|
|||
|
v-for="product in filteredProducts"
|
|||
|
:key="product.id"
|
|||
|
class="product-card card"
|
|||
|
@click="$router.push(`/product/${product.id}`)"
|
|||
|
>
|
|||
|
<div class="product-image">
|
|||
|
<div class="image-placeholder">
|
|||
|
<svg viewBox="0 0 250 200" width="100%" height="200">
|
|||
|
<rect width="250" height="200" :fill="product.color" opacity="0.2"/>
|
|||
|
<circle cx="125" cy="100" r="40" :fill="product.color" opacity="0.5"/>
|
|||
|
<text x="125" y="110" text-anchor="middle" :fill="product.color"
|
|||
|
font-size="14" font-weight="bold">{{ product.name.substring(0, 2) }}</text>
|
|||
|
</svg>
|
|||
|
</div>
|
|||
|
<div class="product-badges">
|
|||
|
<span v-if="product.isNew" class="badge new">新品</span>
|
|||
|
<span v-if="product.isHot" class="badge hot">热销</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="product-info">
|
|||
|
<h3 class="product-name">{{ product.name }}</h3>
|
|||
|
<p class="product-category">{{ getCategoryName(product.category) }}</p>
|
|||
|
<p class="product-description">{{ product.description }}</p>
|
|||
|
|
|||
|
<div class="product-meta">
|
|||
|
<div class="craftsman-info" v-if="product.craftsman">
|
|||
|
<el-icon><User /></el-icon>
|
|||
|
<span>{{ product.craftsman }}</span>
|
|||
|
</div>
|
|||
|
<div class="stock-info">
|
|||
|
<span :class="['stock-status', product.stock > 0 ? 'in-stock' : 'out-stock']">
|
|||
|
{{ product.stock > 0 ? `库存${product.stock}件` : '暂时缺货' }}
|
|||
|
</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="product-footer">
|
|||
|
<div class="price-info">
|
|||
|
<span class="current-price">¥{{ product.price }}</span>
|
|||
|
<span v-if="product.originalPrice" class="original-price">¥{{ product.originalPrice }}</span>
|
|||
|
</div>
|
|||
|
<el-button
|
|||
|
type="primary"
|
|||
|
size="small"
|
|||
|
:disabled="product.stock === 0"
|
|||
|
@click.stop="addToCart(product)"
|
|||
|
>
|
|||
|
{{ product.stock > 0 ? '加入购物车' : '缺货' }}
|
|||
|
</el-button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- 加载更多 -->
|
|||
|
<div class="load-more" v-if="hasMore">
|
|||
|
<el-button @click="loadMore" :loading="loading">加载更多</el-button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</section>
|
|||
|
</div>
|
|||
|
</template>
|
|||
|
|
|||
|
<script setup lang="ts">
|
|||
|
import { ref, computed, onMounted } from 'vue'
|
|||
|
import { useRoute } from 'vue-router'
|
|||
|
import { useMainStore } from '../stores'
|
|||
|
import { User } from '@element-plus/icons-vue'
|
|||
|
import { ElMessage } from 'element-plus'
|
|||
|
|
|||
|
const route = useRoute()
|
|||
|
const store = useMainStore()
|
|||
|
|
|||
|
// 响应式数据
|
|||
|
const selectedCategory = ref('all')
|
|||
|
const priceRange = ref('all')
|
|||
|
const sortBy = ref('default')
|
|||
|
const loading = ref(false)
|
|||
|
const hasMore = ref(true)
|
|||
|
|
|||
|
// 产品分类
|
|||
|
const categories = ref([
|
|||
|
{ id: 'all', name: '全部产品' },
|
|||
|
{ id: 'handicrafts', name: '手工艺品' },
|
|||
|
{ id: 'food', name: '特色食品' },
|
|||
|
{ id: 'cultural', name: '文创产品' },
|
|||
|
{ id: 'clothing', name: '服饰配饰' }
|
|||
|
])
|
|||
|
|
|||
|
// 产品数据
|
|||
|
const products = ref([
|
|||
|
{
|
|||
|
id: 1,
|
|||
|
name: '桦皮工艺盒',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '传统桦皮制作工艺,精美实用的收纳盒',
|
|||
|
price: 168,
|
|||
|
originalPrice: 198,
|
|||
|
stock: 15,
|
|||
|
craftsman: '关小云',
|
|||
|
isNew: true,
|
|||
|
isHot: false,
|
|||
|
color: '#8b4513'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 2,
|
|||
|
name: '鄂伦春族刺绣挂画',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '手工刺绣,图案精美,寓意吉祥',
|
|||
|
price: 288,
|
|||
|
stock: 8,
|
|||
|
craftsman: '孟淑珍',
|
|||
|
isNew: false,
|
|||
|
isHot: true,
|
|||
|
color: '#2d5016'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 3,
|
|||
|
name: '传统民族服饰',
|
|||
|
category: 'clothing',
|
|||
|
description: '复刻传统服饰,展现民族风采',
|
|||
|
price: 588,
|
|||
|
stock: 5,
|
|||
|
craftsman: '白热布',
|
|||
|
isNew: false,
|
|||
|
isHot: true,
|
|||
|
color: '#8b4513'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 4,
|
|||
|
name: '手工木雕摆件',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '精美木雕工艺,展现森林文化',
|
|||
|
price: 128,
|
|||
|
stock: 20,
|
|||
|
craftsman: '关金山',
|
|||
|
isNew: false,
|
|||
|
isHot: false,
|
|||
|
color: '#2d5016'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 5,
|
|||
|
name: '野生蓝莓干',
|
|||
|
category: 'food',
|
|||
|
description: '大兴安岭野生蓝莓,天然无添加',
|
|||
|
price: 58,
|
|||
|
originalPrice: 68,
|
|||
|
stock: 50,
|
|||
|
isNew: true,
|
|||
|
isHot: false,
|
|||
|
color: '#8b4513'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 6,
|
|||
|
name: '鄂伦春文化笔记本',
|
|||
|
category: 'cultural',
|
|||
|
description: '融入民族元素的精美笔记本',
|
|||
|
price: 35,
|
|||
|
stock: 100,
|
|||
|
isNew: false,
|
|||
|
isHot: false,
|
|||
|
color: '#2d5016'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 7,
|
|||
|
name: '桦皮茶具套装',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '传统桦皮工艺制作的茶具',
|
|||
|
price: 368,
|
|||
|
stock: 3,
|
|||
|
craftsman: '关小云',
|
|||
|
isNew: false,
|
|||
|
isHot: true,
|
|||
|
color: '#8b4513'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 8,
|
|||
|
name: '民族风头饰',
|
|||
|
category: 'clothing',
|
|||
|
description: '传统图案设计的精美头饰',
|
|||
|
price: 88,
|
|||
|
stock: 12,
|
|||
|
isNew: true,
|
|||
|
isHot: false,
|
|||
|
color: '#2d5016'
|
|||
|
}
|
|||
|
])
|
|||
|
|
|||
|
// 计算属性
|
|||
|
const filteredProducts = computed(() => {
|
|||
|
let result = products.value
|
|||
|
|
|||
|
// 分类筛选
|
|||
|
if (selectedCategory.value !== 'all') {
|
|||
|
result = result.filter(p => p.category === selectedCategory.value)
|
|||
|
}
|
|||
|
|
|||
|
// 价格筛选
|
|||
|
if (priceRange.value !== 'all') {
|
|||
|
const [min, max] = priceRange.value.split('-').map(v => v === '+' ? Infinity : parseInt(v))
|
|||
|
result = result.filter(p => {
|
|||
|
if (max === undefined) return p.price >= min
|
|||
|
return p.price >= min && p.price <= max
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
// 排序
|
|||
|
switch (sortBy.value) {
|
|||
|
case 'price-asc':
|
|||
|
result.sort((a, b) => a.price - b.price)
|
|||
|
break
|
|||
|
case 'price-desc':
|
|||
|
result.sort((a, b) => b.price - a.price)
|
|||
|
break
|
|||
|
case 'newest':
|
|||
|
result.sort((a, b) => (b.isNew ? 1 : 0) - (a.isNew ? 1 : 0))
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
return result
|
|||
|
})
|
|||
|
|
|||
|
// 方法
|
|||
|
const getCategoryName = (categoryId: string) => {
|
|||
|
const category = categories.value.find(c => c.id === categoryId)
|
|||
|
return category ? category.name : ''
|
|||
|
}
|
|||
|
|
|||
|
const addToCart = (product: any) => {
|
|||
|
if (product.stock === 0) {
|
|||
|
ElMessage.warning('商品暂时缺货')
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
store.addToCart(product)
|
|||
|
ElMessage.success('已添加到购物车')
|
|||
|
}
|
|||
|
|
|||
|
const loadMore = () => {
|
|||
|
loading.value = true
|
|||
|
// 模拟加载更多数据
|
|||
|
setTimeout(() => {
|
|||
|
loading.value = false
|
|||
|
hasMore.value = false
|
|||
|
}, 1000)
|
|||
|
}
|
|||
|
|
|||
|
// 生命周期
|
|||
|
onMounted(() => {
|
|||
|
// 处理搜索参数
|
|||
|
const searchQuery = route.query.search as string
|
|||
|
if (searchQuery) {
|
|||
|
// 这里可以根据搜索关键词筛选产品
|
|||
|
console.log('搜索关键词:', searchQuery)
|
|||
|
}
|
|||
|
})
|
|||
|
</script>
|
|||
|
|
|||
|
<style scoped>
|
|||
|
/* 页面头部 */
|
|||
|
.page-header {
|
|||
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
|||
|
color: white;
|
|||
|
padding: 4rem 0 2rem;
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
|
|||
|
.page-title {
|
|||
|
font-size: 3rem;
|
|||
|
font-weight: bold;
|
|||
|
margin-bottom: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.page-subtitle {
|
|||
|
font-size: 1.2rem;
|
|||
|
opacity: 0.9;
|
|||
|
}
|
|||
|
|
|||
|
/* 筛选区域 */
|
|||
|
.filters-section {
|
|||
|
background: var(--background-light);
|
|||
|
padding: 2rem 0;
|
|||
|
border-bottom: 1px solid var(--border-color);
|
|||
|
}
|
|||
|
|
|||
|
.filters {
|
|||
|
display: flex;
|
|||
|
flex-wrap: wrap;
|
|||
|
gap: 2rem;
|
|||
|
align-items: center;
|
|||
|
}
|
|||
|
|
|||
|
.filter-group {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
gap: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.filter-group label {
|
|||
|
font-weight: 500;
|
|||
|
color: var(--text-color);
|
|||
|
white-space: nowrap;
|
|||
|
}
|
|||
|
|
|||
|
.filter-buttons {
|
|||
|
display: flex;
|
|||
|
gap: 0.5rem;
|
|||
|
flex-wrap: wrap;
|
|||
|
}
|
|||
|
|
|||
|
.filter-btn {
|
|||
|
padding: 0.5rem 1rem;
|
|||
|
border: 1px solid var(--border-color);
|
|||
|
background: white;
|
|||
|
color: var(--text-color);
|
|||
|
border-radius: 20px;
|
|||
|
cursor: pointer;
|
|||
|
transition: all 0.3s ease;
|
|||
|
font-size: 0.9rem;
|
|||
|
}
|
|||
|
|
|||
|
.filter-btn:hover,
|
|||
|
.filter-btn.active {
|
|||
|
background: var(--primary-color);
|
|||
|
color: white;
|
|||
|
border-color: var(--primary-color);
|
|||
|
}
|
|||
|
|
|||
|
/* 产品网格 */
|
|||
|
.products-grid {
|
|||
|
display: grid;
|
|||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|||
|
gap: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-card {
|
|||
|
cursor: pointer;
|
|||
|
transition: all 0.3s ease;
|
|||
|
overflow: hidden;
|
|||
|
}
|
|||
|
|
|||
|
.product-card:hover {
|
|||
|
transform: translateY(-5px);
|
|||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
|||
|
}
|
|||
|
|
|||
|
.product-image {
|
|||
|
position: relative;
|
|||
|
width: 100%;
|
|||
|
height: 200px;
|
|||
|
}
|
|||
|
|
|||
|
.image-placeholder {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
background: var(--background-light);
|
|||
|
}
|
|||
|
|
|||
|
.product-badges {
|
|||
|
position: absolute;
|
|||
|
top: 10px;
|
|||
|
right: 10px;
|
|||
|
display: flex;
|
|||
|
flex-direction: column;
|
|||
|
gap: 5px;
|
|||
|
}
|
|||
|
|
|||
|
.badge {
|
|||
|
padding: 4px 8px;
|
|||
|
border-radius: 12px;
|
|||
|
font-size: 0.8rem;
|
|||
|
font-weight: bold;
|
|||
|
color: white;
|
|||
|
}
|
|||
|
|
|||
|
.badge.new {
|
|||
|
background: #ff4757;
|
|||
|
}
|
|||
|
|
|||
|
.badge.hot {
|
|||
|
background: #ff6b35;
|
|||
|
}
|
|||
|
|
|||
|
.product-info {
|
|||
|
padding: 1.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-name {
|
|||
|
font-size: 1.2rem;
|
|||
|
font-weight: bold;
|
|||
|
color: var(--primary-color);
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-category {
|
|||
|
color: var(--secondary-color);
|
|||
|
font-size: 0.9rem;
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-description {
|
|||
|
color: var(--text-light);
|
|||
|
font-size: 0.9rem;
|
|||
|
line-height: 1.5;
|
|||
|
margin-bottom: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-meta {
|
|||
|
display: flex;
|
|||
|
justify-content: space-between;
|
|||
|
align-items: center;
|
|||
|
margin-bottom: 1rem;
|
|||
|
font-size: 0.9rem;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-info {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
gap: 0.5rem;
|
|||
|
color: var(--text-light);
|
|||
|
}
|
|||
|
|
|||
|
.stock-status {
|
|||
|
font-size: 0.8rem;
|
|||
|
padding: 2px 6px;
|
|||
|
border-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
.stock-status.in-stock {
|
|||
|
background: rgba(46, 160, 67, 0.1);
|
|||
|
color: #2ea043;
|
|||
|
}
|
|||
|
|
|||
|
.stock-status.out-stock {
|
|||
|
background: rgba(218, 54, 51, 0.1);
|
|||
|
color: #da3633;
|
|||
|
}
|
|||
|
|
|||
|
.product-footer {
|
|||
|
display: flex;
|
|||
|
justify-content: space-between;
|
|||
|
align-items: center;
|
|||
|
}
|
|||
|
|
|||
|
.price-info {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
gap: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.current-price {
|
|||
|
font-size: 1.3rem;
|
|||
|
font-weight: bold;
|
|||
|
color: var(--secondary-color);
|
|||
|
}
|
|||
|
|
|||
|
.original-price {
|
|||
|
font-size: 1rem;
|
|||
|
color: var(--text-light);
|
|||
|
text-decoration: line-through;
|
|||
|
}
|
|||
|
|
|||
|
/* 加载更多 */
|
|||
|
.load-more {
|
|||
|
text-align: center;
|
|||
|
margin-top: 3rem;
|
|||
|
}
|
|||
|
|
|||
|
/* 响应式设计 */
|
|||
|
@media (max-width: 768px) {
|
|||
|
.page-title {
|
|||
|
font-size: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.filters {
|
|||
|
flex-direction: column;
|
|||
|
align-items: stretch;
|
|||
|
gap: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.filter-group {
|
|||
|
flex-direction: column;
|
|||
|
align-items: stretch;
|
|||
|
gap: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.filter-buttons {
|
|||
|
justify-content: center;
|
|||
|
}
|
|||
|
|
|||
|
.products-grid {
|
|||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|||
|
gap: 1.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-meta {
|
|||
|
flex-direction: column;
|
|||
|
align-items: stretch;
|
|||
|
gap: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-footer {
|
|||
|
flex-direction: column;
|
|||
|
gap: 1rem;
|
|||
|
align-items: stretch;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
@media (max-width: 480px) {
|
|||
|
.products-grid {
|
|||
|
grid-template-columns: 1fr;
|
|||
|
}
|
|||
|
|
|||
|
.filter-btn {
|
|||
|
flex: 1;
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|