@@ -1,230 +1,285 @@
< template >
< uv-popup ref = "skuPopup" mode = "bottom" :closeable = "true" :safeAreaInsetBottom = "props.safeAreaInsetBottom"
@change ="handleChangePopup" >
< view class = "sku-content" >
< view class = "godds-box" >
< image class = "img" :src = "state?.chooseSku?.image" > < / image >
< view class = "price-box" >
< view class = "price" > ¥ { { state . chooseSku ? . price } } < / view >
< view class = "stock" > 库存 : { { state . chooseSku ? . stock } } < / view >
< view class = "specs" > 规格 : { { state . chooseSku ? . sku _name _arr ? . join ( ',' ) } } < / view >
< / view >
< / view >
< view class = "specs-box" >
< 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)" >
{ { itemChild. name }}
< image v-if = "itemChild.select " class="icon-select"
:src = "handleStaticResources('/static/images/tag-select.png')" > < / image >
< / view >
< / view >
< / view >
< / view >
< view class = "number-box" >
< view class = "tit" > 数量 : < / view >
< uv-number-box v-model = "state.numberVal" :disabledInput="true"
@change ="handleChangeNumberVal" > < / uv -number -box >
< / view >
< 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 >
< / 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 >
< /view >
< / view >
< / view >
< / uv-popup >
< uv-popup ref = "skuPopup" mode = "bottom" :closeable = "true" :safeAreaInsetBottom = "props.safeAreaInsetBottom"
@change ="handleChangePopup" >
< view class = "sku-content" >
< view class = "godds-box" >
< image class = "img" :src = "state?.chooseSku?.image" > < / image >
< view class = "price-box" >
< view class = "price" > ¥ { { state . chooseSku ? . price } } < / view >
< view class = "stock" > 库存 : { { state . chooseSku ? . stock } } < / view >
< view class = "specs" > 规格 : { { state . chooseSku ? . sku _name _arr ? . join ( ',' ) } } < / view >
< / view >
< / view >
< view class = "specs-box" >
< view class = "item-box" v-for = "item, index in state?.spec_list" :key="index" >
< view class = "title" > { { item . name } } < / view >
< view class = "tag-box" >
< 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 = "getSkuSelected( itemChild.name) " class="icon-select"
:src = "handleStaticResources('/static/images/tag-select.png')" > < / image >
< / button >
< / view >
< / view >
< / view >
< view class = "number-box" >
< view class = "tit" > 数量 : < / view >
< uv-number-box v-model = "state.numberVal" :disabledInput="true"
@change ="handleChangeNumberVal" > < / uv -number -box >
< / view >
< 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 >
< / 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 >
< / view >
< / view >
< / view >
< / uv-popup >
< / template >
< script setup lang = "ts" >
import { watch , reactive , ref } 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' ;
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' ;
const buyNowCustomStyle = {
'border' : 'none' ,
'background-color' : '#d33123' ,
'flex' : 1 ,
'padding' : '32rpx 0' ,
'font-size' : '27rpx' ,
'text-align' : 'center' ,
'color' : '#fff' ,
'height' : '90rpx'
}
type SpecWeight = Record < string , {
weight : number ;
sort : number ;
} >
const addCartCustomStyle = {
... buyNowCustomStyle ,
'background-color' : '#000' ,
}
type SelectedSpec = {
name : string ;
weight : number ;
sort : number ;
}
const buyNowCustomStyle = {
'border' : 'none' ,
'background-color' : '#d33123' ,
'flex' : 1 ,
'padding' : '32rpx 0' ,
'font-size' : '27rpx' ,
'text-align' : 'center' ,
'color' : '#fff' ,
'height' : '90rpx'
}
const addCartCustomStyle = {
... buyNowCustomStyle ,
'background-color' : '#000' ,
}
const props = withDefaults ( defineProps < {
showSku : boolean ,
goodsDetailData : any ,
isShowAddCartBtn : boolean ,
btnBuyTitlt : string ,
safeAreaInsetBottom : boolean ;
buyNowCustomStyle : { [ key : string ] : any } ,
buyNowNowloading : boolean ,
addCartloading : boolean
} > ( ) , {
showSku : false ,
goodsDetailData : { } ,
isShowAddCartBtn : true ,
btnBuyTitlt : '立即购买' ,
safeAreaInsetBottom : true ,
buyNowCustomStyle : {
'border' : 'none' ,
'background-color' : '#d33123' ,
'flex' : 1 ,
'padding' : '25rpx 0' ,
'font-size' : '27rpx' ,
'text-align' : 'center' ,
'color' : '#fff' ,
'height' : '90rpx'
} ,
buyNowNowloading : false ,
addCartloading : false
} ) ;
const props = withDefaults ( defineProps < {
showSku : boolean ,
goodsDetailData : any ,
isShowAddCartBtn : boolean ,
btnBuyTitlt : string ,
safeAreaInsetBottom : boolean ;
buyNowCustomStyle : any ,
buyNowNowloading : boolean ,
addCartloading : boolean
} > ( ) , {
showSku : false ,
goodsDetailData : { } ,
isShowAddCartBtn : true ,
btnBuyTitlt : '立即购买' ,
safeAreaInsetBottom : true ,
buyNowCustomStyle : {
'border' : 'none' ,
'background-color' : '#d33123' ,
'flex' : 1 ,
'padding' : '25rpx 0' ,
'font-size' : '27rpx' ,
'text-align' : 'center' ,
'color' : '#fff' ,
'height' : '90rpx'
} ,
buyNowNowloading : false ,
addCartloading : false
} ) ;
const emits = defineEmits ( [ 'handleChangePopup' , 'handleAddCart' , 'handleBuyNow' , 'handleChangeGoodSku' ] ) ;
const emits = defineEmits ( [ 'handleChangePopup' , 'handleAddCart' , 'handleBuyNow' , 'handleChangeGoodSku' ] ) ;
const skuPopup = ref ( ) ;
const skuPopup = ref ( ) ;
const state = reactive < {
numberVal : number ;
spec _list : Array < GoodsSpecListType > ;
chooseSku : GoodsSkuListType ;
skuLength : number ;
} > ( {
numberVal : 1 ,
spec _list : [ ] ,
chooseSku : { } ,
skuLength : 1 ,
} ) ;
const state = reactive < {
numberVal : number ;
spec _list : Array < GoodsSpecListType > ;
chooseSku : GoodsSkuListType ;
specCombinationList : Array < GoodsSkuListType > ;
specMap : SpecWeight ;
selectedSpecs : Array < SelectedSpec >
} > ( {
numberVal : 1 ,
spec _list : [ ] ,
chooseSku : { } ,
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 ) ;
watch ( ( ) => props . goodsDetailData , ( newVal : any ) => {
if ( newVal ) {
// 构造规格权重
let i = 0 , j = 0 ;
state . spec _list = deepClone ( newVal ? . skuList ? . spec _list . map ( ( item : GoodsSpecListType ) => {
item . list . forEach ( ( itemChild : GoodsSpecListType ) => {
state . specMap [ itemChild . name ] = {
sort : i ,
weight : 1 << j
}
j ++ ;
} )
i ++ ;
return item ;
} ) ) ;
state . spec_list = deepClone ( newVal ? . skuList ? . spec _list . map ( ( item : GoodsSpec ListType ) => {
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 ;
}
state . specCombinationList = ( props . goodsDetailData ? . skuList ? . sku _list . filter ( ( item : GoodsSkuListType ) => item . stock > 0 ) ) . map ( ( item : GoodsSku ListType ) => {
return {
... item ,
weight : item . sku _name _arr . reduce ( ( prev , current ) => prev + state . specMap [ current ] ? . weight , 0 ) ,
}
} )
/** 获取数据返回的默认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
} )
let findChooseSku = state . c hoose Sku. sku _name _arr . includes ( itemChild . name ) ;
if ( findChooseSku ) {
itemChild [ 'select' ] = true ;
} else {
itemChild [ 'select' ] = false ;
}
} )
return item ;
} ) )
}
} , {
deep : true
} )
watch ( ( ) => props . s how Sku, ( newVal : boolean ) => {
if ( newVal ) {
skuPopup . value . open ( ) ;
} else {
skuPopup . value . close ( ) ;
state . numberVal = 1 ;
}
} )
watch ( ( ) => props . showSku , ( newVal : boolean ) => {
if ( newVal ) {
skuPopup . value . open ( ) ;
} else {
skuPopup . value . close ( ) ;
state . numberVal = 1 ;
}
} )
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 handleChangeNumberVal = ( e : any ) => {
state . numberVal = e . value ;
}
const getSkuSelected = computed ( ( ) => {
return ( item : string ) => {
return state . selectedSpecs . findIndex ( o => o . name === item ) > - 1 ;
} ;
} )
/** sku弹框显示或者隐藏 */
const handleChangePopup = ( e : any ) => {
emits ( 'handleChangePopup' , e . show )
}
/** 商品数量加减变化 */
const handleChangeNumberVal = ( e : any ) => {
state . numberVal = e . value ;
}
/** sku选择 */
const handleChooseSku = ( index : number , sku : GoodsSpecListType ) => {
let chooseSku : GoodsSkuListType = { } ; // 选中的sku
/** sku弹框显示或者隐藏 */
const handleChangePopup = ( e : any ) => {
emits ( 'handleChangePopup' , e . show )
}
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 )
}
} )
} )
chooseSku = props . goodsDetailData . skuList ? . sku _list . find ( ( item : GoodsSkuListType ) => item . sku _name _arr . every ( ( itemChild : string ) => nameArr . inclu des ( itemChild ) ) ) ;
}
/** sku选择 */
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 ;
}
// 如果列表中的元素存在和当前元素的维度相同,则从列表中把那个元素删除,保证同一纬度只有一个元素。
if ( sortIndex > - 1 ) {
state . selectedSpecs . splice ( sortIn dex , 1 ) ;
}
state . selectedSpecs . push ( { name : name , weight : spec . weight , sort : spec . sort } ) ;
/** 判断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 ) ;
}
} ;
if ( ! chooseSku || chooseSku . stock <= 0 ) {
handleShowToast ( '没有库存了,请选择其它规格' ) ;
return false ;
}
/** 加入购物车 */
const handleAddCart = ( ) => {
if ( ! handleHaveStock ( ) ) {
return ;
}
emits ( 'handleAddCart' , {
productId : state . chooseSku . _id ,
nums : state . numberVal ,
} ) ;
state . spec _list [ index ] . list . forEach ( ( item : any ) => item . select = false ) ;
sku . select = ! sku . select ;
state . chooseSku = chooseSku ;
emits ( 'handleChangeGoodSku' , state . chooseSku ) ;
} ;
}
/** 加入购物车 */
const handleAddCart = ( ) => {
if ( ! handleHaveStock ( ) ) {
return ;
}
emits ( 'handleAddCart ' , {
productId : state . chooseSku . _id ,
nums : state . numberVal ,
} ) ;
/** 立即购买 */
const handleBuyNow = ( ) => {
if ( ! handleHaveStock ( ) ) {
return ;
}
emits ( 'handleBuyNow ' , {
productId : state . chooseSku . _id ,
nums : state . numberVal ,
} ) ;
}
}
/** 立即购买 */
const handleBuyNow = ( ) => {
if ( ! handleHaveStock ( ) ) {
return ;
}
emits ( 'handleBuyNow' , {
productId : state . chooseSku . _id ,
nums : state . numberVal ,
} ) ;
}
/** 判断库存 */
const handleHaveStock = ( ) => {
if ( state . chooseSku . stock === 0 ) {
handleShowToast ( '没有库存了,请选择其它规格' ) ;
return false ;
}
return true ;
}
/** 判断库存 */
const handleHaveStock = ( ) => {
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 ;
}
< / script >
< style lang = "scss" scoped >
@ import './goods-detail-sku.scss' ;
@ import './goods-detail-sku.scss' ;
< / style >