2025-10-16 17:07:47 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="detail-container">
|
|
|
|
|
|
<u-navbar id="uNavbarId" title="商品详情" :background="{background: 'transparent' }" :border-bottom="false"
|
|
|
|
|
|
back-icon-color="#000" title-color="#000">
|
|
|
|
|
|
<template v-slot:right>
|
|
|
|
|
|
<image class="collection" src="/static/icon/Settings.png" mode=""></image>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</u-navbar>
|
|
|
|
|
|
<scroll-view scroll-y="true" :style="'height:'+scrollHeight+'px'">
|
2025-10-21 15:25:55 +08:00
|
|
|
|
<view class="img-banner">
|
|
|
|
|
|
<u-swiper height="400" img-mode="aspectFit" mode="none" bg-color="transparent" :list="dataInfo.banner"
|
|
|
|
|
|
@change="handleChangeBanner" :current="currentBanner"></u-swiper>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<scroll-view scroll-x="true" class="img-sub-banner">
|
|
|
|
|
|
<image v-for="(item,index) in dataInfo.banner" :src="item" class="img-item"
|
|
|
|
|
|
:class="{active: currentBanner==index}" mode="aspectFit" @click="handleChangeBanner(index)">
|
|
|
|
|
|
</image>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="mall-info">
|
|
|
|
|
|
<view class="name">
|
|
|
|
|
|
{{dataInfo.name}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="description">
|
|
|
|
|
|
{{dataInfo.description}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="price">
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<view class="mall-price">
|
|
|
|
|
|
<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
|
|
|
|
|
{{dataInfo.rongdou_price}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mall-price">
|
2025-10-23 10:18:06 +08:00
|
|
|
|
<image src="/static/icon/jifen.png" class="icon" mode=""></image>
|
2025-10-21 15:25:55 +08:00
|
|
|
|
{{dataInfo.points_price}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view v-if="dataInfo.discount" class="discount-info">
|
|
|
|
|
|
<u-tag type="error" text="1折优惠" />
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<view>已售{{dataInfo.sales}}</view>
|
|
|
|
|
|
<view style="color: red;">剩余{{dataInfo.stock}}</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="rating">
|
|
|
|
|
|
评分:<u-rate :disabled="true" :count="5" v-model="dataInfo.rating" active-color="#f7ba2a"></u-rate>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="detail">
|
|
|
|
|
|
<h3>具体描述</h3>
|
|
|
|
|
|
<view v-html="dataInfo.details"></view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="recommend">
|
|
|
|
|
|
<h3>推荐商品</h3>
|
|
|
|
|
|
<view class="mall-list">
|
|
|
|
|
|
<u-waterfall v-model="recommendedList" ref="mallListRef">
|
|
|
|
|
|
<template v-slot:left="{leftList}">
|
|
|
|
|
|
<view class="mall-item u-m-r-10" v-for="(item, index) in leftList" :key="index"
|
|
|
|
|
|
@click="handleCheck(item)">
|
|
|
|
|
|
<u-lazy-load threshold="-450" border-radius="10" :image="getImageUrl(item.image)"
|
|
|
|
|
|
:index="index"></u-lazy-load>
|
|
|
|
|
|
<view class="mall-title u-m-l-5 u-m-r-5">
|
|
|
|
|
|
{{item.name}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
|
|
|
|
|
<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
|
|
|
|
|
{{item.price}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
2025-10-23 10:18:06 +08:00
|
|
|
|
<image src="/static/icon/jifen.png" class="icon" mode=""></image>
|
2025-10-21 15:25:55 +08:00
|
|
|
|
{{item.points}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-slot:right="{rightList}">
|
|
|
|
|
|
<view class="mall-item u-m-l-10" v-for="(item, index) in rightList" :key="index"
|
|
|
|
|
|
@click="handleCheck(item)">
|
|
|
|
|
|
<u-lazy-load threshold="-450" border-radius="10" :image="getImageUrl(item.image)"
|
|
|
|
|
|
:index="index"></u-lazy-load>
|
|
|
|
|
|
<view class="mall-title u-m-l-5 u-m-r-5">
|
|
|
|
|
|
{{item.name}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
|
|
|
|
|
<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
|
|
|
|
|
{{item.price}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
2025-10-23 10:18:06 +08:00
|
|
|
|
<image src="/static/icon/jifen.png" class="icon" mode=""></image>
|
2025-10-21 15:25:55 +08:00
|
|
|
|
{{item.points}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</u-waterfall>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-10-16 17:07:47 +08:00
|
|
|
|
</scroll-view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="bottom-view" id="bottomViewId">
|
|
|
|
|
|
<view class="icon-btn">
|
2025-10-23 10:18:06 +08:00
|
|
|
|
<!-- <view class="item">
|
2025-10-16 17:07:47 +08:00
|
|
|
|
<u-image width="100%" :fade="false" src="/static/mall/Home.png" mode="widthFix"></u-image>
|
|
|
|
|
|
店铺
|
2025-10-21 15:25:55 +08:00
|
|
|
|
</view> -->
|
2025-10-16 17:07:47 +08:00
|
|
|
|
<view class="item">
|
|
|
|
|
|
<u-image width="100%" :fade="false" src="/static/mall/Twitch.png" mode="widthFix"></u-image>
|
|
|
|
|
|
客服
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="item">
|
|
|
|
|
|
<u-image width="100%" :fade="false" src="/static/mall/Star.png" mode="widthFix"></u-image>
|
|
|
|
|
|
收藏
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="text-btn">
|
2025-10-21 15:25:55 +08:00
|
|
|
|
<u-button class="add-car common" :hair-line="false" hover-class="none"
|
|
|
|
|
|
@click="handleBtn(0)">加入购物车</u-button>
|
|
|
|
|
|
<u-button class="buy common" :hair-line="false" hover-class="none" @click="handleBtn(1)">领券购买</u-button>
|
2025-10-16 17:07:47 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-10-21 15:25:55 +08:00
|
|
|
|
|
|
|
|
|
|
<u-popup v-model="showSure" mode="bottom" length="70%" :closeable="true" blur="90%">
|
|
|
|
|
|
<scroll-view scroll-y="true" style="height: 100%;">
|
|
|
|
|
|
<view class="sure-popup">
|
|
|
|
|
|
<view class="title">{{popTitle}}</view>
|
|
|
|
|
|
<view class="address">
|
|
|
|
|
|
<view class="text">
|
|
|
|
|
|
<image style="width: 40rpx;height: 40rpx;" src="/static/icon/Map pin2.png" mode=""></image>
|
|
|
|
|
|
<view class="u-m-l-10">张三 | </view>
|
|
|
|
|
|
<view class="u-m-l-10">浙江省 宁波市 海曙区</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="right-icon">
|
|
|
|
|
|
<image style="width: 100%;height: 100%;" src="/static/icon/Chevron right Menu.png" mode="">
|
|
|
|
|
|
</image>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="count-select u-p-l-10 u-p-r-10">
|
|
|
|
|
|
<view class="pre-view">
|
|
|
|
|
|
<u-image :src="getImageUrl(dataInfo.image_url)" height="100%" width="100%">
|
|
|
|
|
|
<template v-slot:error>
|
|
|
|
|
|
<view style="font-size: 24rpx;">暂无图片</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</u-image>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="text">
|
|
|
|
|
|
<view>
|
|
|
|
|
|
实付<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
|
|
|
|
|
{{dataInfo.price * order.count}}
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<u-icon name="integral"></u-icon>
|
|
|
|
|
|
{{dataInfo.points * order.count}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<u-number-box v-model="order.count" :max="dataInfo.stock" :min="1"></u-number-box>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="spec-option" v-for="item in specNames">
|
|
|
|
|
|
<view class="title">
|
|
|
|
|
|
{{item + "("+specOptions.get(item).size+")"}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="option-list">
|
|
|
|
|
|
<view class="option-item" v-for="key in specOptions.get(item).keys()" :key="key"
|
|
|
|
|
|
:class="{active: isChose(key), unactive: !canChose(key)}"
|
|
|
|
|
|
@click="handleChangeSpec(key, item)">
|
|
|
|
|
|
{{specOptions.get(item).get(key)}}
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="spec-option">
|
|
|
|
|
|
<view class="title">
|
|
|
|
|
|
订单备注
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="">
|
|
|
|
|
|
<u-input type="textarea" :border="true" :height="100" v-model="order.orderNote"></u-input>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="spec-option">
|
|
|
|
|
|
<u-button @click="handleSubmit">{{popTitle}}</u-button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
</u-popup>
|
|
|
|
|
|
|
|
|
|
|
|
<u-toast ref="msgRef" />
|
2025-10-16 17:07:47 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
2025-10-21 15:25:55 +08:00
|
|
|
|
<script setup>
|
|
|
|
|
|
import {
|
|
|
|
|
|
onMounted,
|
|
|
|
|
|
ref,
|
|
|
|
|
|
getCurrentInstance
|
|
|
|
|
|
} from 'vue';
|
2025-10-17 14:10:49 +08:00
|
|
|
|
import {
|
|
|
|
|
|
onLoad
|
|
|
|
|
|
} from '@dcloudio/uni-app';
|
2025-10-21 15:25:55 +08:00
|
|
|
|
import {
|
|
|
|
|
|
mallAPI
|
|
|
|
|
|
} from '../../api/mall';
|
|
|
|
|
|
import {
|
2025-10-23 10:18:06 +08:00
|
|
|
|
getImageUrl, arrayContainsAll
|
2025-10-21 15:25:55 +08:00
|
|
|
|
} from '../../util/common.js';
|
2025-10-16 17:07:47 +08:00
|
|
|
|
|
|
|
|
|
|
const instance = getCurrentInstance();
|
|
|
|
|
|
const scrollHeight = ref(0)
|
|
|
|
|
|
const loadHeight = () => {
|
|
|
|
|
|
uni.getSystemInfo({
|
|
|
|
|
|
success(res) {
|
|
|
|
|
|
let screenHeight = res.screenHeight
|
2025-10-21 15:25:55 +08:00
|
|
|
|
uni.createSelectorQuery().in(instance.proxy).select("#uNavbarId").boundingClientRect((
|
|
|
|
|
|
data) => {
|
2025-10-16 17:07:47 +08:00
|
|
|
|
scrollHeight.value = screenHeight - data.height
|
|
|
|
|
|
}).exec()
|
2025-10-21 15:25:55 +08:00
|
|
|
|
uni.createSelectorQuery().in(instance.proxy).select("#bottomViewId").boundingClientRect((
|
|
|
|
|
|
data) => {
|
2025-10-16 17:07:47 +08:00
|
|
|
|
scrollHeight.value = scrollHeight.value - data.height
|
|
|
|
|
|
}).exec()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-10-21 15:25:55 +08:00
|
|
|
|
const currentBanner = ref(0)
|
|
|
|
|
|
const handleChangeBanner = (index) => {
|
|
|
|
|
|
currentBanner.value = index
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleCheck = (item) => {
|
|
|
|
|
|
uni.redirectTo({
|
|
|
|
|
|
url: '/pages/home/mallDetail?id=' + item.id
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
const msgRef = ref()
|
|
|
|
|
|
|
|
|
|
|
|
// 切换 加入购物车/确认购买
|
|
|
|
|
|
const showSure = ref(false)
|
|
|
|
|
|
const popTitle = ref('')
|
|
|
|
|
|
const flag = ref(0)
|
|
|
|
|
|
|
|
|
|
|
|
const handleBtn = (sign) => {
|
|
|
|
|
|
if (sign == 0) {
|
|
|
|
|
|
popTitle.value = '加入购物车'
|
|
|
|
|
|
flag.value = 0
|
|
|
|
|
|
} else if (sign == 1) {
|
|
|
|
|
|
popTitle.value = '确认购买'
|
|
|
|
|
|
flag.value = 1
|
|
|
|
|
|
}
|
|
|
|
|
|
showSure.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const order = ref({
|
|
|
|
|
|
count: 1
|
|
|
|
|
|
})
|
|
|
|
|
|
const choseKeys = ref([])
|
|
|
|
|
|
|
|
|
|
|
|
const handleChangeSpec = (key, allKey) => {
|
|
|
|
|
|
if (!canChose(key)) return
|
|
|
|
|
|
if (choseKeys.value.includes(key)) {
|
|
|
|
|
|
// 取消选中
|
|
|
|
|
|
choseKeys.value = choseKeys.value.filter(item => item != key)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// console.log(choseKeys.value);
|
|
|
|
|
|
// console.log(specOptions.value.get(allKey));
|
|
|
|
|
|
// 清除选项
|
|
|
|
|
|
const keysArray = Array.from(specOptions.value.get(allKey).keys())
|
|
|
|
|
|
choseKeys.value = choseKeys.value.filter(item => {
|
|
|
|
|
|
if (keysArray.includes(item)) return false
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
|
|
|
|
|
// 选中
|
|
|
|
|
|
choseKeys.value.push(key)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断当前key是否可选
|
|
|
|
|
|
const canChose = (key) => {
|
|
|
|
|
|
// console.log("当前规格ID:", key);
|
|
|
|
|
|
// console.log("规格组合列表:", activeKeys.value);
|
|
|
|
|
|
// console.log("已选择的规格ID列表:", choseKeys.value);
|
|
|
|
|
|
// console.log("规格选项:", specOptions.value);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已经选中,肯定可选(因为可以取消选中)
|
|
|
|
|
|
if (isChose(key)) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建临时选择的key列表
|
|
|
|
|
|
let tempChoseKeys = [...choseKeys.value];
|
|
|
|
|
|
|
|
|
|
|
|
// 找到当前key所属的规格类别
|
|
|
|
|
|
let specCategory = '';
|
|
|
|
|
|
for (const [category, options] of specOptions.value.entries()) {
|
|
|
|
|
|
if (options.has(key)) {
|
|
|
|
|
|
specCategory = category;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果找到了所属规格类别,需要先移除同类别下已选的其他选项
|
|
|
|
|
|
if (specCategory) {
|
|
|
|
|
|
const sameCategoryKeys = Array.from(specOptions.value.get(specCategory).keys());
|
|
|
|
|
|
tempChoseKeys = tempChoseKeys.filter(item => !sameCategoryKeys.includes(item));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加当前要判断的key
|
|
|
|
|
|
tempChoseKeys.push(key);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否存在包含所有临时选择key的有效规格组合
|
|
|
|
|
|
const hasValidCombination = activeKeys.value.some(combination => {
|
|
|
|
|
|
return arrayContainsAll(combination, tempChoseKeys);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return hasValidCombination;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 是否被选中
|
|
|
|
|
|
const isChose = (key) => {
|
|
|
|
|
|
return choseKeys.value.includes(key)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmit = () => {
|
|
|
|
|
|
// 匹配规格
|
|
|
|
|
|
let specification = dataInfo.value.specifications.filter(item => {
|
|
|
|
|
|
let group = item.combination_key.split('-').map(Number)
|
|
|
|
|
|
if (arraysEqualUnordered(group, choseKeys.value)) return true
|
|
|
|
|
|
return false
|
|
|
|
|
|
})
|
|
|
|
|
|
let specificationId = null
|
|
|
|
|
|
if (specification.length != 0) {
|
|
|
|
|
|
specificationId = specification[0].id
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgRef.value.show({
|
|
|
|
|
|
title: '请选择规格',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 立即购买
|
|
|
|
|
|
const cartItem = {
|
|
|
|
|
|
productId: dataInfo.value.id,
|
|
|
|
|
|
quantity: order.value.count,
|
|
|
|
|
|
specificationId: specificationId,
|
|
|
|
|
|
points: dataInfo.value.points,
|
|
|
|
|
|
name: dataInfo.value.name,
|
|
|
|
|
|
image: dataInfo.value.image,
|
|
|
|
|
|
stock: dataInfo.value.stock
|
|
|
|
|
|
}
|
|
|
|
|
|
if (flag.value == 0) {
|
|
|
|
|
|
// 加入购物车
|
|
|
|
|
|
mallAPI.addCart(cartItem).then(res => {
|
|
|
|
|
|
if (res.success) {
|
|
|
|
|
|
msgRef.value.show({
|
|
|
|
|
|
title: '已加入购物车',
|
|
|
|
|
|
type: 'error'
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgRef.value.show({
|
|
|
|
|
|
title: res.data.message || '添加到购物车失败',
|
|
|
|
|
|
type: 'error'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}).finally(() => {
|
|
|
|
|
|
order.value.count = 1
|
|
|
|
|
|
order.value.orderNote = ''
|
|
|
|
|
|
choseKeys.value = []
|
|
|
|
|
|
showSure.value = false
|
|
|
|
|
|
})
|
|
|
|
|
|
} else if (flag.value == 1) {
|
|
|
|
|
|
mallAPI.addCart(cartItem).then(res => {
|
|
|
|
|
|
if (res.success) {
|
|
|
|
|
|
const cartItemId = res.data?.cart_item_id || res.data?.id || res.data
|
|
|
|
|
|
?.cartItemId || res.id
|
|
|
|
|
|
if (!cartItemId) {
|
|
|
|
|
|
msgRef.value.show({
|
|
|
|
|
|
title: '无法获取购物车项ID',
|
|
|
|
|
|
type: 'error'
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
mallAPI.createOrder({
|
|
|
|
|
|
cart_item_ids: [cartItemId]
|
|
|
|
|
|
}).then(response => {
|
|
|
|
|
|
if (response.success) {
|
2025-10-23 10:18:06 +08:00
|
|
|
|
showSure.value = false
|
2025-10-21 15:25:55 +08:00
|
|
|
|
// 进入支付页面
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: '/pages/home/pay?preOrderId=' + response.data
|
|
|
|
|
|
.preOrderId
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgRef.value.show({
|
|
|
|
|
|
title: '操作失败',
|
|
|
|
|
|
type: 'error'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
msgRef.value.show({
|
|
|
|
|
|
title: res.data.message || '添加到购物车失败',
|
|
|
|
|
|
type: 'error'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断两个数组值是否相等
|
|
|
|
|
|
const arraysEqualUnordered = (arr1, arr2) => {
|
|
|
|
|
|
if (arr1.length !== arr2.length) return false;
|
|
|
|
|
|
|
|
|
|
|
|
const sorted1 = [...arr1].sort();
|
|
|
|
|
|
const sorted2 = [...arr2].sort();
|
|
|
|
|
|
|
|
|
|
|
|
return sorted1.every((item, index) => item === sorted2[index]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 17:07:47 +08:00
|
|
|
|
|
2025-10-17 14:10:49 +08:00
|
|
|
|
const dataId = ref()
|
2025-10-21 15:25:55 +08:00
|
|
|
|
const dataInfo = ref({})
|
|
|
|
|
|
const recommendedList = ref([])
|
|
|
|
|
|
const specNames = ref(new Set())
|
|
|
|
|
|
const specOptions = ref(new Map())
|
|
|
|
|
|
const activeKeys = ref([])
|
2025-10-17 14:10:49 +08:00
|
|
|
|
|
|
|
|
|
|
const loadData = () => {
|
|
|
|
|
|
mallAPI.getMallDetail(dataId.value).then(res => {
|
2025-10-21 15:25:55 +08:00
|
|
|
|
dataInfo.value = res.data.product
|
|
|
|
|
|
dataInfo.value.banner = []
|
|
|
|
|
|
if (dataInfo.value.images.length != 0) {
|
|
|
|
|
|
dataInfo.value.images.forEach(item => {
|
|
|
|
|
|
dataInfo.value.banner.push(getImageUrl(item))
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
loadSpec()
|
|
|
|
|
|
})
|
|
|
|
|
|
mallAPI.getRecommended(dataId.value).then(res => {
|
|
|
|
|
|
recommendedList.value = res.data.products
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const loadSpec = () => {
|
|
|
|
|
|
dataInfo.value.specifications.forEach(specification => {
|
|
|
|
|
|
activeKeys.value.push(specification.combination_key)
|
|
|
|
|
|
specification.spec_details.forEach(detail => {
|
|
|
|
|
|
specNames.value.add(detail.spec_display_name)
|
|
|
|
|
|
let data = specOptions.value.get(detail.spec_display_name)
|
|
|
|
|
|
if (data == undefined) {
|
|
|
|
|
|
data = new Map()
|
|
|
|
|
|
data.set(detail.id, detail.display_value)
|
|
|
|
|
|
specOptions.value.set(detail.spec_display_name, data)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
data.set(detail.id, detail.display_value)
|
|
|
|
|
|
specOptions.value.set(detail.spec_display_name, data)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-10-17 14:10:49 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onLoad((val) => {
|
|
|
|
|
|
dataId.value = val.id
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-10-16 17:07:47 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadHeight()
|
2025-10-17 14:10:49 +08:00
|
|
|
|
loadData()
|
2025-10-16 17:07:47 +08:00
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.detail-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
background: linear-gradient(180deg, #2F75F9 0%, #F0F3FF 34.13%);
|
|
|
|
|
|
|
|
|
|
|
|
.collection {
|
|
|
|
|
|
width: 48rpx;
|
|
|
|
|
|
height: 48rpx;
|
|
|
|
|
|
margin-right: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-21 15:25:55 +08:00
|
|
|
|
.img-sub-banner {
|
|
|
|
|
|
padding: 12rpx 0;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
.img-item {
|
|
|
|
|
|
border-radius: 20rpx;
|
|
|
|
|
|
width: 128rpx;
|
|
|
|
|
|
height: 128rpx;
|
|
|
|
|
|
margin: 0 4rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.active {
|
|
|
|
|
|
border: 1px solid #189eff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mall-info {
|
|
|
|
|
|
padding: 20rpx 34rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.name {
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
leading-trim: NONE;
|
|
|
|
|
|
line-height: 46rpx;
|
|
|
|
|
|
letter-spacing: 0%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.price {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
.mall-price {
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
color: #305DEF;
|
|
|
|
|
|
margin-top: 10rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
|
|
height: 30rpx;
|
|
|
|
|
|
width: 30rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail {
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
border: 1rpx solid #fff;
|
|
|
|
|
|
padding: 10rpx 20rpx;
|
|
|
|
|
|
box-shadow: 1px 1px 2px 2px rgba(0, 0, 255, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.recommend {
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.mall-list {
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.mall-item {
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
background: #F0F5FF;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
margin-top: 20rpx;
|
|
|
|
|
|
box-shadow: 0px 4px 4px 0px #00000040;
|
|
|
|
|
|
padding-bottom: 10rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.mall-title {
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
margin-top: 10rpx;
|
|
|
|
|
|
color: $u-main-color;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mall-price {
|
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
|
color: $u-type-error;
|
|
|
|
|
|
margin-top: 10rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
|
|
height: 30rpx;
|
|
|
|
|
|
width: 30rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.mall-tag {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
margin-top: 5px;
|
|
|
|
|
|
|
|
|
|
|
|
.mall-tag-owner {
|
|
|
|
|
|
background-color: $u-type-error;
|
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 4rpx 14rpx;
|
|
|
|
|
|
border-radius: 50rpx;
|
|
|
|
|
|
font-size: 20rpx;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mall-tag-text {
|
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
|
border: 1px solid $u-type-primary;
|
|
|
|
|
|
color: $u-type-primary;
|
|
|
|
|
|
border-radius: 50rpx;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
padding: 4rpx 14rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
border-radius: 50rpx;
|
|
|
|
|
|
font-size: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mall-shop {
|
|
|
|
|
|
font-size: 22rpx;
|
|
|
|
|
|
color: $u-tips-color;
|
|
|
|
|
|
margin-top: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-10-16 17:07:47 +08:00
|
|
|
|
.bottom-view {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 116rpx;
|
|
|
|
|
|
background: #F5F8FF;
|
|
|
|
|
|
padding: 0 29rpx;
|
|
|
|
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
.icon-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
width: 228rpx;
|
|
|
|
|
|
width: 30%;
|
|
|
|
|
|
flex-wrap: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
.item {
|
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
font-family: Work Sans;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-style: Regular;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
leading-trim: NONE;
|
|
|
|
|
|
line-height: 100%;
|
|
|
|
|
|
letter-spacing: -2%;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.text-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
|
|
|
|
|
|
.common {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
width: 200rpx;
|
|
|
|
|
|
height: 70rpx;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-family: Work Sans;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
font-style: Regular;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
leading-trim: NONE;
|
|
|
|
|
|
line-height: 100%;
|
|
|
|
|
|
letter-spacing: -2%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.buy {
|
|
|
|
|
|
background: #6287FF;
|
|
|
|
|
|
border-top-right-radius: 12rpx;
|
|
|
|
|
|
border-bottom-right-radius: 12rpx;
|
|
|
|
|
|
border-top-left-radius: 0;
|
|
|
|
|
|
border-bottom-left-radius: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.add-car {
|
|
|
|
|
|
background: #A8BCFF;
|
|
|
|
|
|
border-top-right-radius: 0rpx;
|
|
|
|
|
|
border-bottom-right-radius: 0rpx;
|
|
|
|
|
|
border-top-left-radius: 12rpx;
|
|
|
|
|
|
border-bottom-left-radius: 12rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-21 15:25:55 +08:00
|
|
|
|
|
|
|
|
|
|
.sure-popup {
|
|
|
|
|
|
background: #F5F8FF;
|
|
|
|
|
|
padding-bottom: 20rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-weight: 274;
|
|
|
|
|
|
font-style: Light;
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
leading-trim: NONE;
|
|
|
|
|
|
line-height: 48rpx;
|
|
|
|
|
|
letter-spacing: 0%;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.address {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-top: 30rpx;
|
|
|
|
|
|
padding: 20rpx 10rpx;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
|
|
|
|
|
|
.text {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.right-icon {
|
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.count-select {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
margin-top: 10rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.pre-view {
|
|
|
|
|
|
width: 160rpx;
|
|
|
|
|
|
height: 160rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.text {
|
|
|
|
|
|
margin-left: 20rpx;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
|
|
height: 30rpx;
|
|
|
|
|
|
width: 30rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.spec-option {
|
|
|
|
|
|
margin: 20rpx 0;
|
|
|
|
|
|
padding: 0 20rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-style: Bold;
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
leading-trim: NONE;
|
|
|
|
|
|
line-height: 48rpx;
|
|
|
|
|
|
letter-spacing: 0%;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.option-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
|
|
|
|
.option-item {
|
|
|
|
|
|
background: #f2f3f5;
|
|
|
|
|
|
padding: 10rpx 20rpx;
|
|
|
|
|
|
margin: 20rpx 20rpx 20rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.active {
|
|
|
|
|
|
border: 1rpx solid #ff6b35;
|
|
|
|
|
|
color: #ff6b35;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.unactive {
|
|
|
|
|
|
background: #D9D9D9;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-16 17:07:47 +08:00
|
|
|
|
</style>
|