Merge branch 'dev' into 'master'

uniapp【修复】:sku优化多层选择,默认,置灰等问题/分类页面增加标题

See merge request jianweie/coreshoppro!85
This commit is contained in:
花城
2024-11-15 04:44:10 +00:00
6 changed files with 293 additions and 218 deletions
@@ -134,6 +134,7 @@ export interface GoodsSkuListType {
sku_name_arr ?: Array<string>;
stock ?: number;
_id ?: string
[key:string]:any;
}
export interface GoodsSpecListType {
@@ -141,6 +142,7 @@ export interface GoodsSpecListType {
name ?: string;
select ?: boolean;
show ?: boolean;
[key:string]:any;
}
export interface GoodsFootprintType {
@@ -8,15 +8,22 @@
.back {
width: 50rpx;
height: 50rpx;
margin-right: 20rpx;
margin-right: 10rpx;
}
.page-title{
font-size: 27rpx;
margin-right: 10rpx;
width: 110rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.search-tit {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 10rpx;
font-size: 24rpx;
font-size: 22rpx;
width: 100rpx;
text-align: center;
line-height: 50rpx;
@@ -5,10 +5,13 @@
<view class="search-box">
<image @click="hanlderBack" class="back" :src="handleStaticResources('/static/images/back.png')">
</image>
<uv-input shape="circle" :placeholder="state.pageTitle ? state.pageTitle :`请输入关键词`"
<view class="page-title">
{{state.pageTitle}}
</view>
<uv-input shape="circle" placeholder="请输入关键词"
prefixIcon="search" v-model="state.searchKey"
:customStyle="{ 'background-color': '#fff','padding':'3px 9px' }" @confirm="handleSearch"
:customStyle="{ 'background-color': '#fff','padding':'3px 9px','width':state.searchInputW}"
@confirm="handleSearch"
prefixIconStyle="font-size: 22px;color: #909399">
<template #suffix>
<view class="search-tit" @click="handleSearch">搜索</view>
@@ -167,6 +170,7 @@
priceTo : number | string;
brandId : string;
pageTitle : string;
searchInputW:string;
}>({
searchKey: "",
page: 1,
@@ -217,6 +221,7 @@
priceTo: '',
brandId: "",
pageTitle: "",
searchInputW:"320rpx"
})
let queryParams : QueryParams
@@ -235,6 +240,10 @@
queryParams = query;
/** 处理 query 然后获取商品列表 */
handleuQueryProduct(query);
// #ifdef APP
state.searchInputW = 'auto';
// #endif
});
onPullDownRefresh(async () => {
@@ -2,7 +2,7 @@
<view class="m-b-25 p-25 bg-white radius-15" v-if="props.data?.list?.length > 0">
<view class="coupon-box">
<view class="title-box">
<text class="tit">领卷专区</text>
<text class="tit">领券专区</text>
<coreshop-view-more @hanldeClickViewMore="hanldeClickViewMore"></coreshop-view-more>
</view>
<view class="coupon-card-box">
@@ -56,11 +56,10 @@
.tag {
position: relative;
font-size: 27rpx;
padding: 15rpx 20rpx;
border: 1rpx solid #6e737d;
border: 1rpx solid #f4f4f4;
border-radius: 15rpx;
text-align: center;
height: max-content;
margin: inherit;
background-color: #fff;
.icon-select {
position: absolute;
right: 0;
@@ -68,6 +67,9 @@
width: 28rpx;
height: 30rpx;
}
&:after{
content: none;
}
}
.active {
border: 1px solid #d33123;
@@ -75,9 +77,9 @@
background-color: rgba(211, 49, 35, 0.04);
}
.gray {
border: 1px solid #eee;
background-color: #eee;
color: #6e737d;
color: #c3c3c3;
border-color: #f6f6f6;
background-color: #f6f6f6;
}
}
}
@@ -14,13 +14,13 @@
<view class="item-box" v-for="item, index in state?.spec_list" :key="index">
<view class="title">{{ item.name }}</view>
<view class="tag-box">
<view v-for="itemChild, indexChild in item?.list" :key="indexChild"
:class="['tag', { 'active': itemChild.select }, { 'gray': !itemChild.show && state.skuLength == 1 }]"
@click="handleChooseSku(index, itemChild)">
<button v-for="itemChild, indexChild in item?.list" :key="indexChild"
:class="['tag',{'active':getSkuSelected(itemChild.name)},{'gray':getSkuDisabled(itemChild.name)}]"
@click="handleChooseSku(itemChild.name)" :disabled="getSkuDisabled(itemChild.name)">
{{ itemChild.name }}
<image v-if="itemChild.select" class="icon-select"
<image v-if="getSkuSelected(itemChild.name)" class="icon-select"
:src="handleStaticResources('/static/images/tag-select.png')"> </image>
</view>
</button>
</view>
</view>
</view>
@@ -32,13 +32,14 @@
<view class="btn-box">
<!-- <view v-if="props.isShowAddCartBtn" class="btn add-cart" @click="handleAddCart">加入购物车</view> -->
<view v-if="props.isShowAddCartBtn" class="core-button-confirm add-cart">
<coreshop-button :loading="props.addCartloading" class="core-button-confirm_" :radius="0" title="加入购物车"
:customStyle="addCartCustomStyle" @onClick="handleAddCart()"></coreshop-button>
<coreshop-button :loading="props.addCartloading" class="core-button-confirm_" :radius="0"
title="加入购物车" :customStyle="addCartCustomStyle" @onClick="handleAddCart()"></coreshop-button>
</view>
<!-- <view class="btn buy-now" @click="handleBuyNow">{{ props.btnBuyTitlt }}</view> -->
<view class="core-button-confirm">
<coreshop-button :loading="props.buyNowNowloading" class="core-button-confirm_" :radius="0"
:title="props.btnBuyTitlt" :customStyle="buyNowCustomStyle" @onClick="handleBuyNow()"></coreshop-button>
:title="props.btnBuyTitlt" :customStyle="buyNowCustomStyle"
@onClick="handleBuyNow()"></coreshop-button>
</view>
</view>
</view>
@@ -46,11 +47,22 @@
</template>
<script setup lang="ts">
import { watch, reactive, ref } from 'vue';
import { watch, reactive, ref, computed } from 'vue';
import type { GoodsSkuListType, GoodsSpecListType } from '@/core/models';
import { handleStaticResources, handleShowToast } from '@/core/utils';
import { deepClone } from '@/uni_modules/uv-ui-tools/libs/function/index.js';
type SpecWeight = Record<string, {
weight : number;
sort : number;
}>
type SelectedSpec = {
name : string;
weight : number;
sort : number;
}
const buyNowCustomStyle = {
'border': 'none',
'background-color': '#d33123',
@@ -74,7 +86,7 @@
isShowAddCartBtn : boolean,
btnBuyTitlt : string,
safeAreaInsetBottom : boolean;
buyNowCustomStyle : { [key : string] : any },
buyNowCustomStyle : any,
buyNowNowloading : boolean,
addCartloading : boolean
}>(), {
@@ -105,39 +117,53 @@
numberVal : number;
spec_list : Array<GoodsSpecListType>;
chooseSku : GoodsSkuListType;
skuLength : number;
specCombinationList : Array<GoodsSkuListType>;
specMap : SpecWeight;
selectedSpecs : Array<SelectedSpec>
}>({
numberVal: 1,
spec_list: [],
chooseSku: {},
skuLength: 1,
specCombinationList: [],
specMap: {},
selectedSpecs: [],
});
watch(() => props.goodsDetailData, (newVal : any) => {
if (newVal) {
/** 获取sku有几层 */
state.skuLength = newVal?.skuList?.spec_list.length;
/** 默认选择第一个sku */
state.chooseSku = newVal?.skuList?.sku_list.find((item : any) => item.stock != 0);
// 构造规格权重
let i = 0, j = 0;
state.spec_list = deepClone(newVal?.skuList?.spec_list.map((item : GoodsSpecListType) => {
item.list.forEach((itemChild : GoodsSpecListType, indexChild : number) => {
let findSku = newVal?.skuList?.sku_list.find((item : any) => item.sku_name_arr.includes(itemChild.name))
if (findSku && state.skuLength == 1) {
itemChild['show'] = true;
} else {
itemChild['show'] = false;
item.list.forEach((itemChild : GoodsSpecListType) => {
state.specMap[itemChild.name] = {
sort: i,
weight: 1 << j
}
j++;
})
i++;
return item;
}));
let findChooseSku = state.chooseSku.sku_name_arr.includes(itemChild.name);
if (findChooseSku) {
itemChild['select'] = true;
} else {
itemChild['select'] = false;
state.specCombinationList = (props.goodsDetailData?.skuList?.sku_list.filter((item : GoodsSkuListType) => item.stock > 0)).map((item : GoodsSkuListType) => {
return {
...item,
weight: item.sku_name_arr.reduce((prev, current) => prev + state.specMap[current]?.weight, 0),
}
})
return item;
}))
/** 获取数据返回的默认sku,如果有就用没有就找第一个库存不为0的sku */
let systemDefaultSku:Array<string> = (props.goodsDetailData?.product?.spesDesc?.split(',')).map((item:string)=> item.split(':')[1]);
let skuDefaultSku:GoodsSkuListType = state.specCombinationList.find((item:GoodsSkuListType)=> item.sku_name_arr.every((name:string)=> systemDefaultSku.includes(name)));
if(!systemDefaultSku || !skuDefaultSku){
state.specCombinationList[0].sku_name_arr.forEach((item:string)=>{
handleChooseSku(item);
});
return ;
}
skuDefaultSku.sku_name_arr.forEach((item:string)=>{
handleChooseSku(item);
});
}
}, {
deep: true
@@ -152,6 +178,39 @@
}
})
const getSkuDisabled = computed(() => {
return (item : string) => {
// 每次选中之后要判断其他规格是否能够被选中
const weights = state.selectedSpecs.reduce((prev, current) => prev + current.weight, 0);
// 所有是初始状态,则所有属性均可选,因此disabled为false.
if (weights === 0) return false;
const current = state.specMap[item];
const weight = current.weight;
// 假如选择该属性,这是选中后的“权重之和”
let sum = weight + weights;
// 找到是否存在同一维度的规格已被选中的规格
const existEle = state.selectedSpecs.find(o => o.sort === current.sort);
if (existEle) {
// 如果存在,则减去即前选中的规格权重,即替换为当前权重。
sum -= existEle.weight;
}
for (let i = 0; i < state.specCombinationList.length; i++) {
const sWeight = state.specCombinationList[i].weight!;
// 与“权重之和”按位与运算后与其进行比较,如果相等,说明该属性是可选的
if ((sWeight & sum) === sum) {
return false;
}
}
return state.selectedSpecs.findIndex(o => o.name === item) == -1;
};
})
const getSkuSelected = computed(() => {
return (item : string) => {
return state.selectedSpecs.findIndex(o => o.name === item) > -1;
};
})
/** 商品数量加减变化 */
const handleChangeNumberVal = (e : any) => {
state.numberVal = e.value;
@@ -163,33 +222,27 @@
}
/** sku选择 */
const handleChooseSku = (index : number, sku : GoodsSpecListType) => {
let chooseSku : GoodsSkuListType = {}; // 选中的sku
if (state.skuLength === 1) { /** 当sku只有一层的时候 */
chooseSku = props.goodsDetailData?.skuList?.sku_list.find((item : GoodsSkuListType) => item.sku_name_arr.includes(sku.name));
} else { /** 当sku多层的时候 */
let nameArr : Array<string> = [sku.name];
let noClickSpecList = state.spec_list.filter((item : any, idx : number) => idx !== index);
noClickSpecList.forEach((item : GoodsSpecListType) => {
item.list.forEach((itemChild : GoodsSpecListType) => {
if (itemChild.select) {
nameArr.push(itemChild.name)
const handleChooseSku = (name : string) => {
const spec = state.specMap[name];
const eleIndex = state.selectedSpecs.findIndex(item => item.name === name);
const sortIndex = state.selectedSpecs.findIndex(item => item.sort === spec.sort);
// 如果当前元素已经存在,则从列表中删除
if (eleIndex > -1) {
state.selectedSpecs.splice(eleIndex, 1);
return;
}
})
})
chooseSku = props.goodsDetailData.skuList?.sku_list.find((item : GoodsSkuListType) => item.sku_name_arr.every((itemChild : string) => nameArr.includes(itemChild)));
// 如果列表中的元素存在和当前元素的维度相同,则从列表中把那个元素删除,保证同一纬度只有一个元素。
if (sortIndex > -1) {
state.selectedSpecs.splice(sortIndex, 1);
}
state.selectedSpecs.push({ name: name, weight: spec.weight, sort: spec.sort });
if (!chooseSku || chooseSku.stock <= 0) {
handleShowToast('没有库存了,请选择其它规格');
return false;
}
state.spec_list[index].list.forEach((item : any) => item.select = false);
sku.select = !sku.select;
state.chooseSku = chooseSku;
/** 判断sku是否选择完毕 */
if(state.selectedSpecs.length === state.spec_list.length){
let skuName:Array<string> = state.selectedSpecs.map(item => item.name);
state.chooseSku = state.specCombinationList.find((item:GoodsSkuListType)=> item.sku_name_arr.every((name:string)=> skuName.includes(name)));
emits('handleChangeGoodSku', state.chooseSku);
}
};
/** 加入购物车 */
@@ -217,8 +270,10 @@
/** 判断库存 */
const handleHaveStock = () => {
if (state.chooseSku.stock === 0) {
handleShowToast('没有库存了,请选择其它规格');
let skuName:Array<string> = state.selectedSpecs.map(item => item.name) || [];
let sku = state.spec_list.find((item:any)=> !item.list.some((itemChild:any)=> skuName.includes(itemChild.name)));
if (sku) {
handleShowToast(`请选择 ${sku.name} 规格`);
return false;
}
return true;