Files
flowers/frontend/src/views/ProductDetail.vue
王子琦 7a50853cac add
2026-01-16 15:33:29 +08:00

167 lines
4.2 KiB
Vue

<template>
<div class="page">
<el-card>
<el-button type="text" @click="$router.push('/')">返回首页</el-button>
</el-card>
<el-card class="content">
<el-row :gutter="20">
<el-col :span="10">
<img :src="product.coverUrl || placeholder" class="cover" />
</el-col>
<el-col :span="14">
<h2>{{ product.name }}</h2>
<p>{{ product.description }}</p>
<div class="price">{{ product.price }}</div>
<el-input-number v-model="quantity" :min="1" :max="product.stock || 99" />
<div class="section">
<el-select v-model="addressId" placeholder="选择收货地址">
<el-option
v-for="addr in addresses"
:key="addr.id"
:label="formatAddress(addr)"
:value="addr.id"
/>
</el-select>
<el-button type="primary" @click="submitOrder">立即购买</el-button>
</div>
</el-col>
</el-row>
</el-card>
<el-card class="reviews">
<div class="review-title">用户评价</div>
<div v-if="reviews.length === 0" class="review-empty">暂无评价</div>
<div v-else>
<div v-for="item in reviews" :key="item.id" class="review-item">
<el-rate :value="item.rating" disabled />
<div class="review-content">{{ item.content }}</div>
</div>
</div>
</el-card>
</div>
</template>
<script>
import { getProduct } from '../api/product';
import { listAddresses } from '../api/address';
import { createOrder } from '../api/order';
import { listReviewsByProduct } from '../api/review';
import { codeToText } from 'element-china-area-data';
export default {
data() {
return {
product: {},
quantity: 1,
addresses: [],
addressId: null,
reviews: [],
placeholder: 'https://via.placeholder.com/400x300?text=Flower'
};
},
created() {
this.loadDetail();
this.loadAddresses();
this.loadReviews();
},
methods: {
loadDetail() {
getProduct(this.$route.params.id).then((res) => {
this.product = res.data.data || {};
});
},
loadAddresses() {
listAddresses().then((res) => {
this.addresses = res.data.data || [];
const def = this.addresses.find((item) => item.isDefault);
if (def) {
this.addressId = def.id;
}
});
},
formatAddress(addr) {
const province = this.formatRegion(addr.province);
const city = this.formatRegion(addr.city);
const district = this.formatRegion(addr.district);
return `${addr.recipientName} ${addr.phone} ${province}${city}${district}${addr.detail || ''}`;
},
formatRegion(value) {
if (!value) return '';
const key = String(value);
if (codeToText && codeToText[key]) {
return codeToText[key];
}
return value;
},
submitOrder() {
if (!this.addressId) {
this.$message.warning('请选择收货地址');
return;
}
createOrder({
addressId: this.addressId,
items: [{ productId: this.product.id, quantity: this.quantity }]
}).then((res) => {
if (!res || !res.data || res.data.code !== 0 || !res.data.data) {
return;
}
this.$message.success('下单成功');
const order = res.data.data.order;
this.$router.push(`/orders?highlight=${order.id}`);
});
},
loadReviews() {
listReviewsByProduct(this.$route.params.id).then((res) => {
this.reviews = res.data.data || [];
});
}
}
};
</script>
<style scoped>
.page {
padding: 20px;
}
.content {
margin-top: 16px;
}
.cover {
width: 100%;
border-radius: 8px;
object-fit: cover;
}
.price {
color: #f56c6c;
font-size: 20px;
margin: 12px 0;
}
.section {
margin-top: 20px;
display: flex;
align-items: center;
}
.section .el-select {
margin-right: 12px;
width: 300px;
}
.reviews {
margin-top: 16px;
}
.review-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
}
.review-empty {
color: #909399;
}
.review-item {
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
}
.review-content {
margin-top: 6px;
color: #606266;
}
</style>