128 lines
2.7 KiB
Vue
128 lines
2.7 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="loading" v-if="loading">加载中...</view>
|
||
<view v-else-if="detail.id" class="detail-wrap">
|
||
<image v-if="detail.main_image" class="main-image" :src="detail.main_image" mode="widthFix" />
|
||
<view class="info-card">
|
||
<view class="title">{{ detail.title || detail.name || '-' }}</view>
|
||
<view class="price-row">
|
||
<text class="price">¥{{ formatPrice(detail.price_sale || detail.price) }}</text>
|
||
<text class="points" v-if="detail.points_required">{{ detail.points_required }}积分</text>
|
||
</view>
|
||
<view class="stock" v-if="detail.stock !== null && detail.stock !== undefined">库存:{{ detail.stock }}</view>
|
||
<view class="desc" v-if="detail.description">{{ detail.description }}</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty">商品不存在</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { getProductDetail } from '../../api/appUser'
|
||
|
||
const detail = ref({})
|
||
const loading = ref(false)
|
||
|
||
function formatPrice(p) {
|
||
if (p === undefined || p === null) return '0.00'
|
||
return (Number(p) / 100).toFixed(2)
|
||
}
|
||
|
||
async function fetchDetail(id) {
|
||
loading.value = true
|
||
try {
|
||
const res = await getProductDetail(id)
|
||
detail.value = res || {}
|
||
} catch (e) {
|
||
detail.value = {}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onLoad((opts) => {
|
||
const id = opts && opts.id
|
||
if (id) fetchDetail(id)
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* ============================================
|
||
奇盒潮玩 - 商品详情页
|
||
============================================ */
|
||
|
||
.page {
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #FFF8F3 0%, #FFFFFF 100%);
|
||
}
|
||
|
||
.loading, .empty {
|
||
text-align: center;
|
||
padding: 120rpx 40rpx;
|
||
color: #9CA3AF;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.detail-wrap {
|
||
padding-bottom: 40rpx;
|
||
}
|
||
|
||
.main-image {
|
||
width: 100%;
|
||
display: block;
|
||
}
|
||
|
||
.info-card {
|
||
margin: 24rpx;
|
||
background: #FFFFFF;
|
||
border-radius: 20rpx;
|
||
padding: 28rpx;
|
||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.title {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #1F2937;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.price-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.price {
|
||
font-size: 40rpx;
|
||
font-weight: 800;
|
||
background: linear-gradient(135deg, #FF6B35, #FF9F43);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.points {
|
||
font-size: 26rpx;
|
||
color: #FF9F43;
|
||
padding: 4rpx 12rpx;
|
||
background: rgba(255, 159, 67, 0.15);
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
.stock {
|
||
font-size: 26rpx;
|
||
color: #6B7280;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.desc {
|
||
font-size: 28rpx;
|
||
color: #4B5563;
|
||
line-height: 1.7;
|
||
}
|
||
</style>
|