This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
@import '../price/index';
@import '../inputnumber/index';
@import '../popup/index';
.nut-theme-dark {
.nut-sku {
background: $dark-background;
}
}
.nut-sku {
display: flex;
flex-direction: column;
height: 100%;
padding: 0;
background: $white;
&-content {
flex: 1;
margin-top: 24px;
overflow: hidden auto;
&-wrapper {
box-sizing: border-box;
width: 100%;
padding: 0 18px;
}
&::-webkit-scrollbar {
display: none;
}
}
}

View File

@@ -0,0 +1 @@
export type * from './sku'

View File

@@ -0,0 +1,79 @@
import type { ExtractPropTypes } from 'vue'
import { CLOSE_EVENT, UPDATE_VISIBLE_EVENT } from '../_constants'
import { commonProps, isBoolean, isNumber, isString, makeArrayProp, makeNumericProp } from '../_utils'
export const skuProps = {
...commonProps,
/**
* @description 是否显示商品规格弹框
*/
visible: Boolean,
/**
* @description 商品 sku 数据
*/
sku: makeArrayProp<any>([]),
/**
* @description 商品信息
*/
goods: Object,
/**
* @description 设置 inputNumber 最大值
*/
stepperMax: makeNumericProp(99999),
/**
* @description 设置 inputNumber 最小值
*/
stepperMin: makeNumericProp(1),
/**
* 底部按钮设置。[`confirm`, `buy`, `cart`] 分别对应确定、立即购买、加入购物车
* @description 底部按钮设置
*/
btnOptions: makeArrayProp<string>(['confirm']),
/**
* @description 按钮上部添加文案,默认为空,有值时显示
*/
btnExtraText: String,
/**
* @description 数量选择组件左侧文案
*/
stepperTitle: String,
/**
* @description InputNumber 与标题之间的文案
*/
stepperExtraText: {
type: [Function, Boolean],
default: false,
},
/**
* @description 立即购买按钮文案
*/
buyText: String,
/**
* @description 加入购物车按钮文案
*/
addCartText: String,
/**
* @description 确定按钮文案
*/
confirmText: String,
}
export type SkuProps = ExtractPropTypes<typeof skuProps>
export const skuEmits = {
[UPDATE_VISIBLE_EVENT]: (val: boolean) => isBoolean(val),
selectSku: (val: any) => val instanceof Object,
changeStepper: (val: number) => isNumber(val),
clickBtnOperate: (val: {
type: string
value: string | number
}) => val instanceof Object,
clickCloseIcon: () => true,
clickOverlay: () => true,
[CLOSE_EVENT]: () => true,
reduce: (val: number | object) => isNumber(val) || isString(val) || val instanceof Object,
add: (val: number | object) => isNumber(val) || isString(val) || val instanceof Object,
overLimit: (val: any) => val instanceof Object,
}
export type SkuEmits = typeof skuEmits

View File

@@ -0,0 +1,188 @@
<script setup lang="ts">
import { computed, defineComponent, ref, useSlots, watch } from 'vue'
import { CLOSE_EVENT, PREFIX, UPDATE_VISIBLE_EVENT } from '../_constants'
import { getMainClass } from '../_utils'
import { useTranslate } from '../../locale'
import NutPopup from '../popup/popup.vue'
import SkuHeader from '../skuheader/skuheader.vue'
import SkuOperate from '../skuoperate/skuoperate.vue'
import SkuSelect from '../skuselect/skuselect.vue'
import SkuStepper from '../skustepper/skustepper.vue'
import { skuEmits, skuProps } from './sku'
const props = defineProps(skuProps)
const emit = defineEmits(skuEmits)
defineExpose({
resetCount,
})
const slots = useSlots()
const showPopup = ref(props.visible)
const skuStepperRef = ref()
const goodsCount = ref(props.stepperMin)
const classes = computed(() => {
return getMainClass(props, componentName)
})
watch(
() => props.visible,
(value) => {
showPopup.value = value
},
)
watch(
() => showPopup.value,
(value) => {
if (value === false)
close()
},
)
// 商品规格 sku 选择
function selectSku(skus: any) {
emit('selectSku', skus)
}
// 数量计步器变化
function changeStepper(value: number) {
goodsCount.value = value
emit('changeStepper', value)
}
// 修改购买数量 add 加 reduce 减
function add(value: number) {
emit('add', value)
}
function reduce(value: number) {
emit('reduce', value)
}
// 触发极限值
function stepperOverLimit(count: any) {
emit('overLimit', count)
}
// 点击 button 操作
function clickBtnOperate(btn: string) {
emit('clickBtnOperate', {
type: btn,
value: goodsCount.value,
})
}
// 关闭
function closePopup(type: string) {
if (type === 'icon')
emit('clickCloseIcon')
if (type === 'overlay')
emit('clickOverlay')
if (type === 'close')
emit(CLOSE_EVENT)
showPopup.value = false
}
function close() {
emit(UPDATE_VISIBLE_EVENT, false)
}
function resetCount() {
skuStepperRef.value.reset()
}
const getSlots = (name: string) => slots[name]
const hasSkuOperateSlot = getSlots('skuOperate') != null
</script>
<script lang="ts">
const componentName = `${PREFIX}-sku`
const { translate } = useTranslate(componentName)
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<NutPopup
v-model:visible="showPopup"
safe-area-inset-bottom
position="bottom"
closeable
round
custom-style="height: 75%"
@click-close-icon="closePopup('icon')"
@click-overlay="closePopup('overlay')"
@close="closePopup('close')"
>
<view :class="classes" :style="customStyle">
<slot name="skuHeader">
<SkuHeader :goods="goods">
<template #skuHeaderPrice>
<slot name="skuHeaderPrice" />
</template>
<template #skuHeaderExtra>
<slot name="skuHeaderExtra" />
</template>
</SkuHeader>
</slot>
<scroll-view scroll-y class="nut-sku-content">
<view class="nut-sku-content-wrapper">
<slot name="skuSelectTop" />
<slot name="skuSelect" />
<SkuSelect v-if="!getSlots('sku-select')" :sku="sku" @select-sku="selectSku" />
<slot name="skuStepper">
<SkuStepper
ref="skuStepperRef"
:goods="goods"
:stepper-title="stepperTitle || translate('buyNumber')"
:stepper-max="stepperMax"
:stepper-min="stepperMin"
:stepper-extra-text="stepperExtraText"
@add="add"
@reduce="reduce"
@change-stepper="changeStepper"
@over-limit="stepperOverLimit"
/>
</slot>
<slot name="skuStepperBottom" />
</view>
</scroll-view>
<SkuOperate
:btn-extra-text="btnExtraText"
:btn-options="btnOptions"
:buy-text="buyText || translate('buyNow')"
:add-cart-text="addCartText || translate('addToCart')"
:confirm-text="confirmText || translate('confirm')"
:show-default-operate="!hasSkuOperateSlot"
@click-btn-operate="clickBtnOperate"
>
<template #operateBtn>
<slot name="skuOperate" />
</template>
</SkuOperate>
</view>
</NutPopup>
</template>
<style lang="scss">
@import './index';
</style>