167 lines
4.2 KiB
Vue
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>
|