344 lines
8.4 KiB
Vue
344 lines
8.4 KiB
Vue
<template>
|
||
<view class="product-detail" v-if="product">
|
||
<!-- 自定义导航栏 -->
|
||
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="custom-navbar__content" :style="{ height: navBarHeight + 'px' }">
|
||
<image class="custom-navbar__back" src="/static/ic_back.png" mode="aspectFit" @click="goBack" />
|
||
<text class="custom-navbar__title">商品详情</text>
|
||
<view class="custom-navbar__placeholder" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 导航栏占位 -->
|
||
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }" />
|
||
|
||
<!-- Banner 轮播 -->
|
||
<view class="banner-wrapper">
|
||
<BannerSwiper :images="product.bannerImages || []" :video="product.bannerVideo" />
|
||
</view>
|
||
|
||
<!-- 基础信息 -->
|
||
<view class="base-info">
|
||
<view class="base-info__top">
|
||
<text class="base-info__name">{{ product.name }}</text>
|
||
<view class="base-info__price">
|
||
<text class="base-info__price-symbol">¥</text>
|
||
<text class="base-info__price-num">{{ product.basePrice }}</text>
|
||
<text class="base-info__price-unit">元</text>
|
||
</view>
|
||
</view>
|
||
<view class="base-info__attrs">
|
||
<view class="attr-row">
|
||
<text class="attr-label">款 号</text>
|
||
<text class="attr-value">{{ product.styleNo }}</text>
|
||
<text class="attr-label">库存</text>
|
||
<text class="attr-value">{{ product.stock }}</text>
|
||
</view>
|
||
<view class="attr-row">
|
||
<text class="attr-label">损 耗</text>
|
||
<text class="attr-value">{{ product.loss }}%</text>
|
||
<text class="attr-label">工费</text>
|
||
<text class="attr-value">¥{{ product.laborCost }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 发货公告 -->
|
||
<ShippingNotice />
|
||
|
||
<!-- 商品详情(大图展示) -->
|
||
<view class="detail-section">
|
||
<view class="detail-section__title">商品详情</view>
|
||
<view class="detail-section__images">
|
||
<image
|
||
v-for="(img, idx) in (product.detailImages || [])"
|
||
:key="idx"
|
||
class="detail-section__img"
|
||
:src="fullUrl(img)"
|
||
mode="widthFix"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-bar">
|
||
<view class="bottom-bar__icons">
|
||
<view class="bottom-bar__icon-item" @click="showQrCode = true">
|
||
<image class="bottom-bar__icon-img" src="/static/ic_customer.png" mode="aspectFit" />
|
||
<text class="bottom-bar__icon-text">客服</text>
|
||
</view>
|
||
<view class="bottom-bar__icon-item" @click="goCart">
|
||
<view class="cart-icon-wrap">
|
||
<image class="bottom-bar__icon-img" src="/static/tab/car.png" mode="aspectFit" />
|
||
<text v-if="cartCount > 0" class="cart-badge">{{ cartCount > 99 ? '99+' : cartCount }}</text>
|
||
</view>
|
||
<text class="bottom-bar__icon-text">购物车</text>
|
||
</view>
|
||
</view>
|
||
<view class="bottom-bar__main-btn" @click="showSpecPanel = true">
|
||
<text>空托—查看详细参数</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 详细参数面板 -->
|
||
<SpecPanel v-if="showSpecPanel" :product-id="product.id" :product="product" @close="showSpecPanel = false" />
|
||
|
||
<!-- 客服二维码弹窗 -->
|
||
<CustomerServiceBtn v-if="showQrCode" mode="qrcode" @close="showQrCode = false" />
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import type { Product } from '../../types'
|
||
import { getProductDetail } from '../../api/product'
|
||
import { BASE_URL } from '../../utils/request'
|
||
import { useCartStore } from '../../store/cart'
|
||
import BannerSwiper from '../../components/BannerSwiper.vue'
|
||
import ShippingNotice from '../../components/ShippingNotice.vue'
|
||
import SpecPanel from '../../components/SpecPanel.vue'
|
||
import CustomerServiceBtn from '../../components/CustomerServiceBtn.vue'
|
||
|
||
const cartStore = useCartStore()
|
||
const cartCount = computed(() => cartStore.items.length)
|
||
|
||
const product = ref<Product | null>(null)
|
||
const showSpecPanel = ref(false)
|
||
const showQrCode = ref(false)
|
||
|
||
const statusBarHeight = ref(20)
|
||
const navBarHeight = ref(44)
|
||
try {
|
||
const sysInfo = uni.getSystemInfoSync()
|
||
statusBarHeight.value = sysInfo.statusBarHeight || 20
|
||
// #ifdef MP-WEIXIN
|
||
const menuBtn = uni.getMenuButtonBoundingClientRect()
|
||
navBarHeight.value = (menuBtn.top - (sysInfo.statusBarHeight || 20)) * 2 + menuBtn.height
|
||
// #endif
|
||
} catch { /* fallback */ }
|
||
|
||
function goBack() {
|
||
uni.navigateBack({ delta: 1 })
|
||
}
|
||
|
||
function fullUrl(path: string): string {
|
||
if (!path) return ''
|
||
if (path.startsWith('http')) return path
|
||
return BASE_URL + path
|
||
}
|
||
|
||
function goCart() {
|
||
uni.switchTab({ url: '/pages/cart/index' })
|
||
}
|
||
|
||
onMounted(() => {
|
||
const pages = getCurrentPages()
|
||
const currentPage = pages[pages.length - 1] as { options?: { id?: string } }
|
||
const id = Number(currentPage.options?.id)
|
||
if (id) loadProduct(id)
|
||
cartStore.fetchCart()
|
||
})
|
||
|
||
async function loadProduct(id: number) {
|
||
try {
|
||
product.value = await getProductDetail(id)
|
||
} catch {
|
||
uni.showToast({ title: '加载商品失败', icon: 'none' })
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.product-detail {
|
||
padding-bottom: 140rpx;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
/* 自定义导航栏 */
|
||
.custom-navbar {
|
||
background: linear-gradient(to right, #FFCFDE, #FFA6C4);
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 100;
|
||
}
|
||
.custom-navbar__content {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 24rpx;
|
||
}
|
||
.custom-navbar__back {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
}
|
||
.custom-navbar__title {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 34rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
.custom-navbar__placeholder {
|
||
width: 44rpx;
|
||
}
|
||
|
||
/* Banner 容器 */
|
||
.banner-wrapper {
|
||
margin: 20rpx 24rpx 0;
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 基础信息卡片 */
|
||
.base-info {
|
||
background: #fff;
|
||
margin: 20rpx 24rpx 0;
|
||
border-radius: 20rpx;
|
||
padding: 30rpx;
|
||
}
|
||
.base-info__top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.base-info__name {
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
font-weight: 600;
|
||
flex: 1;
|
||
margin-right: 20rpx;
|
||
}
|
||
.base-info__price {
|
||
display: flex;
|
||
align-items: baseline;
|
||
color: #FF6D9B;
|
||
flex-shrink: 0;
|
||
}
|
||
.base-info__price-symbol {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
}
|
||
.base-info__price-num {
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
}
|
||
.base-info__price-unit {
|
||
font-size: 24rpx;
|
||
margin-left: 4rpx;
|
||
}
|
||
|
||
/* 属性行 - 两列布局 */
|
||
.base-info__attrs {
|
||
margin-top: 20rpx;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
padding-top: 20rpx;
|
||
}
|
||
.attr-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 8rpx 0;
|
||
}
|
||
.attr-label {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
width: 80rpx;
|
||
flex-shrink: 0;
|
||
letter-spacing: 4rpx;
|
||
}
|
||
.attr-value {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
min-width: 180rpx;
|
||
margin-right: 40rpx;
|
||
}
|
||
|
||
/* 商品详情大图 */
|
||
.detail-section {
|
||
background: #fff;
|
||
margin: 20rpx 24rpx 0;
|
||
border-radius: 20rpx;
|
||
padding: 30rpx;
|
||
}
|
||
.detail-section__title {
|
||
text-align: center;
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
font-weight: 600;
|
||
padding-bottom: 24rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
.detail-section__images {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
}
|
||
.detail-section__img {
|
||
width: 100%;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
/* 底部操作栏 */
|
||
.bottom-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: #fff;
|
||
padding: 16rpx 24rpx;
|
||
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
|
||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
.bottom-bar__icons {
|
||
display: flex;
|
||
gap: 32rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
.bottom-bar__icon-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4rpx;
|
||
}
|
||
.bottom-bar__icon-img {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
}
|
||
.cart-icon-wrap {
|
||
position: relative;
|
||
}
|
||
.cart-badge {
|
||
position: absolute;
|
||
top: -10rpx;
|
||
right: -16rpx;
|
||
background: #e91e63;
|
||
color: #fff;
|
||
font-size: 20rpx;
|
||
min-width: 32rpx;
|
||
height: 32rpx;
|
||
line-height: 32rpx;
|
||
text-align: center;
|
||
border-radius: 16rpx;
|
||
padding: 0 8rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
.bottom-bar__icon-text {
|
||
font-size: 20rpx;
|
||
color: #666;
|
||
}
|
||
.bottom-bar__main-btn {
|
||
flex: 1;
|
||
background: linear-gradient(to right, #f5a0b8, #e4393c);
|
||
color: #fff;
|
||
text-align: center;
|
||
padding: 24rpx 0;
|
||
border-radius: 44rpx;
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
}
|
||
</style>
|