Skip to content

Sku 商品规格选择

介绍

常用于进行商品选择

基础用法

html
<template>
  <nut-cell :title="`基础用法`" desc="" @click="base = true"></nut-cell>
  <nut-sku
    v-model:visible="base"
    :sku="sku"
    :goods="goods"
    @selectSku="selectSku"
    @clickBtnOperate="clickBtnOperate"
    @close="close"
  ></nut-sku>
</template>
<script lang="ts">
import { ref,reactive,onMounted,toRefs} from 'vue';
export default {
  setup() {
      const base = ref(false);
      const data = reactive({
        sku: [],
        goods: {}
      });

      onMounted(() => {
        uni.request({
          url: 'https://storage.360buyimg.com/nutui/3x/data.js', //仅为示例,并非真实的接口地址
          success: function (res) {
            console.log(res.data)
            const { Sku, Goods, imagePathMap } = res.data;
              data.sku = Sku;
              data.goods = Goods;
          }
        })
      });
      // 切换规格类目
      const selectSku = (ss: string) => {
        const { sku, skuIndex, parentSku, parentIndex } = ss;
        if (sku.disable) return false;
        data.sku[parentIndex].list.forEach((s) => {
          s.active = s.id == sku.id;
        });
        data.goods = {
          skuId: sku.id,
          price: '4599.00',
          imagePath:
            '//img14.360buyimg.com/n4/jfs/t1/215845/12/3788/221990/618a5c4dEc71cb4c7/7bd6eb8d17830991.jpg'
        };
      };
      // 底部操作按钮触发
      const clickBtnOperate = (op:string)=>{
        console.log('点击了操作按钮',op)
      }
      // 关闭商品规格弹框
      const close = ()=>{}
      return { base, selectSku, clickBtnOperate,close, ...toRefs(data) };
  }
}
</script>

不可售

html
<template>
  <nut-cell title="不可售" desc="" @click="notSell = true"></nut-cell>
  <nut-sku
    v-model:visible="notSell"
    :sku="sku"
    :goods="goods"
    :btnExtraText="btnExtraText"
    @changeStepper="changeStepper"
    @selectSku="selectSku"
    :btnOptions="['buy', 'cart']"
  >
    <template #skuOperate>
      <div class="sku-operate-box">
        <nut-button class="sku-operate-box-dis" type="warning">查看相似商品</nut-button>
        <nut-button class="sku-operate-box-dis" type="info">到货通知</nut-button>
      </div>
    </template>
  </nut-sku>
</template>
<script lang="ts">
import { ref,reactive,onMounted,toRefs} from 'vue';
export default {
setup() {
    const notSell = ref(false);
    const data = reactive({
      sku: [],
      goods: {}
    });

    const btnExtraText = ref('抱歉,此商品在所选区域暂无存货');

    onMounted(() => {
        uni.request({
          url: 'https://storage.360buyimg.com/nutui/3x/data.js', //仅为示例,并非真实的接口地址
          success: function (res) {
            console.log(res.data)
            const { Sku, Goods, imagePathMap } = res.data;
              data.sku = Sku;
              data.goods = Goods;
          }
        })
    });

    // inputNumber 更改
    const changeStepper = (count: number) => {
      console.log('购买数量', count);
    };

    // 切换规格类目
    const selectSku = (ss: string) => {
      const { sku, skuIndex, parentSku, parentIndex } = ss;
      if (sku.disable) return false;
      data.sku[parentIndex].list.forEach((s) => {
        s.active = s.id == sku.id;
      });
      data.goods = {
        skuId: sku.id,
        price: '4599.00',
        imagePath:
          '//img14.360buyimg.com/n4/jfs/t1/216079/14/3895/201095/618a5c0cEe0b9e2ba/cf5b98fb6128a09e.jpg'
      };
    };
    // 底部操作按钮触发
    const clickBtnOperate = (op:string)=>{
      console.log('点击了操作按钮',op)
    }
    return { notSell, changeStepper,selectSku,btnExtraText,...toRefs(data) };
  }
}
</script>
<style>
.sku-operate-box {
  width: 100%;
  display: flex;
  padding: 8px 10px;
  box-sizing: border-box;
}
.sku-operate-box-dis{
    flex:1
}
.sku-operate-box-dis:first-child{
  margin-right: 18px;
}
</style>

自定义步进器

可以按照需求配置数字输入框的最大值、最小值、文案等

html
<template>
  <nut-cell title="自定义计步器" desc="" @click="customStepper = true"></nut-cell>
  <nut-sku
    v-model:visible="customStepper"
    :sku="sku"
    :goods="goods"
    :stepperMax="7"
    :stepperMin="2"
    :stepperExtraText="stepperExtraText"
    @changeStepper="changeStepper"
    @overLimit="overLimit"
    :btnOptions="['buy', 'cart']"
    @selectSku="selectSku"
    @clickBtnOperate="clickBtnOperate"
  ></nut-sku>
</template>
<script lang="ts">
import { ref,reactive,onMounted,toRefs} from 'vue';
export default {
setup() {
    const customStepper = ref(false);
    const data = reactive({
      sku: [],
      goods: {}
    });

    onMounted(() => {
        uni.request({
          url: 'https://storage.360buyimg.com/nutui/3x/data.js', //仅为示例,并非真实的接口地址
          success: function (res) {
            console.log(res.data)
            const { Sku, Goods, imagePathMap } = res.data;
              data.sku = Sku;
              data.goods = Goods;
          }
        })
    });

    const stepperExtraText = () => {
      return `<div style="width:100%;text-align:right;color:#F00">2 件起售</div>`
    };
    // inputNumber 更改
    const changeStepper = (count: number) => {
      console.log('购买数量', count);
    };

    // inputNumber 极限值
    const overLimit = (val: any) => {
      if (val.action == 'reduce') {
        console.log(`至少买${val.value}件哦`);
      } else {
        console.log(`最多买${val.value}件哦`);
      }
    };
    // 切换规格类目
    const selectSku = (ss: string) => {
      const { sku, skuIndex, parentSku, parentIndex } = ss;
      if (sku.disable) return false;
      data.sku[parentIndex].list.forEach((s) => {
        s.active = s.id == sku.id;
      });
      data.goods = {
        skuId: sku.id,
        price: '4599.00',
        imagePath:
          '//img14.360buyimg.com/n4/jfs/t1/215845/12/3788/221990/618a5c4dEc71cb4c7/7bd6eb8d17830991.jpg'
      };
    };
    // 底部操作按钮触发
    const clickBtnOperate = (op:string)=>{
      console.log('点击了操作按钮',op)
    }
    return { customStepper, overLimit, changeStepper,selectSku, clickBtnOperate,stepperExtraText,...toRefs(data) };
}
}
</script>

自定义插槽

Sku 组件默认划分为若干区域,这些区域都定义成了插槽,可以按照需求进行替换。

html
<template>
  <nut-cell title="通过插槽自定义设置" desc="" @click="customBySlot = true"></nut-cell>
  <nut-sku
      v-model:visible="customBySlot"
      :sku="sku"
      :goods="goods"
      :btnOptions="['buy', 'cart']"
      @selectSku="selectSku"
      @clickBtnOperate="clickBtnOperate"
  >
      <!-- 商品展示区,价格区域 -->
      <template #skuHeaderPrice>
          <div>
              <nut-price :price="goods.price" :needSymbol="true" :thousands="false"> </nut-price>
              <span class="tag"></span>
          </div>
      </template>
      <!-- 商品展示区,编号区域 -->
      <template #skuHeaderExtra>
          <span class="nut-sku-header-right-extra">重量:0.1kg  编号:{{skuId}}  </span>
      </template>
      <!-- sku 展示区上方与商品信息展示区下方区域,无默认展示内容 -->
      <template #skuSelectTop>
          <div class="address">
              <nut-cell style="box-shadow:none;padding:13px 0" title="送至" :desc="addressDesc" @click="showAddressPopup=true"></nut-cell>
          </div>
      </template>
      <!-- 底部按钮操作区 -->
      <template #skuOperate>
          <div class="sku-operate-box">
          <nut-button class="sku-operate-item" shape="square" type="warning">加入购物车</nut-button>
          <nut-button class="sku-operate-item" shape="square" type="primary">立即购买</nut-button>
          </div>
      </template>
  </nut-sku>

  <nut-address
    v-model:visible="showAddressPopup"
    type="exist"
    :exist-address="existAddress"
    :is-show-custom-address="false"
    @selected="selectedAddress"
    exist-address-title="配送至"
  ></nut-address>

</template>
<script lang="ts">
import { ref,reactive,onMounted,toRefs} from 'vue';
export default {
setup() {
    const customBySlot = ref(false);
    const showAddressPopup = ref(false);
    const data = reactive({
      sku: [],
      goods: {}
    });

    const addressDesc = ref('(配送地会影响库存,请先确认)');
    const existAddress = ref([
      {
        id: 1,
        addressDetail: 'th ',
        cityName: '石景山区',
        countyName: '城区',
        provinceName: '北京',
        selectedAddress: true,
        townName: ''
      },
      {
        id: 2,
        addressDetail: '12 ',
        cityName: '电饭锅',
        countyName: '扶绥县',
        provinceName: '北京',
        selectedAddress: false,
        townName: ''
      },
      {
        id: 3,
        addressDetail: '发大水比 ',
        cityName: '放到',
        countyName: '广宁街道',
        provinceName: '钓鱼岛全区',
        selectedAddress: false,
        townName: ''
      },
      {
        id: 4,
        addressDetail: '还是想吧百度吧 ',
        cityName: '研发',
        countyName: '八里庄街道',
        provinceName: '北京',
        selectedAddress: false,
        townName: ''
      }
    ]);

    onMounted(() => {
        uni.request({
          url: 'https://storage.360buyimg.com/nutui/3x/data.js', //仅为示例,并非真实的接口地址
          success: function (res) {
            console.log(res.data)
            const { Sku, Goods, imagePathMap } = res.data;
              data.sku = Sku;
              data.goods = Goods;
          }
        })
    });

    // 切换规格类目
    const selectSku = (ss: string) => {
      const { sku, skuIndex, parentSku, parentIndex } = ss;
      if (sku.disable) return false;
      data.sku[parentIndex].list.forEach((s) => {
        s.active = s.id == sku.id;
      });
      data.goods = {
        skuId: sku.id,
        price: '6002.10',
        imagePath:
          '//img14.360buyimg.com/n4/jfs/t1/215845/12/3788/221990/618a5c4dEc71cb4c7/7bd6eb8d17830991.jpg'
      };
    };
    const selectedAddress = (prevExistAdd: any, nowExistAdd: any) => {
      const { provinceName, countyName, cityName } = nowExistAdd;
      addressDesc.value = `${provinceName}${countyName}${cityName}`;
    };
    // 底部操作按钮触发
    const clickBtnOperate = (op:string)=>{
      console.log('点击了操作按钮',op)
    }
    return { customBySlot, selectSku, clickBtnOperate,existAddress,addressDesc,selectedAddress,...toRefs(data) };
}
}
</script>

<style>
.sku-operate-box {
  width: 100%;
  display: flex;
  padding: 8px 10px;
  box-sizing: border-box;
}
.sku-operate-item {
    flex:1
}
.sku-operate-item:first-child {
      border-top-left-radius: 20px;
      border-bottom-left-radius: 20px;
    }
.sku-operate-item:last-child {
      border-top-right-radius: 20px;
      border-bottom-right-radius: 20px;
    }
</style>

规格改变后重置商品数量

html
<template>
  <nut-cell :title="`重置商品数量`" desc="" @click="resetVisible = true"></nut-cell>
  <nut-sku
    v-model:visible="resetVisible"
    ref="skuRef"
    :sku="sku"
    :goods="goods"
    @selectSku="selectSku"
    @close="close"
  ></nut-sku>
</template>
<script lang="ts">
import { ref, reactive, onMounted, toRefs} from 'vue';

export default {
  setup() {
    const resetVisible = ref(false);
    const skuRef = ref()
    const data = reactive({
      sku: [],
      goods: {}
    });

    onMounted(() => {
      uni.request({
        url: 'https://storage.360buyimg.com/nutui/3x/data.js', //仅为示例,并非真实的接口地址
        success: function (res) {
          console.log(res.data)
          const { Sku, Goods, imagePathMap } = res.data;
            data.sku = Sku;
            data.goods = Goods;
        }
      })
    });

    // 切换规格类目
    const selectSku = (ss: string) => {
      const { sku, skuIndex, parentSku, parentIndex } = ss;
      if (sku.disable) return false;
      data.sku[parentIndex].list.forEach((s) => {
        s.active = s.id == sku.id;
      });
      data.goods = {
        skuId: sku.id,
        price: '4599.00',
        imagePath:
          '//img14.360buyimg.com/n4/jfs/t1/215845/12/3788/221990/618a5c4dEc71cb4c7/7bd6eb8d17830991.jpg'
      };

      // 在此处重置商品数量
      skuRef.value.resetCount()
    };

    // 关闭商品规格弹框
    const close = ()=>{}

    return { resetVisible, skuRef, selectSku, close, ...toRefs(data) };
  }
}
</script>

API

Props

参数说明类型默认值
v-model:visible是否显示商品规格弹框booleanfalse
sku商品 sku 数据Array[]
goods商品信息object-
stepper-max设置 inputNumber 最大值string | number99999
stepper-min设置 inputNumber 最小值string | number1
btn-options底部按钮设置。[confirm, buy, cart] 分别对应确定、立即购买、加入购物车Array[confirm]
btn-extra-text按钮上部添加文案,默认为空,有值时显示string-
stepper-title数量选择组件左侧文案string购买数量
stepper-extra-textInputNumber 与标题之间的文案Function | booleanfalse
buy-text立即购买按钮文案string立即购买
add-cart-text加入购物车按钮文案string加入购物车
confirm-text确定按钮文案string确定

Events

事件名说明回调参数
select-sku切换规格类目时触发{sku,skuIndex,parentSku,parentIndex}
addInputNumber 点击增加按钮时触发value
reduceInputNumber 点击减少按钮时触发value
overLimitInputNumber 点击不可用的按钮时触发value
change-stepper购买变化时触发value
click-btn-operate点击底部按钮时触发{type:'confirm',value:'inputNumber value'}
click-close-icon点击左上角关闭 icon 时触发-
click-overlay点击遮罩时触发-
close关闭弹层时触发-

Slots

Sku 组件默认划分为若干区域,这些区域都定义成了插槽,可以按照需求进行替换。

名称说明
skuHeader商品信息展示区,包含商品图片、价格、编号
skuHeaderPrice商品信息展示区,价格区域展示
skuHeaderExtra商品信息展示区,编号区域展示
skuSelectTopSku 展示区上方与商品信息展示区下方区域,无默认展示内容
skuSelectSku 展示区
skuStepper数量选择区
skuStepperBottom数量选择区下方区域
skuOperate底部按钮操作区域

Methods

通过 ref 可以获取到 Sku 实例并调用实例方法

方法名说明参数返回值
resetCount重置商品数量-

goods 对象结构

javascript
goods:{
    skuId:'', // 商品信息展示区,商品编号
    price: "0", // 商品信息展示区,商品价格
    imagePath: "", // 商品信息展示区,商品图
}

sku 数组结构

sku 数组中,每一个数组索引代表一个规格类目。其中,list 代表该规格类目下的类目值。每个类目值对象包括:nameidactive(是否选中)、disable(是否可选)

javascript
sku : [{
  id: 1,
  name: '颜色',
  list: [{
    name: '亮黑色',
    id: 100016015112,
    active: true,
    disable: false
  }, {
    name: '釉白色',
    id: 100016015142,
    active: false,
    disable: false
  }, {
    name: '秘银色',
    id: 100016015078,
    active: false,
    disable: false
  }, {
    name: '夏日胡杨',
    id: 100009064831,
    active: false,
    disable: false
  }, {
    name: '秋日胡杨',
    id: 100009064830,
    active: false,
    disable: false
  }]
}, {
  id: 2,
  name: '版本',
  list: [{
    name: '8GB+128GB',
    id: 100016015102,
    active: true,
    disable: false
  }, {
    name: '8GB+256GB',
    id: 100016015122,
    active: false,
    disable: false
  }]
}, {
  id: 3,
  name: '版本',
  list: [{
    name: '4G(有充版)',
    id: 100016015103,
    active: true,
    disable: false
  }, {
    name: '5G(有充版)',
    id: 100016015123,
    active: false,
    disable: false
  }, {
    name: '5G(无充版)',
    id: 100016015104,
    active: true,
    disable: true
  }, {
    name: '5G(无充)质保换新版',
    id: 100016015125,
    active: false,
    disable: false
  }]
}]

主题定制

样式变量

组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 ConfigProvider 组件

名称默认值
--nut-sku-item-border1px solid var(--nut-primary-color)
--nut-sku-item-disable-lineline-through
--nut-sku-opetate-bg-defaultlinear-gradient(90deg, var(--nut-primary-color), var(--nut-primary-color-end) 100%)
--nut-sku-item-active-bgvar(--nut-primary-color)
--nut-sku-opetate-bg-buylinear-gradient(135deg,rgba(255, 186, 13, 1) 0%,rgba(255, 195, 13, 1) 69%,rgba(255, 207, 13, 1) 100%)
--nut-sku-spec-height30px
--nut-sku-spec-line-heightvar(--nut-sku-spec-height)
--nut-sku-spec-font-size11px
--nut-sku-spec-backgroundrgba(242, 242, 242, 1)
--nut-sku-spec-colorvar(--nut-black)
--nut-sku-spec-margin-right12px
--nut-sku-spec-padding0 18px
--nut-sku-spec-title-font-weightbold
--nut-sku-spec-title-font-size13px
--nut-sku-spec-title-colorvar(--nut-black)
--nut-sku-spec-title-margin-bottom18px
--nut-sku-operate-btn-height54px
--nut-sku-operate-btn-border-top0
--nut-sku-operate-btn-item-height40px
--nut-sku-operate-btn-item-line-heightvar(--nut-sku-operate-btn-item-height)
--nut-sku-operate-btn-item-font-size15px
--nut-sku-operate-btn-item-font-weightnormal
--nut-sku-product-img-width100px
--nut-sku-product-img-heightvar(--nut-sku-product-img-width)
--nut-sku-product-img-border-radius0

MIT Licensed