@react-vant-next/campaign
Version:
React Mobile UI Components based on Vant UI - Next Generation
386 lines (381 loc) • 17.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var tslib = require('tslib');
var jsxRuntime = require('react/jsx-runtime');
var hooks = require('@react-vant-next/hooks');
var ui = require('@react-vant-next/ui');
var utils = require('@react-vant-next/utils');
var cls = require('clsx');
var React = require('react');
var SkuRow = require('./components/SkuRow.js');
var SkuRowItem = require('./components/SkuRowItem.js');
var SkuRowPropItem = require('./components/SkuRowPropItem.js');
var SkuStepper = require('./components/SkuStepper.js');
var constants = require('./constants.js');
var utils$1 = require('./utils.js');
const { QUOTA_LIMIT } = constants.LIMIT_TYPE;
const [bem] = utils.createNamespace("sku");
function Sku(_a) {
var _b, _c;
var { ref } = _a, p = tslib.__rest(_a, ["ref"]);
const props = utils.mergeProps(p, {
stepperTitle: "购买数量",
properties: [],
showAddCartBtn: true,
disableSoldoutSku: true,
showHeaderImage: true,
previewOnClickImage: true,
showSoldoutSku: true,
resetOnHide: true,
safeAreaInsetBottom: true,
quota: 0,
quotaUsed: 0,
startSaleNum: 1,
stockThreshold: 50,
bodyOffsetTop: 200,
customStepperConfig: {},
});
const stepperError = React.useRef(false);
const [visible, setVisible] = React.useState(false);
const [state, updateState] = hooks.useSetState({
selectedSku: {},
selectedProp: {},
selectedNum: props.startSaleNum,
});
const { sku, properties = [] } = props;
const { tree = [] } = sku;
const bodyStyle = React.useMemo(() => {
const maxHeight = window.innerHeight - props.bodyOffsetTop;
return {
maxHeight: `${maxHeight}px`,
};
}, [props.bodyOffsetTop]);
const imageList = React.useMemo(() => {
const { goods } = props;
const rs = [goods === null || goods === void 0 ? void 0 : goods.picture];
if (sku.tree.length > 0) {
sku.tree.forEach((treeItem) => {
if (!treeItem.v)
return;
treeItem.v.forEach((vItem) => {
const img = vItem.previewImgUrl || vItem.imgUrl || vItem.img_url;
if (img && !rs.includes(img)) {
rs.push(img);
}
});
});
}
return rs;
}, [(_b = props.goods) === null || _b === void 0 ? void 0 : _b.picture, sku.tree]);
const hasSku = React.useMemo(() => !sku.none_sku, [sku.none_sku]);
const hasSkuOrAttr = React.useMemo(() => hasSku || properties.length > 0, [hasSku, properties]);
const isSkuCombSelected = React.useMemo(() => {
// SKU 未选完
if (hasSku && !utils$1.isAllSelected(tree, state.selectedSku)) {
return false;
}
// 属性未全选
return !properties
.filter(i => i.is_necessary !== false)
.some(i => (state.selectedProp[i.k_id] || []).length === 0);
}, [hasSku, state]);
const selectedSkuValues = React.useMemo(() => {
return utils$1.getSelectedSkuValues(tree, state.selectedSku);
}, [tree, state.selectedSku]);
const selectedPropValues = React.useMemo(() => {
return utils$1.getSelectedPropValues(properties, state.selectedProp);
}, [properties, state.selectedProp]);
const selectedSkuComb = React.useMemo(() => {
let skuComb = null;
if (isSkuCombSelected) {
if (hasSku) {
skuComb = utils$1.getSkuComb(sku.list, state.selectedSku);
}
else {
skuComb = {
id: sku.collection_id,
price: Math.round(+sku.price * 100),
stock_num: sku.stock_num,
};
}
if (skuComb) {
skuComb.properties = utils$1.getSelectedProperties(properties, state.selectedProp);
skuComb.property_price = selectedPropValues.reduce((acc, cur) => acc + (cur.price || 0), 0);
}
}
return skuComb;
}, [
isSkuCombSelected,
hasSku,
JSON.stringify(sku),
JSON.stringify(state),
properties,
selectedPropValues,
]);
const unselectedSku = React.useMemo(() => {
return tree.filter(item => !state.selectedSku[item.k_s]).map(item => item.k);
}, [tree, state.selectedSku]);
const getUnselectedProp = React.useCallback((isNecessary) => {
return properties
.filter(item => (isNecessary ? item.is_necessary !== false : true))
.filter(item => (state.selectedProp[item.k_id] || []).length < 1)
.map(item => item.k);
}, [properties, state.selectedProp]);
const selectedText = React.useMemo(() => {
if (selectedSkuComb) {
const values = selectedSkuValues.concat(selectedPropValues);
return `已选 ${values.map(item => item.name).join(" ")}`;
}
return `请选择 ${unselectedSku.concat(getUnselectedProp()).join(" ")}`;
}, [
unselectedSku,
getUnselectedProp,
selectedSkuComb,
selectedSkuValues,
selectedPropValues,
]);
const price = React.useMemo(() => {
if (selectedSkuComb) {
return ((selectedSkuComb.price + selectedSkuComb.property_price)
/ 100).toFixed(2);
}
// sku.price是一个格式化好的价格区间
return sku.price;
}, [JSON.stringify(selectedSkuComb), sku.price]);
const stock = React.useMemo(() => {
const { stockNum } = props.customStepperConfig;
if (stockNum !== undefined) {
return stockNum;
}
if (selectedSkuComb) {
return selectedSkuComb.stock_num;
}
return sku.stock_num;
}, [sku.stock_num, JSON.stringify(selectedSkuComb)]);
const stockContent = React.useMemo(() => {
if (props.stockRender) {
return props.stockRender(stock);
}
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: ["\u5269\u4F59", jsxRuntime.jsx("span", { className: cls(bem("stock-num", {
highlight: stock < props.stockThreshold,
})), children: stock }), "\u4EF6"] }));
}, [stock]);
const onSelect = (skuValue) => {
// 点击已选中的sku时则取消选中
const selectedSku = state.selectedSku[skuValue.skuKeyStr] === skuValue.id
? Object.assign(Object.assign({}, state.selectedSku), { [skuValue.skuKeyStr]: constants.UNSELECTED_SKU_VALUE_ID }) : Object.assign(Object.assign({}, state.selectedSku), { [skuValue.skuKeyStr]: skuValue.id });
updateState({ selectedSku });
if (props.onSkuSelected) {
props.onSkuSelected({
skuValue,
selectedSku: state.selectedSku,
selectedSkuComb,
});
}
};
const onPropSelect = (propValue) => {
const arr = state.selectedProp[propValue.skuKeyStr] || [];
const pos = arr.indexOf(propValue.id);
if (pos > -1) {
arr.splice(pos, 1);
}
else if (propValue.multiple) {
arr.push(propValue.id);
}
else {
arr.splice(0, 1, propValue.id);
}
const selectedProp = Object.assign(Object.assign({}, state.selectedProp), { [propValue.skuKeyStr]: arr });
updateState({ selectedProp });
if (props.onSkuPropSelected) {
props.onSkuPropSelected({
propValue,
selectedProp: state.selectedProp,
selectedSkuComb,
});
}
};
const onOverLimit = (data) => {
const { action, limitType, quota, quotaUsed } = data;
const { handleOverLimit } = props.customStepperConfig;
if (handleOverLimit) {
handleOverLimit(data);
return;
}
if (action === "minus") {
if (props.startSaleNum > 1) {
ui.Toast(`${props.startSaleNum}件起售`);
}
else {
ui.Toast("至少选择一件");
}
}
else if (action === "plus") {
if (limitType === QUOTA_LIMIT) {
if (quotaUsed > 0) {
ui.Toast(`每人限购${quota}件,你已购买${quotaUsed}件`);
}
else {
ui.Toast(`每人限购${quota}件`);
}
}
else {
ui.Toast("库存不足");
}
}
};
const onStepperState = (data) => {
stepperError.current = data.valid
? null
: Object.assign(Object.assign({}, data), { action: "plus" });
};
const validateSku = () => {
if (state.selectedNum === 0) {
return "商品已经无法购买啦";
}
if (isSkuCombSelected) {
return "";
}
return `请选择 ${unselectedSku.concat(getUnselectedProp(true)).join(" ")}`;
};
const getSkuData = () => {
return {
goodsId: props.goodsId,
selectedNum: state.selectedNum,
selectedSkuComb,
};
};
const onAddCart = (data) => {
var _a;
(_a = props.onAddCart) === null || _a === void 0 ? void 0 : _a.call(props, data);
};
const onBuyClicked = (data) => {
var _a;
(_a = props.onBuyClicked) === null || _a === void 0 ? void 0 : _a.call(props, data);
};
const onBuyOrAddCart = (type) => tslib.__awaiter(this, void 0, void 0, function* () {
// sku 不符合购买条件
if (stepperError.current) {
onOverLimit(stepperError.current);
return;
}
if (props.customSkuValidator) {
if (!(yield props.customSkuValidator(type, Object.assign(Object.assign({}, state.selectedSku), state.selectedProp)))) {
return;
}
}
else {
const error = validateSku();
if (error) {
ui.Toast(error);
return;
}
}
const data = getSkuData();
if (type === "add-cart") {
onAddCart(data);
}
else {
onBuyClicked(data);
}
});
const show = (initialValue) => {
setVisible(true);
if (initialValue) {
updateState(initialValue);
}
};
const reset = () => {
updateState({
selectedSku: {},
selectedProp: {},
selectedNum: props.startSaleNum,
});
};
const onPopupClose = () => {
setVisible(false);
if (props.popupProps && props.popupProps.onClose)
props.popupProps.onClose();
};
const onPopupClosed = () => {
if (props.resetOnHide) {
reset();
}
if (props.popupProps && props.popupProps.onClosed)
props.popupProps.onClosed();
};
const onPreviewImage = (selectedValue) => {
let index = 0;
let indexImage = imageList[0];
if (selectedValue && selectedValue.imgUrl) {
imageList.some((image, pos) => {
if (image === selectedValue.imgUrl) {
index = pos;
return true;
}
return false;
});
indexImage = selectedValue.imgUrl;
}
const params = Object.assign(Object.assign({}, selectedValue), { index,
imageList,
indexImage });
if (!props.previewOnClickImage)
return;
ui.ImagePreview.open({
images: imageList,
startPosition: index,
onClose: () => {
if (props.onClosePreview)
props.onClosePreview(params);
},
});
if (props.onOpenPreview) {
props.onOpenPreview(params);
}
};
const renderHeaderInfo = () => {
var _a;
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [((_a = props.skuHeaderPriceRender) === null || _a === void 0 ? void 0 : _a.call(props, price)) || (jsxRuntime.jsxs("div", { className: cls(bem("goods-price")), children: [jsxRuntime.jsx("span", { className: cls(bem("price-symbol")), children: "\uFFE5" }), jsxRuntime.jsx("span", { className: cls(bem("price-num")), children: price }), props.priceTag && (jsxRuntime.jsx("span", { className: cls(bem("price-tag")), children: props.priceTag }))] })), props.skuHeaderOriginPrice && (jsxRuntime.jsx("div", { className: cls(bem("header-item")), children: props.skuHeaderOriginPrice })), !props.hideStock && (jsxRuntime.jsx("div", { className: cls(bem("header-item")), children: jsxRuntime.jsx("span", { className: cls(bem("stock")), children: stockContent }) })), !props.hideSelectedText && (jsxRuntime.jsx("div", { className: cls(bem("header-item")), children: selectedText }))] }));
};
const renderHeader = () => {
if (props.skuHeader)
return props.skuHeader;
const selectedValue = utils$1.getSkuImgValue(sku, state.selectedSku);
const imgUrl = selectedValue
? selectedValue.imgUrl
: props.goods.picture;
return (jsxRuntime.jsxs("div", { className: cls(bem("header"), ui.BORDER_BOTTOM), children: [props.showHeaderImage && (jsxRuntime.jsx(ui.Image, { fit: "cover", src: imgUrl, className: cls(bem("header__img-wrap")), onClick: () => onPreviewImage(selectedValue), children: props.skuHeaderImageExtra })), jsxRuntime.jsxs("div", { className: cls(bem("header__goods-info")), children: [renderHeaderInfo(), props.skuHeaderExtra] })] }));
};
const renderGroup = () => {
return (props.skuGroup
|| (hasSkuOrAttr && (jsxRuntime.jsxs("div", { className: cls(bem("group-container", {
"hide-soldout": !props.showSoldoutSku,
})), children: [tree.map((skuTreeItem, i) => (jsxRuntime.jsx(SkuRow.default, { skuRow: skuTreeItem, children: skuTreeItem.v.map((skuValue, idx) => (jsxRuntime.jsx(SkuRowItem.default, { skuList: sku.list, skuValue: skuValue, skuKeyStr: `${skuTreeItem.k_s}`, selectedSku: state.selectedSku, disableSoldoutSku: props.disableSoldoutSku, largeImageMode: skuTreeItem.largeImageMode, previewIcon: props.previewIcon, onSkuSelected: onSelect, onSkuPreviewImage: selectedValue => onPreviewImage(selectedValue) }, idx))) }, i))), properties.map((skuTreeItem, i) => (jsxRuntime.jsx(SkuRow.default, { skuRow: skuTreeItem, children: skuTreeItem.v.map((skuValue, idx) => (jsxRuntime.jsx(SkuRowPropItem.default, { skuValue: skuValue, skuKeyStr: `${skuTreeItem.k_id}`, selectedProp: state.selectedProp, multiple: skuTreeItem.is_multiple, onSkuPropSelected: onPropSelect }, idx))) }, i)))] }))));
};
const renderStepper = () => props.skuStepper || (jsxRuntime.jsx(SkuStepper.default, { currentNum: state.selectedNum, onChange: (currentValue) => {
updateState({ selectedNum: Number.parseInt(`${currentValue}`, 10) });
if (props.onStepperChange)
props.onStepperChange(currentValue);
}, stock: stock, quota: props.quota, quotaUsed: props.quotaUsed, startSaleNum: props.startSaleNum, disableStepperInput: props.disableStepperInput, customStepperConfig: props.customStepperConfig, stepperTitle: props.stepperTitle, hideQuotaText: props.hideQuotaText, onSkuStepperState: onStepperState, onSkuOverLimit: onOverLimit }));
const renderBody = () => {
return (jsxRuntime.jsxs("div", { className: cls(bem("body")), style: bodyStyle, children: [props.skuBodyTop, renderGroup(), props.skuGroupExtra, renderStepper()] }));
};
const renderActions = () => {
return (props.skuActions || (jsxRuntime.jsx("div", { className: cls(bem("actions")), children: jsxRuntime.jsxs(ui.ActionBar, { children: [props.showAddCartBtn && (jsxRuntime.jsx(ui.ActionBar.Button, { type: "warning", text: props.addCartText || "加入购物车", onClick: () => onBuyOrAddCart("add-cart") })), jsxRuntime.jsx(ui.ActionBar.Button, { type: "danger", text: props.buyText || "立即购买", onClick: () => onBuyOrAddCart("buy-clicked") })] }) })));
};
React.useEffect(() => {
if (props.initialSku) {
updateState(props.initialSku);
}
}, [JSON.stringify(props.initialSku)]);
React.useImperativeHandle(ref, () => ({
reset,
getSkuData,
show,
update: updateState,
}));
return (jsxRuntime.jsxs(ui.Popup, Object.assign({ round: true, closeable: true, position: "bottom" }, props.popupProps, { visible: visible, onClose: onPopupClose, onClosed: onPopupClosed, className: cls((_c = props.popupProps) === null || _c === void 0 ? void 0 : _c.className, bem("container")), children: [renderHeader(), renderBody(), props.skuActionsTop, renderActions(), props.skuActionsBottom] })));
}
exports.default = Sku;
//# sourceMappingURL=Sku.js.map