"use client"; import { useState, useMemo } from "react"; import { redirect, RedirectType } from 'next/navigation' import { useAppDispatch, useAppSelector } from "@/store/hooks"; import { useCustomToast } from "@utils/hooks/useToast"; import { closeAddToCartDialog, clearAddToCartProduct } from '@/store/slices/addToCartDialogSlice'; import { useAddProduct } from "@utils/hooks/useAddToCart"; import Portal from "@/components/Portal"; import { Drawer, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter } from "@heroui/drawer"; import { ProductOption, ResolvedVariant } from "@/components/catalog/type"; import ImgSwiperInAddToCartModal from "./ImgSwiperInAddToCartModal"; import ProductOptionsInAddToCartModal from "./ProductOptionsInAddToCartModal"; import FooterBtnInAddToCartModal from "./FooterBtnInAddToCartModal"; import { Price } from "@components/theme/ui/Price"; import { isValueAvailable, isOptionValueAvailable, getFirstAvailable, getAvailableVariants, isCombinationAvailable, formatFlexibleVariants, findNearestAvailableVariant } from "@/utils/variantTools"; export function AddToCartModal() { const dispatch = useAppDispatch(); const {isOpen, product} = useAppSelector((state) => state.addToCartDialog); const { isCartLoading, onAddToCart } = useAddProduct(); const { showToast } = useCustomToast(); const images = useMemo(() => { return product?.images?.edges.map((item) => { return item.node; }) ?? []; },[product?.images]); const flexibleVariants = useMemo(() => { return formatFlexibleVariants(product?.flexibleVariants); },[product?.flexibleVariants]); const productOptions: ProductOption[] = useMemo(() => { return product?.productOptions ? JSON.parse(product.productOptions) : []; },[product?.productOptions]); // 获取所有可用变体 const availableVariants = useMemo(() => { return getAvailableVariants(flexibleVariants); },[flexibleVariants]); const isSaleable = product?.isSaleable; const [productQty, setProductQty] = useState(1); // 记录当前哪个选项组刚刚被点击(用于实现“点击组内全部可点”) const [lastClickedOptionId, setLastClickedOptionId] = useState(productOptions[0]?.id ); // 当前选中的选项值映射:option_id -> value_id // useEffect和useMemo的执行时机不同,useMemo早于useEffect(useEffect在浏览器绘制后执行)执行, // useLayoutEffect早于useEffect执行,useLayoutEffect在浏览器绘制之前执行(官方说会阻塞浏览器绘制) const [selected, setSelected] = useState>(() => { // if (Object.keys(selected).length === 0) { let res: Record = {}; const defaultSelected = getFirstAvailable(productOptions, flexibleVariants); if (defaultSelected) { res = defaultSelected; } else { // 极端情况:没有任何可用组合,默认全选每组第一个(但仍需标记为不可用,由禁用逻辑处理) productOptions.forEach((opt) => { res[opt.id] = opt.values[0]?.id; }); } console.log('set default selected ---- ', res); return res; // } }); // 计算每个选项值的禁用状态 const disabledMap = useMemo(() => { const map: Record = {}; if (Object.keys(selected).length === 0) return map; // 没有选中的项 productOptions.forEach((option) => { option.values.forEach((value) => { // 规则:如果该选项组是最近点击的组,校验组中的值是否存在可用变体 if (option.id === lastClickedOptionId) { // map[value.id] = false; map[value.id] = !isValueAvailable(value.id,availableVariants); } else { // 校验其他组的选项值是否可用 map[value.id] = !isOptionValueAvailable( option.id, value.id, selected, productOptions, availableVariants ); } }); }); return map; }, [selected, lastClickedOptionId, productOptions, availableVariants]); // 判断当前选中的组合是否可购买(用于控制购买按钮等) const isCurrentSelectionAvailable = useMemo(() => { let res = true; if(isSaleable !== '1') { res = false; } else { const selectedIds = Object.values(selected); if (selectedIds.length !== productOptions.length) { res = false } else { res = isCombinationAvailable(selectedIds, availableVariants); } } return res; }, [selected, productOptions, availableVariants, isSaleable]); // 获取当前选中变体和价格等信息 const currentVariantInfo: {variant:ResolvedVariant | null; totalLinePrice: number; totalNowPrice: number; save: number;} = useMemo(() => { const selectedIds = Object.values(selected); let totalLinePrice = 0; let totalNowPrice = 0; let save = 0; let variant = flexibleVariants.find((v) => { const vIds = v.optionValues.map((ov) => ov.id); return selectedIds.length === vIds.length && selectedIds.every((id) => vIds.includes(id)); }); if (selectedIds.length === 0 || variant === undefined) { // 尚未初始化,返回一个安全占位对象 return { variant: null, totalLinePrice: 0, totalNowPrice: 0, save: 0 }; } if(variant.priceIndices && variant.priceIndices.length > 0) { totalLinePrice = Number(variant.priceIndices[0].regular_min_price) * productQty; totalNowPrice = Number(variant.priceIndices[0].min_price) * productQty; } if(totalLinePrice !== totalNowPrice) { save = -(totalLinePrice - totalNowPrice); } return { variant, totalLinePrice: totalLinePrice, totalNowPrice: totalNowPrice, save: save }; }, [selected, flexibleVariants, productQty]); const handleOptionClick = (result:{ clickOptionId: number; clickValueId: number; }) => { // 更新前需要判断当前选中的组合是否可以购买,如果可以购买正常更新;如果不能购买查找可以购买的组合然后更新 let {clickOptionId: optionId, clickValueId: valueId} = result; let newSelected: Record = {...selected,[optionId]: valueId}; let selectedValueIds = Object.values(newSelected); if(!isCombinationAvailable(selectedValueIds, availableVariants)) { newSelected = findNearestAvailableVariant(optionId,valueId,{...newSelected},productOptions,availableVariants); } setSelected(newSelected); // 记录当前点击的选项组 setLastClickedOptionId(optionId); }; async function addProductToCart(action:string = 'addtocart') { let params = { productId: String(product?._id), quantity: productQty, variantId: currentVariantInfo.variant?._id, }; let res = await onAddToCart(params); console.log('onAddToCart run ----- 1'); if(action === 'buynow') { if(res) { const responseData = res.data?.createAddProductInCart?.addProductInCart; if(responseData && responseData.success) { redirect('/checkout?step=address', RedirectType.push); } } } } const addToCartHandler = async (callback: ()=> void) => { if(!isCurrentSelectionAvailable) { showToast("The selected options are not available!", "warning"); return; } if(currentVariantInfo.variant && !isCartLoading) { await addProductToCart(); callback(); } }; const buyNowHandler = async (callback: ()=> void) => { if(!isCurrentSelectionAvailable) { showToast("The selected options are not available!", "warning"); return; } if(currentVariantInfo.variant && !isCartLoading) { await addProductToCart('buynow'); callback(); } }; const closeModal = () => { dispatch(closeAddToCartDialog()); setTimeout(() => { dispatch(clearAddToCartProduct()); }, 300); }; const openChange = (e:boolean) => { if(!e) { closeModal(); } } return ( openChange(e)} > {(onClose) => ( <>
Select Options
{/**产品图片 */} {images.length > 0 && (
)}
{/**产品选项 */}
addToCartHandler(onClose)} onBuyNow={()=> buyNowHandler(onClose)} />
)}
); }