You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
shop-pc/pages/goods/detail/_id.vue

502 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div>
<nav class="nav flex flex-middle flex-center">
<p class="nav__crumbs">
全部商品
<i class="el-icon-arrow-right"></i>
开发书籍
<i class="el-icon-arrow-right"></i>
后端书籍
<i class="el-icon-arrow-right"></i>
Java从入门到项目实战
</p>
</nav>
<main class="main flex">
<aside class="main__preview">
<img
class="main__preview-big"
:src="detailData.pictureList[activeImg]"
alt="商品大图"
/>
<div class="main__preview-control">
<img
class="main__preview-control--left"
src="~/static/images/goods/left.png"
alt="左侧切换"
@click="onImgLeft"
/>
<div
class="main__preview-control--imgs"
:style="
activeImg > 4
? 'transform: translateX(-' + (activeImg - 4) * 78 + 'px);'
: ''
"
>
<img
:class="index == activeImg ? 'img-active' : ''"
:src="item"
alt=""
v-for="(item, index) in detailData.pictureList"
:key="index"
@click="onActiveImg(index)"
/>
</div>
<img
class="main__preview-control--right"
src="~/static/images/goods/right.png"
alt="右侧切换"
@click="onImgRight"
/>
</div>
</aside>
<article class="main__details">
<p class="main__details-title">
<span class="main__details-title--label">新品</span>
{{ detailData.name }}
</p>
<div class="main__details-msg">
<div class="main__details-msg--price flex">
<span class="msg-txt">售价</span>
<UiMoney :money="detailData.startingPrice"></UiMoney>
</div>
<div class="hr"></div>
<div class="main__details-msg--service flex flex-middle">
<span class="msg-txt">服务</span>
<span class="msg-service"
>假一赔四 · 全国包邮 · 不支持7天无理由退换</span
>
<img
class="msg-icon"
src="~/static/images/goods/more.png"
alt="服务"
/>
</div>
</div>
<div class="main__details-option">
<div
class="main__details-option--line flex flex-middle"
v-for="(item, index) in detailData.attributeGroupList"
:key="item.id"
>
<span class="line-txt">{{ item.name }}</span>
<div class="line-btns">
<UiButton
type="yellow_line"
v-for="val in item.attributes"
:key="val.symbol"
@click="handleAttrItem(val, index)"
:class="{
'attr-item__active': val.active,
'attr-item__disabled': val.disabled,
}"
>{{ val.name }}</UiButton
>
</div>
</div>
</div>
<div class="main__details-pay">
<UiButton type="yellow_line">加入购物车</UiButton>
<UiButton type="yellow_panel" @click="buyNow">立即购买</UiButton>
</div>
</article>
</main>
<section class="section flex">
<div class="section-recommend">
<p class="section-title">看了又看</p>
<UiGoodsItem
v-for="item in recommendedData"
:item="item"
:key="item.id"
></UiGoodsItem>
</div>
<div class="section-details">
<p class="section-title">商品详情</p>
<div class="rich" v-html="detailData.detail"></div>
</div>
</section>
</div>
</template>
<script>
import UiMoney from "@/components/UiMoney.vue";
import UiButton from "@/components/UiButton.vue";
import {
ApiGetGoodsDetail,
ApiGetGoodsSkus,
ApiGetRecommendedGoodsList,
} from "@/plugins/api/goods";
import UiGoodsItem from "@/components/UiGoodsItem.vue";
export default {
componetns: { UiMoney, UiButton, UiGoodsItem },
data() {
return {
curBuyNum: 1,
activeImg: 0,
recommendedData: [],
skuData: {},
detailData: {
pictureList: [],
},
};
},
async created() {
let vm = this;
let id = this.$route.params.id;
let res1 = await ApiGetGoodsDetail({ id });
let res2 = await ApiGetGoodsSkus({ productId: id });
let res3 = await ApiGetRecommendedGoodsList();
vm.detailData = res1.result;
vm.skuData = res2.result;
vm.recommendedData = res3.result;
console.log(`res1`, res1.result);
console.log(`res2`, res2.result);
console.log(`res3`, res3.result);
},
computed: {
/**
* 当前选中SKU根据选中规格计算
*/
curSku() {
return (
this.skuData.find(
(i) => i.attributeSymbolList === this.selectedSymbol.join(",")
) || {}
);
},
// [1,.,3]
selectedSymbol() {
return this.detailData.attributeGroupList
.map((item) => {
const activeAttr = item.attributes.find((i) => i.active);
return activeAttr ? activeAttr.symbol : ".";
})
.filter((i) => i); //.sort();
},
/**
* 最大可购买数量
* 1、有限购则对比限购跟库存取最小值
* 2、没限购取库存
*/
maxBuyNum() {
const singleBuyLimit = this.detailData.singleBuyLimit;
const stock = this.curSku.stock;
return singleBuyLimit ? Math.min(singleBuyLimit, stock || 1) : stock;
},
},
methods: {
onActiveImg(i) {
this.activeImg = i;
},
onImgLeft() {
let vm = this;
if (vm.activeImg > 0) {
vm.activeImg--;
}
},
onImgRight() {
let vm = this;
if (vm.activeImg < vm.detailData.pictureList.length - 1) {
vm.activeImg++;
}
},
/**
* 设置默认选中规格
*/
setDefaultAttr() {
let vm = this;
const curSku = vm.skuData.find((i) => i.stock > 0);
if (!curSku) {
return false;
}
vm.detailData.attributeGroupList.forEach((item, index) => {
for (let i of item.attributes) {
if (curSku.attributeSymbolList.includes(i.symbol)) {
this.$set(i, "active", true);
this.setDisabledItem(i, index, true);
break;
}
}
});
this.$emit("input", this.curSku);
},
/**
* 点击属性项,设置选中和禁用项
*/
handleAttrItem(item, groupIndex) {
let vm = this;
// 禁用选项
if (item.disabled) {
return false;
}
// 每次重选规格购买数量都置为1
vm.curBuyNum = 1;
const active = item.active;
// 把当前选项组的装先置为未选状态
vm.detailData.attributeGroupList[groupIndex].attributes.forEach(
(item) => {
this.$set(item, "active", false);
}
);
// 设置当前点击选项选中
this.$set(item, "active", !active);
this.setDisabledItem(item, groupIndex);
},
/**
* 每次点击选项属性时,计算不可选属性
*/
setDisabledItem(item, groupIndex) {
let vm = this;
vm.detailData.attributeGroupList.forEach((group, idx) => {
// 拿到已选项数组,这个是按照项组顺序排序好的缓存数组
let symbolCache = Object.assign([], this.selectedSymbol);
// 跳过当前属性组
if (groupIndex === idx) return false;
// 遍历其他选项组中的选项
group.attributes.forEach((item) => {
// 根据选项组下标,补位选项属性
symbolCache[idx] = item.symbol;
const reg = new RegExp(symbolCache.join(","));
// 根据补位选项寻找是否有有效SKU有则可选没有则禁用
const res = vm.skuData
.filter((i) => reg.test(i.attributeSymbolList))
.find((i) => i.stock > 0);
if (res) {
item.disabled = false;
} else {
item.disabled = true;
}
});
});
},
buyNow() {
if (!this.curSku.skuId) {
this.$message.error("请选择规格~");
return false;
}
let query = {
mode: "buyNow",
skuId: this.curSku.skuId,
num: this.curBuyNum,
activityType: 1,
};
console.log(query);
this.$router.push({
path: "/order/submit",
query,
});
},
},
};
</script>
<style lang="scss" scoped>
.nav {
width: 100%;
height: 40px;
background: #f2f4f6;
margin-bottom: 14px;
&__crumbs {
width: 1200px;
}
}
.main {
width: 1200px;
margin: 0 auto;
&__preview {
width: 456px;
margin-right: 30px;
&-big {
width: 456px;
height: 456px;
margin-bottom: 24px;
}
&-control {
width: 100%;
height: 58px;
position: relative;
overflow: hidden;
padding-left: 43px;
&::before {
content: "";
width: 43px;
height: 58px;
background: #fff;
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
&::after {
content: "";
width: 43px;
height: 58px;
background: #fff;
position: absolute;
right: 0;
top: 0;
}
&--left,
&--right {
width: 20px;
height: 34px;
position: absolute;
top: 12px;
z-index: 1;
cursor: pointer;
}
&--left {
left: 0;
}
&--right {
right: 0;
}
&--imgs {
width: 1500px;
img {
width: 58px;
height: 58px;
margin-right: 20px;
cursor: pointer;
}
.img-active {
border: 1px solid #ff512b;
}
}
}
}
&__details {
width: 714px;
&-title {
font-size: 16px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #333333;
padding-bottom: 20px;
&--label {
display: inline-block;
font-size: 12px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #3083ff;
padding: 4px 8px;
margin-right: 6px;
background: rgba(48, 131, 255, 0.1);
}
}
&-msg {
width: 714px;
height: 127px;
background: #f8f8f8;
padding: 30px 20px 0;
.msg-txt {
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #9e9e9e;
margin-right: 20px;
}
.msg-service {
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #666666;
}
.msg-icon {
margin-left: 6px;
width: 12px;
height: 12px;
}
&--price {
padding-bottom: 30px;
}
.hr {
width: 673px;
height: 1px;
background: #dddddd;
}
&--service {
padding-top: 16px;
}
}
&-option {
padding-top: 24px;
&--line {
margin-bottom: 6px;
.line-txt {
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #9e9e9e;
margin-left: 20px;
margin-right: 33px;
}
.line-btns {
width: 600px;
/deep/.ui-button__yellow_line {
background: #fff;
margin-right: 14px;
color: #666;
margin-bottom: 6px;
border-color: #ccc;
}
}
}
}
&-pay {
margin-top: 40px;
button {
width: 144px;
height: 46px;
margin-right: 30px;
font-size: 18px;
&:nth-child(1) {
background: #fff;
}
&:nth-child(2) {
border: none;
}
}
}
}
}
.section {
width: 1200px;
margin: 64px auto 0;
&-recommend {
width: 210px;
margin-right: 30px;
/deep/.goods-item {
border: 1px solid #f2f2f2;
}
}
&-details {
width: 960px;
}
&-title {
width: 100%;
height: 40px;
line-height: 40px;
background: #f7f7f7;
border: 1px solid #f2f2f2;
padding-left: 16px;
font-size: 14px;
font-family: Microsoft YaHei-Regular, Microsoft YaHei;
font-weight: 400;
color: #666666;
}
}
.attr-item__active {
background: red !important;
}
.attr-item__disabled {
background: cyan !important;
}
</style>