1104 lines
29 KiB
Vue
1104 lines
29 KiB
Vue
|
<template>
|
|||
|
<div class="product-detail" v-if="product">
|
|||
|
<!-- 面包屑导航 -->
|
|||
|
<section class="breadcrumb-section">
|
|||
|
<div class="container">
|
|||
|
<div class="breadcrumb">
|
|||
|
<router-link to="/">首页</router-link>
|
|||
|
<span class="separator">/</span>
|
|||
|
<router-link to="/products">产品中心</router-link>
|
|||
|
<span class="separator">/</span>
|
|||
|
<span class="current">{{ product.name }}</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</section>
|
|||
|
|
|||
|
<!-- 产品详情主体 -->
|
|||
|
<section class="product-main section">
|
|||
|
<div class="container">
|
|||
|
<div class="product-layout">
|
|||
|
<!-- 产品图片 -->
|
|||
|
<div class="product-images">
|
|||
|
<div class="main-image">
|
|||
|
<div class="image-placeholder">
|
|||
|
<svg viewBox="0 0 400 400" width="100%" height="400">
|
|||
|
<rect width="400" height="400" :fill="product.color" opacity="0.2"/>
|
|||
|
<circle cx="200" cy="200" r="80" :fill="product.color" opacity="0.5"/>
|
|||
|
<text x="200" y="210" text-anchor="middle" :fill="product.color"
|
|||
|
font-size="24" font-weight="bold">{{ product.name.substring(0, 2) }}</text>
|
|||
|
</svg>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div class="thumbnail-list">
|
|||
|
<div v-for="i in 4" :key="i" class="thumbnail" :class="{ active: i === 1 }">
|
|||
|
<svg viewBox="0 0 100 100" width="100%" height="80">
|
|||
|
<rect width="100" height="100" :fill="product.color" opacity="0.3"/>
|
|||
|
<text x="50" y="55" text-anchor="middle" :fill="product.color"
|
|||
|
font-size="12" font-weight="bold">{{ i }}</text>
|
|||
|
</svg>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- 产品信息 -->
|
|||
|
<div class="product-info">
|
|||
|
<div class="product-badges">
|
|||
|
<span v-if="product.isNew" class="badge new">新品</span>
|
|||
|
<span v-if="product.isHot" class="badge hot">热销</span>
|
|||
|
</div>
|
|||
|
|
|||
|
<h1 class="product-title">{{ product.name }}</h1>
|
|||
|
<p class="product-subtitle">{{ getCategoryName(product.category) }}</p>
|
|||
|
|
|||
|
<div class="price-section">
|
|||
|
<div class="current-price">¥{{ product.price }}</div>
|
|||
|
<div v-if="product.originalPrice" class="original-price">原价:¥{{ product.originalPrice }}</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="product-specs">
|
|||
|
<div class="spec-item">
|
|||
|
<label>材质:</label>
|
|||
|
<span>{{ product.material || '天然桦皮' }}</span>
|
|||
|
</div>
|
|||
|
<div class="spec-item">
|
|||
|
<label>尺寸:</label>
|
|||
|
<span>{{ product.size || '长20cm × 宽15cm × 高8cm' }}</span>
|
|||
|
</div>
|
|||
|
<div class="spec-item">
|
|||
|
<label>重量:</label>
|
|||
|
<span>{{ product.weight || '约200g' }}</span>
|
|||
|
</div>
|
|||
|
<div class="spec-item">
|
|||
|
<label>产地:</label>
|
|||
|
<span>{{ product.origin || '黑龙江大兴安岭' }}</span>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="craftsman-section" v-if="product.craftsman">
|
|||
|
<h3>手工艺人</h3>
|
|||
|
<div class="craftsman-info">
|
|||
|
<div class="craftsman-avatar">
|
|||
|
<el-icon size="40"><User /></el-icon>
|
|||
|
</div>
|
|||
|
<div class="craftsman-details">
|
|||
|
<div class="craftsman-name">{{ product.craftsman }}</div>
|
|||
|
<div class="craftsman-title">{{ product.craftsmanTitle || '桦皮工艺传承人' }}</div>
|
|||
|
<div class="craftsman-desc">{{ product.craftsmanDesc || '从事桦皮工艺制作30余年,作品精美,技艺精湛' }}</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="stock-section">
|
|||
|
<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="purchase-section">
|
|||
|
<div class="quantity-selector">
|
|||
|
<label>数量:</label>
|
|||
|
<div class="quantity-controls">
|
|||
|
<el-button size="small" @click="decreaseQuantity" :disabled="quantity <= 1">-</el-button>
|
|||
|
<span class="quantity">{{ quantity }}</span>
|
|||
|
<el-button size="small" @click="increaseQuantity" :disabled="quantity >= product.stock">+</el-button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="action-buttons">
|
|||
|
<el-button
|
|||
|
type="primary"
|
|||
|
size="large"
|
|||
|
:disabled="product.stock === 0"
|
|||
|
@click="addToCart"
|
|||
|
>
|
|||
|
加入购物车
|
|||
|
</el-button>
|
|||
|
<el-button
|
|||
|
type="danger"
|
|||
|
size="large"
|
|||
|
:disabled="product.stock === 0"
|
|||
|
@click="buyNow"
|
|||
|
>
|
|||
|
立即购买
|
|||
|
</el-button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</section>
|
|||
|
|
|||
|
<!-- 产品详细信息 -->
|
|||
|
<section class="product-details section">
|
|||
|
<div class="container">
|
|||
|
<el-tabs v-model="activeTab" class="detail-tabs">
|
|||
|
<el-tab-pane label="产品详情" name="details">
|
|||
|
<div class="detail-content">
|
|||
|
<h3>产品介绍</h3>
|
|||
|
<p>{{ product.fullDescription || product.description }}</p>
|
|||
|
|
|||
|
<h3>制作工艺</h3>
|
|||
|
<div class="craft-process">
|
|||
|
<div v-for="(step, index) in craftSteps" :key="index" class="craft-step">
|
|||
|
<div class="step-number">{{ index + 1 }}</div>
|
|||
|
<div class="step-content">
|
|||
|
<h4>{{ step.title }}</h4>
|
|||
|
<p>{{ step.description }}</p>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<h3>文化内涵</h3>
|
|||
|
<p>{{ product.culturalMeaning || '桦皮工艺是鄂伦春族传统手工艺,承载着深厚的民族文化内涵,每一件作品都体现了匠人的精湛技艺和对传统文化的传承。' }}</p>
|
|||
|
</div>
|
|||
|
</el-tab-pane>
|
|||
|
|
|||
|
<el-tab-pane label="规格参数" name="specs">
|
|||
|
<div class="specs-table">
|
|||
|
<table>
|
|||
|
<tr>
|
|||
|
<td>产品名称</td>
|
|||
|
<td>{{ product.name }}</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>产品分类</td>
|
|||
|
<td>{{ getCategoryName(product.category) }}</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>材质</td>
|
|||
|
<td>{{ product.material || '天然桦皮' }}</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>尺寸</td>
|
|||
|
<td>{{ product.size || '长20cm × 宽15cm × 高8cm' }}</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>重量</td>
|
|||
|
<td>{{ product.weight || '约200g' }}</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>产地</td>
|
|||
|
<td>{{ product.origin || '黑龙江大兴安岭' }}</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>保养方式</td>
|
|||
|
<td>避免阳光直射,保持干燥,定期清洁</td>
|
|||
|
</tr>
|
|||
|
</table>
|
|||
|
</div>
|
|||
|
</el-tab-pane>
|
|||
|
|
|||
|
<el-tab-pane label="用户评价" name="reviews">
|
|||
|
<div class="reviews-section">
|
|||
|
<div class="reviews-summary">
|
|||
|
<div class="rating-overview">
|
|||
|
<div class="rating-score">4.8</div>
|
|||
|
<div class="rating-stars">
|
|||
|
<el-icon v-for="i in 5" :key="i" color="#ffd700"><StarFilled /></el-icon>
|
|||
|
</div>
|
|||
|
<div class="rating-count">基于 {{ reviews.length }} 条评价</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div class="reviews-list">
|
|||
|
<div v-for="review in reviews" :key="review.id" class="review-item">
|
|||
|
<div class="review-header">
|
|||
|
<div class="reviewer-info">
|
|||
|
<div class="reviewer-avatar">
|
|||
|
<el-icon><User /></el-icon>
|
|||
|
</div>
|
|||
|
<div class="reviewer-details">
|
|||
|
<div class="reviewer-name">{{ review.userName }}</div>
|
|||
|
<div class="review-date">{{ review.date }}</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div class="review-rating">
|
|||
|
<el-icon v-for="i in review.rating" :key="i" color="#ffd700"><StarFilled /></el-icon>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div class="review-content">
|
|||
|
{{ review.content }}
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</el-tab-pane>
|
|||
|
</el-tabs>
|
|||
|
</div>
|
|||
|
</section>
|
|||
|
|
|||
|
<!-- 相关推荐 -->
|
|||
|
<section class="related-products section">
|
|||
|
<div class="container">
|
|||
|
<h2 class="section-title">相关推荐</h2>
|
|||
|
<div class="products-grid">
|
|||
|
<div
|
|||
|
v-for="relatedProduct in relatedProducts"
|
|||
|
:key="relatedProduct.id"
|
|||
|
class="product-card card"
|
|||
|
@click="$router.push(`/product/${relatedProduct.id}`)"
|
|||
|
>
|
|||
|
<div class="product-image">
|
|||
|
<div class="image-placeholder">
|
|||
|
<svg viewBox="0 0 200 150" width="100%" height="150">
|
|||
|
<rect width="200" height="150" :fill="relatedProduct.color" opacity="0.2"/>
|
|||
|
<circle cx="100" cy="75" r="30" :fill="relatedProduct.color" opacity="0.5"/>
|
|||
|
<text x="100" y="80" text-anchor="middle" :fill="relatedProduct.color"
|
|||
|
font-size="12" font-weight="bold">{{ relatedProduct.name.substring(0, 2) }}</text>
|
|||
|
</svg>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div class="product-info">
|
|||
|
<h3 class="product-name">{{ relatedProduct.name }}</h3>
|
|||
|
<div class="product-price">¥{{ relatedProduct.price }}</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</section>
|
|||
|
</div>
|
|||
|
|
|||
|
<div v-else class="loading">
|
|||
|
<el-icon class="is-loading" size="50"><Loading /></el-icon>
|
|||
|
<p>加载中...</p>
|
|||
|
</div>
|
|||
|
</template>
|
|||
|
|
|||
|
<script setup lang="ts">
|
|||
|
import { ref, computed, onMounted } from 'vue'
|
|||
|
import { useRoute, useRouter } from 'vue-router'
|
|||
|
import { useMainStore } from '../stores'
|
|||
|
import { User, StarFilled, Loading } from '@element-plus/icons-vue'
|
|||
|
import { ElMessage } from 'element-plus'
|
|||
|
|
|||
|
const route = useRoute()
|
|||
|
const router = useRouter()
|
|||
|
const store = useMainStore()
|
|||
|
|
|||
|
// 响应式数据
|
|||
|
const product = ref<any>(null)
|
|||
|
const quantity = ref(1)
|
|||
|
const activeTab = ref('details')
|
|||
|
|
|||
|
// 产品分类
|
|||
|
const categories = ref([
|
|||
|
{ id: 'handicrafts', name: '手工艺品' },
|
|||
|
{ id: 'food', name: '特色食品' },
|
|||
|
{ id: 'cultural', name: '文创产品' },
|
|||
|
{ id: 'clothing', name: '服饰配饰' }
|
|||
|
])
|
|||
|
|
|||
|
// 制作工艺步骤
|
|||
|
const craftSteps = ref([
|
|||
|
{
|
|||
|
title: '选材',
|
|||
|
description: '选择优质的桦树皮,确保材质坚韧有韧性'
|
|||
|
},
|
|||
|
{
|
|||
|
title: '处理',
|
|||
|
description: '将桦皮进行清洁、软化处理,去除杂质'
|
|||
|
},
|
|||
|
{
|
|||
|
title: '裁剪',
|
|||
|
description: '根据设计图案精确裁剪桦皮材料'
|
|||
|
},
|
|||
|
{
|
|||
|
title: '缝制',
|
|||
|
description: '使用传统针法将各部分缝制组装'
|
|||
|
},
|
|||
|
{
|
|||
|
title: '装饰',
|
|||
|
description: '添加民族特色图案和装饰元素'
|
|||
|
},
|
|||
|
{
|
|||
|
title: '整理',
|
|||
|
description: '最后整理修饰,确保产品完美呈现'
|
|||
|
}
|
|||
|
])
|
|||
|
|
|||
|
// 用户评价
|
|||
|
const reviews = ref([
|
|||
|
{
|
|||
|
id: 1,
|
|||
|
userName: '文化爱好者',
|
|||
|
rating: 5,
|
|||
|
date: '2024-01-15',
|
|||
|
content: '非常精美的桦皮工艺品,做工精细,很有民族特色,值得收藏!'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 2,
|
|||
|
userName: '传统工艺迷',
|
|||
|
rating: 5,
|
|||
|
date: '2024-01-10',
|
|||
|
content: '质量很好,包装精美,能感受到浓厚的鄂伦春族文化气息。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 3,
|
|||
|
userName: '手工艺收藏家',
|
|||
|
rating: 4,
|
|||
|
date: '2024-01-05',
|
|||
|
content: '工艺精湛,材质天然,是很好的文化艺术品。'
|
|||
|
}
|
|||
|
])
|
|||
|
|
|||
|
// 相关产品
|
|||
|
const relatedProducts = ref([
|
|||
|
{
|
|||
|
id: 2,
|
|||
|
name: '鄂伦春族刺绣挂画',
|
|||
|
price: 288,
|
|||
|
color: '#2d5016'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 4,
|
|||
|
name: '手工木雕摆件',
|
|||
|
price: 128,
|
|||
|
color: '#8b4513'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 7,
|
|||
|
name: '桦皮茶具套装',
|
|||
|
price: 368,
|
|||
|
color: '#2d5016'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 8,
|
|||
|
name: '民族风头饰',
|
|||
|
price: 88,
|
|||
|
color: '#8b4513'
|
|||
|
}
|
|||
|
])
|
|||
|
|
|||
|
// 模拟产品数据
|
|||
|
const mockProducts = [
|
|||
|
{
|
|||
|
id: 1,
|
|||
|
name: '桦皮工艺盒',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '传统桦皮制作工艺,精美实用的收纳盒',
|
|||
|
fullDescription: '这款桦皮工艺盒采用传统的鄂伦春族桦皮制作工艺,选用优质天然桦树皮为原料,经过精心处理和手工制作而成。盒子造型优美,实用性强,既可作为收纳盒使用,也是极具收藏价值的民族工艺品。每一个细节都体现了匠人的精湛技艺和对传统文化的传承。',
|
|||
|
price: 168,
|
|||
|
originalPrice: 198,
|
|||
|
stock: 15,
|
|||
|
craftsman: '关小云',
|
|||
|
craftsmanTitle: '桦皮工艺传承人',
|
|||
|
craftsmanDesc: '从事桦皮工艺制作30余年,作品精美,技艺精湛,多次获得民族工艺品大赛奖项',
|
|||
|
isNew: true,
|
|||
|
isHot: false,
|
|||
|
color: '#8b4513',
|
|||
|
material: '天然桦皮',
|
|||
|
size: '长20cm × 宽15cm × 高8cm',
|
|||
|
weight: '约200g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '桦皮工艺是鄂伦春族传统手工艺的重要组成部分,承载着深厚的民族文化内涵。桦皮盒不仅是实用的生活用品,更是民族智慧的结晶,体现了鄂伦春族人民对自然的敬畏和对美好生活的追求。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 2,
|
|||
|
name: '鄂伦春族刺绣挂画',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '手工刺绣,图案精美,寓意吉祥',
|
|||
|
fullDescription: '这幅刺绣挂画采用传统的鄂伦春族刺绣工艺,以丝线为材料,在优质布料上精心绣制而成。图案设计融合了鄂伦春族的传统文化元素,色彩搭配和谐,寓意吉祥如意。每一针每一线都凝聚着绣娘的心血和对民族文化的热爱。',
|
|||
|
price: 288,
|
|||
|
stock: 8,
|
|||
|
craftsman: '孟淑珍',
|
|||
|
craftsmanTitle: '刺绣工艺传承人',
|
|||
|
craftsmanDesc: '从事刺绣工艺25年,擅长传统图案设计,作品多次在民族工艺展览中获奖',
|
|||
|
isNew: false,
|
|||
|
isHot: true,
|
|||
|
color: '#2d5016',
|
|||
|
material: '优质丝线、棉布',
|
|||
|
size: '长40cm × 宽30cm',
|
|||
|
weight: '约150g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '刺绣是鄂伦春族妇女的传统技艺,图案多取材于自然和生活,体现了鄂伦春族人民对美好生活的向往和对自然的崇敬。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 3,
|
|||
|
name: '传统民族服饰',
|
|||
|
category: 'clothing',
|
|||
|
description: '复刻传统服饰,展现民族风采',
|
|||
|
fullDescription: '这套传统民族服饰严格按照鄂伦春族传统服装样式制作,选用优质面料,手工缝制,细节精美。服装设计体现了鄂伦春族的文化特色,穿着舒适,既可用于文化表演,也可作为收藏品。',
|
|||
|
price: 588,
|
|||
|
stock: 5,
|
|||
|
craftsman: '白热布',
|
|||
|
craftsmanTitle: '民族服饰制作师',
|
|||
|
craftsmanDesc: '专注民族服饰制作15年,深谙传统服装制作工艺,作品广受好评',
|
|||
|
isNew: false,
|
|||
|
isHot: true,
|
|||
|
color: '#8b4513',
|
|||
|
material: '优质棉麻、丝绸',
|
|||
|
size: 'M码(可定制)',
|
|||
|
weight: '约800g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '鄂伦春族传统服饰承载着深厚的文化内涵,每一个图案和色彩都有其特定的含义,体现了民族的智慧和审美。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 4,
|
|||
|
name: '手工木雕摆件',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '精美木雕工艺,展现森林文化',
|
|||
|
fullDescription: '这件木雕摆件选用优质木材,经过精心雕刻而成。作品造型生动,线条流畅,充分展现了鄂伦春族的森林文化特色。每一个细节都经过匠人的精心打磨,是极具艺术价值的工艺品。',
|
|||
|
price: 128,
|
|||
|
stock: 20,
|
|||
|
craftsman: '关金山',
|
|||
|
craftsmanTitle: '木雕工艺师',
|
|||
|
craftsmanDesc: '从事木雕工艺20年,擅长动物和自然题材雕刻,技艺精湛',
|
|||
|
isNew: false,
|
|||
|
isHot: false,
|
|||
|
color: '#2d5016',
|
|||
|
material: '优质木材',
|
|||
|
size: '长15cm × 宽10cm × 高12cm',
|
|||
|
weight: '约300g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '木雕是鄂伦春族传统手工艺之一,多以森林动物和自然景观为题材,体现了民族与自然和谐共生的理念。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 5,
|
|||
|
name: '野生蓝莓干',
|
|||
|
category: 'food',
|
|||
|
description: '大兴安岭野生蓝莓,天然无添加',
|
|||
|
fullDescription: '这款蓝莓干选用大兴安岭原始森林中的野生蓝莓,经过自然晾晒工艺制成。保持了蓝莓的天然营养成分,口感酸甜,是健康的天然零食。无任何人工添加剂,绿色健康。',
|
|||
|
price: 58,
|
|||
|
originalPrice: 68,
|
|||
|
stock: 50,
|
|||
|
isNew: true,
|
|||
|
isHot: false,
|
|||
|
color: '#8b4513',
|
|||
|
material: '野生蓝莓',
|
|||
|
size: '净含量200g',
|
|||
|
weight: '约200g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '蓝莓是大兴安岭的特产,鄂伦春族人民世代采集食用,是大自然赐予的珍贵礼物。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 6,
|
|||
|
name: '鄂伦春文化笔记本',
|
|||
|
category: 'cultural',
|
|||
|
description: '融入民族元素的精美笔记本',
|
|||
|
fullDescription: '这款笔记本封面设计融入了鄂伦春族传统文化元素,采用优质纸张制作,书写流畅。既实用又具有文化纪念意义,是学习工作的好伴侣,也是了解鄂伦春族文化的窗口。',
|
|||
|
price: 35,
|
|||
|
stock: 100,
|
|||
|
isNew: false,
|
|||
|
isHot: false,
|
|||
|
color: '#2d5016',
|
|||
|
material: '优质纸张、硬质封面',
|
|||
|
size: 'A5规格',
|
|||
|
weight: '约250g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '笔记本设计元素来源于鄂伦春族传统图案,每一个符号都承载着深厚的文化内涵。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 7,
|
|||
|
name: '桦皮茶具套装',
|
|||
|
category: 'handicrafts',
|
|||
|
description: '传统桦皮工艺制作的茶具',
|
|||
|
fullDescription: '这套桦皮茶具采用传统工艺制作,包含茶壶、茶杯等完整配件。桦皮材质天然环保,具有独特的香味,用于泡茶别有一番风味。是品茶爱好者和文化收藏者的理想选择。',
|
|||
|
price: 368,
|
|||
|
stock: 3,
|
|||
|
craftsman: '关小云',
|
|||
|
craftsmanTitle: '桦皮工艺传承人',
|
|||
|
craftsmanDesc: '从事桦皮工艺制作30余年,作品精美,技艺精湛,多次获得民族工艺品大赛奖项',
|
|||
|
isNew: false,
|
|||
|
isHot: true,
|
|||
|
color: '#8b4513',
|
|||
|
material: '天然桦皮',
|
|||
|
size: '茶壶高12cm,茶杯直径8cm',
|
|||
|
weight: '约500g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '桦皮茶具体现了鄂伦春族人民的生活智慧,将实用性与艺术性完美结合。'
|
|||
|
},
|
|||
|
{
|
|||
|
id: 8,
|
|||
|
name: '民族风头饰',
|
|||
|
category: 'clothing',
|
|||
|
description: '传统图案设计的精美头饰',
|
|||
|
fullDescription: '这款头饰采用传统鄂伦春族图案设计,手工制作,工艺精美。可用于民族服装搭配,也可作为装饰品收藏。设计典雅,做工精细,充分展现了鄂伦春族的文化魅力。',
|
|||
|
price: 88,
|
|||
|
stock: 12,
|
|||
|
isNew: true,
|
|||
|
isHot: false,
|
|||
|
color: '#2d5016',
|
|||
|
material: '优质布料、装饰珠子',
|
|||
|
size: '可调节尺寸',
|
|||
|
weight: '约50g',
|
|||
|
origin: '黑龙江大兴安岭',
|
|||
|
culturalMeaning: '头饰在鄂伦春族传统文化中具有重要地位,不同的图案和颜色代表不同的寓意和身份。'
|
|||
|
}
|
|||
|
]
|
|||
|
|
|||
|
// 计算属性
|
|||
|
const getCategoryName = (categoryId: string) => {
|
|||
|
const category = categories.value.find(c => c.id === categoryId)
|
|||
|
return category ? category.name : ''
|
|||
|
}
|
|||
|
|
|||
|
// 方法
|
|||
|
const increaseQuantity = () => {
|
|||
|
if (quantity.value < product.value.stock) {
|
|||
|
quantity.value++
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const decreaseQuantity = () => {
|
|||
|
if (quantity.value > 1) {
|
|||
|
quantity.value--
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const addToCart = () => {
|
|||
|
if (product.value.stock === 0) {
|
|||
|
ElMessage.warning('商品暂时缺货')
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
for (let i = 0; i < quantity.value; i++) {
|
|||
|
store.addToCart(product.value)
|
|||
|
}
|
|||
|
ElMessage.success(`已添加 ${quantity.value} 件商品到购物车`)
|
|||
|
}
|
|||
|
|
|||
|
const buyNow = () => {
|
|||
|
if (product.value.stock === 0) {
|
|||
|
ElMessage.warning('商品暂时缺货')
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
addToCart()
|
|||
|
router.push('/cart')
|
|||
|
}
|
|||
|
|
|||
|
// 生命周期
|
|||
|
onMounted(() => {
|
|||
|
const productId = parseInt(route.params.id as string)
|
|||
|
|
|||
|
// 模拟从API获取产品数据
|
|||
|
setTimeout(() => {
|
|||
|
const foundProduct = mockProducts.find(p => p.id === productId)
|
|||
|
if (foundProduct) {
|
|||
|
product.value = foundProduct
|
|||
|
} else {
|
|||
|
ElMessage.error('产品不存在')
|
|||
|
router.push('/products')
|
|||
|
}
|
|||
|
}, 500)
|
|||
|
})
|
|||
|
</script>
|
|||
|
|
|||
|
<style scoped>
|
|||
|
/* 面包屑导航 */
|
|||
|
.breadcrumb-section {
|
|||
|
background: var(--background-light);
|
|||
|
padding: 1rem 0;
|
|||
|
border-bottom: 1px solid var(--border-color);
|
|||
|
}
|
|||
|
|
|||
|
.breadcrumb {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
gap: 0.5rem;
|
|||
|
font-size: 0.9rem;
|
|||
|
}
|
|||
|
|
|||
|
.breadcrumb a {
|
|||
|
color: var(--primary-color);
|
|||
|
text-decoration: none;
|
|||
|
}
|
|||
|
|
|||
|
.breadcrumb a:hover {
|
|||
|
text-decoration: underline;
|
|||
|
}
|
|||
|
|
|||
|
.separator {
|
|||
|
color: var(--text-light);
|
|||
|
}
|
|||
|
|
|||
|
.current {
|
|||
|
color: var(--text-color);
|
|||
|
font-weight: 500;
|
|||
|
}
|
|||
|
|
|||
|
/* 产品主体布局 */
|
|||
|
.product-layout {
|
|||
|
display: grid;
|
|||
|
grid-template-columns: 1fr 1fr;
|
|||
|
gap: 4rem;
|
|||
|
align-items: start;
|
|||
|
}
|
|||
|
|
|||
|
/* 产品图片 */
|
|||
|
.product-images {
|
|||
|
position: sticky;
|
|||
|
top: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.main-image {
|
|||
|
width: 100%;
|
|||
|
margin-bottom: 1rem;
|
|||
|
border-radius: 8px;
|
|||
|
overflow: hidden;
|
|||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|||
|
}
|
|||
|
|
|||
|
.image-placeholder {
|
|||
|
width: 100%;
|
|||
|
height: 400px;
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
background: var(--background-light);
|
|||
|
}
|
|||
|
|
|||
|
.thumbnail-list {
|
|||
|
display: flex;
|
|||
|
gap: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.thumbnail {
|
|||
|
width: 80px;
|
|||
|
height: 80px;
|
|||
|
border-radius: 4px;
|
|||
|
overflow: hidden;
|
|||
|
cursor: pointer;
|
|||
|
border: 2px solid transparent;
|
|||
|
transition: border-color 0.3s ease;
|
|||
|
}
|
|||
|
|
|||
|
.thumbnail.active,
|
|||
|
.thumbnail:hover {
|
|||
|
border-color: var(--primary-color);
|
|||
|
}
|
|||
|
|
|||
|
/* 产品信息 */
|
|||
|
.product-info {
|
|||
|
padding: 1rem 0;
|
|||
|
}
|
|||
|
|
|||
|
.product-badges {
|
|||
|
margin-bottom: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.badge {
|
|||
|
display: inline-block;
|
|||
|
padding: 4px 12px;
|
|||
|
border-radius: 12px;
|
|||
|
font-size: 0.8rem;
|
|||
|
font-weight: bold;
|
|||
|
color: white;
|
|||
|
margin-right: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.badge.new {
|
|||
|
background: #ff4757;
|
|||
|
}
|
|||
|
|
|||
|
.badge.hot {
|
|||
|
background: #ff6b35;
|
|||
|
}
|
|||
|
|
|||
|
.product-title {
|
|||
|
font-size: 2rem;
|
|||
|
font-weight: bold;
|
|||
|
color: var(--primary-color);
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-subtitle {
|
|||
|
color: var(--secondary-color);
|
|||
|
font-size: 1.1rem;
|
|||
|
margin-bottom: 1.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.price-section {
|
|||
|
margin-bottom: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.current-price {
|
|||
|
font-size: 2.5rem;
|
|||
|
font-weight: bold;
|
|||
|
color: var(--secondary-color);
|
|||
|
}
|
|||
|
|
|||
|
.original-price {
|
|||
|
font-size: 1.2rem;
|
|||
|
color: var(--text-light);
|
|||
|
text-decoration: line-through;
|
|||
|
margin-left: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-specs {
|
|||
|
background: var(--background-light);
|
|||
|
padding: 1.5rem;
|
|||
|
border-radius: 8px;
|
|||
|
margin-bottom: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.spec-item {
|
|||
|
display: flex;
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.spec-item:last-child {
|
|||
|
margin-bottom: 0;
|
|||
|
}
|
|||
|
|
|||
|
.spec-item label {
|
|||
|
font-weight: 500;
|
|||
|
color: var(--text-color);
|
|||
|
width: 80px;
|
|||
|
flex-shrink: 0;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-section {
|
|||
|
background: var(--background-light);
|
|||
|
padding: 1.5rem;
|
|||
|
border-radius: 8px;
|
|||
|
margin-bottom: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-section h3 {
|
|||
|
margin-bottom: 1rem;
|
|||
|
color: var(--primary-color);
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-info {
|
|||
|
display: flex;
|
|||
|
gap: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-avatar {
|
|||
|
width: 60px;
|
|||
|
height: 60px;
|
|||
|
border-radius: 50%;
|
|||
|
background: var(--primary-color);
|
|||
|
color: white;
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
flex-shrink: 0;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-name {
|
|||
|
font-weight: bold;
|
|||
|
font-size: 1.1rem;
|
|||
|
margin-bottom: 0.25rem;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-title {
|
|||
|
color: var(--secondary-color);
|
|||
|
font-size: 0.9rem;
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-desc {
|
|||
|
color: var(--text-light);
|
|||
|
font-size: 0.9rem;
|
|||
|
line-height: 1.5;
|
|||
|
}
|
|||
|
|
|||
|
.stock-section {
|
|||
|
margin-bottom: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.stock-status {
|
|||
|
font-size: 1rem;
|
|||
|
padding: 8px 16px;
|
|||
|
border-radius: 20px;
|
|||
|
font-weight: 500;
|
|||
|
}
|
|||
|
|
|||
|
.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;
|
|||
|
}
|
|||
|
|
|||
|
.purchase-section {
|
|||
|
border-top: 1px solid var(--border-color);
|
|||
|
padding-top: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.quantity-selector {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
gap: 1rem;
|
|||
|
margin-bottom: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.quantity-selector label {
|
|||
|
font-weight: 500;
|
|||
|
}
|
|||
|
|
|||
|
.quantity-controls {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
gap: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.quantity {
|
|||
|
font-size: 1.2rem;
|
|||
|
font-weight: bold;
|
|||
|
min-width: 2rem;
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
|
|||
|
.action-buttons {
|
|||
|
display: flex;
|
|||
|
gap: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.action-buttons .el-button {
|
|||
|
flex: 1;
|
|||
|
height: 50px;
|
|||
|
font-size: 1.1rem;
|
|||
|
}
|
|||
|
|
|||
|
/* 详细信息标签页 */
|
|||
|
.detail-tabs {
|
|||
|
margin-top: 3rem;
|
|||
|
}
|
|||
|
|
|||
|
.detail-content h3 {
|
|||
|
color: var(--primary-color);
|
|||
|
margin: 2rem 0 1rem;
|
|||
|
font-size: 1.3rem;
|
|||
|
}
|
|||
|
|
|||
|
.detail-content h3:first-child {
|
|||
|
margin-top: 0;
|
|||
|
}
|
|||
|
|
|||
|
.detail-content p {
|
|||
|
line-height: 1.8;
|
|||
|
color: var(--text-color);
|
|||
|
margin-bottom: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.craft-process {
|
|||
|
margin: 2rem 0;
|
|||
|
}
|
|||
|
|
|||
|
.craft-step {
|
|||
|
display: flex;
|
|||
|
gap: 1rem;
|
|||
|
margin-bottom: 2rem;
|
|||
|
align-items: flex-start;
|
|||
|
}
|
|||
|
|
|||
|
.step-number {
|
|||
|
width: 40px;
|
|||
|
height: 40px;
|
|||
|
border-radius: 50%;
|
|||
|
background: var(--primary-color);
|
|||
|
color: white;
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
font-weight: bold;
|
|||
|
flex-shrink: 0;
|
|||
|
}
|
|||
|
|
|||
|
.step-content h4 {
|
|||
|
color: var(--primary-color);
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.step-content p {
|
|||
|
color: var(--text-light);
|
|||
|
margin: 0;
|
|||
|
}
|
|||
|
|
|||
|
.specs-table table {
|
|||
|
width: 100%;
|
|||
|
border-collapse: collapse;
|
|||
|
}
|
|||
|
|
|||
|
.specs-table td {
|
|||
|
padding: 1rem;
|
|||
|
border-bottom: 1px solid var(--border-color);
|
|||
|
}
|
|||
|
|
|||
|
.specs-table td:first-child {
|
|||
|
font-weight: 500;
|
|||
|
color: var(--text-color);
|
|||
|
width: 150px;
|
|||
|
}
|
|||
|
|
|||
|
.reviews-summary {
|
|||
|
background: var(--background-light);
|
|||
|
padding: 2rem;
|
|||
|
border-radius: 8px;
|
|||
|
margin-bottom: 2rem;
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
|
|||
|
.rating-score {
|
|||
|
font-size: 3rem;
|
|||
|
font-weight: bold;
|
|||
|
color: var(--primary-color);
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.rating-stars {
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.rating-count {
|
|||
|
color: var(--text-light);
|
|||
|
}
|
|||
|
|
|||
|
.review-item {
|
|||
|
border-bottom: 1px solid var(--border-color);
|
|||
|
padding: 1.5rem 0;
|
|||
|
}
|
|||
|
|
|||
|
.review-item:last-child {
|
|||
|
border-bottom: none;
|
|||
|
}
|
|||
|
|
|||
|
.review-header {
|
|||
|
display: flex;
|
|||
|
justify-content: space-between;
|
|||
|
align-items: center;
|
|||
|
margin-bottom: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.reviewer-info {
|
|||
|
display: flex;
|
|||
|
gap: 1rem;
|
|||
|
align-items: center;
|
|||
|
}
|
|||
|
|
|||
|
.reviewer-avatar {
|
|||
|
width: 40px;
|
|||
|
height: 40px;
|
|||
|
border-radius: 50%;
|
|||
|
background: var(--primary-color);
|
|||
|
color: white;
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
}
|
|||
|
|
|||
|
.reviewer-name {
|
|||
|
font-weight: 500;
|
|||
|
margin-bottom: 0.25rem;
|
|||
|
}
|
|||
|
|
|||
|
.reviewer-date {
|
|||
|
color: var(--text-light);
|
|||
|
font-size: 0.9rem;
|
|||
|
}
|
|||
|
|
|||
|
.review-content {
|
|||
|
line-height: 1.6;
|
|||
|
color: var(--text-color);
|
|||
|
}
|
|||
|
|
|||
|
/* 相关推荐 */
|
|||
|
.related-products {
|
|||
|
background: var(--background-light);
|
|||
|
}
|
|||
|
|
|||
|
.products-grid {
|
|||
|
display: grid;
|
|||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|||
|
gap: 1.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-card {
|
|||
|
cursor: pointer;
|
|||
|
transition: all 0.3s ease;
|
|||
|
}
|
|||
|
|
|||
|
.product-card:hover {
|
|||
|
transform: translateY(-5px);
|
|||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
|||
|
}
|
|||
|
|
|||
|
.product-card .product-image {
|
|||
|
width: 100%;
|
|||
|
height: 150px;
|
|||
|
margin-bottom: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-card .product-info {
|
|||
|
padding: 1rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-card .product-name {
|
|||
|
font-size: 1rem;
|
|||
|
font-weight: 500;
|
|||
|
color: var(--primary-color);
|
|||
|
margin-bottom: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-card .product-price {
|
|||
|
font-size: 1.2rem;
|
|||
|
font-weight: bold;
|
|||
|
color: var(--secondary-color);
|
|||
|
}
|
|||
|
|
|||
|
/* 加载状态 */
|
|||
|
.loading {
|
|||
|
display: flex;
|
|||
|
flex-direction: column;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
height: 50vh;
|
|||
|
color: var(--text-light);
|
|||
|
}
|
|||
|
|
|||
|
.loading p {
|
|||
|
margin-top: 1rem;
|
|||
|
font-size: 1.1rem;
|
|||
|
}
|
|||
|
|
|||
|
/* 响应式设计 */
|
|||
|
@media (max-width: 768px) {
|
|||
|
.product-layout {
|
|||
|
grid-template-columns: 1fr;
|
|||
|
gap: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.product-images {
|
|||
|
position: static;
|
|||
|
}
|
|||
|
|
|||
|
.product-title {
|
|||
|
font-size: 1.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.current-price {
|
|||
|
font-size: 2rem;
|
|||
|
}
|
|||
|
|
|||
|
.action-buttons {
|
|||
|
flex-direction: column;
|
|||
|
}
|
|||
|
|
|||
|
.craftsman-info {
|
|||
|
flex-direction: column;
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
|
|||
|
.quantity-selector {
|
|||
|
flex-direction: column;
|
|||
|
align-items: stretch;
|
|||
|
gap: 0.5rem;
|
|||
|
}
|
|||
|
|
|||
|
.quantity-controls {
|
|||
|
justify-content: center;
|
|||
|
}
|
|||
|
|
|||
|
.products-grid {
|
|||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|