This commit is contained in:
2025-12-27 10:03:53 +07:00
parent 1805ff8674
commit e2063bce4c
18 changed files with 549 additions and 61 deletions

10
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@fancyapps/ui": "^6.1.7", "@fancyapps/ui": "^6.1.7",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"framer-motion": "^12.23.26", "framer-motion": "^12.23.26",
"lightgallery": "^2.9.0",
"next": "16.0.10", "next": "16.0.10",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"react": "19.2.1", "react": "19.2.1",
@@ -4626,6 +4627,15 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/lightgallery": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/lightgallery/-/lightgallery-2.9.0.tgz",
"integrity": "sha512-58Ud1DyhD2ao58t+kPEqSZrjFxg23tGd5ZKr75erm7q31g5xhUtWUJH3sTUkhHzlyJAKHj5eTrJ37HQRXG4Wbg==",
"license": "GPLv3",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.30.2", "version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",

View File

@@ -12,6 +12,7 @@
"@fancyapps/ui": "^6.1.7", "@fancyapps/ui": "^6.1.7",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"framer-motion": "^12.23.26", "framer-motion": "^12.23.26",
"lightgallery": "^2.9.0",
"next": "16.0.10", "next": "16.0.10",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"react": "19.2.1", "react": "19.2.1",

View File

@@ -4,16 +4,12 @@ import 'tippy.js/dist/tippy.css';
import { Product } from '@/types'; import { Product } from '@/types';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { formatCurrency } from '@/lib/formatPrice';
type ProductItemProps = { type ProductItemProps = {
item: Product; item: Product;
}; };
const formatCurrency = (value: number | string) => {
const num = typeof value === 'string' ? parseInt(value) : value;
return num.toLocaleString('vi-VN');
};
const ItemProduct: React.FC<ProductItemProps> = ({ item }) => { const ItemProduct: React.FC<ProductItemProps> = ({ item }) => {
const offers = item.specialOffer?.all ?? []; const offers = item.specialOffer?.all ?? [];

View File

@@ -1,17 +1,12 @@
import React from 'react'; import React from 'react';
import Tippy from '@tippyjs/react';
import 'tippy.js/dist/tippy.css';
import { DealType } from '@/types'; import { DealType } from '@/types';
import { formatCurrency } from '@/lib/formatPrice';
import Image from 'next/image';
type ProductItemProps = { type ProductItemProps = {
item: DealType; item: DealType;
}; };
const formatCurrency = (value: number | string) => {
const num = typeof value === 'string' ? parseInt(value) : value;
return num.toLocaleString('vi-VN');
};
const ProductItem: React.FC<ProductItemProps> = ({ item }) => { const ProductItem: React.FC<ProductItemProps> = ({ item }) => {
const { product_info } = item; const { product_info } = item;
const offers = product_info.specialOffer?.all ?? []; const offers = product_info.specialOffer?.all ?? [];
@@ -20,7 +15,7 @@ const ProductItem: React.FC<ProductItemProps> = ({ item }) => {
<div className="product-item"> <div className="product-item">
<a href={product_info.productUrl} className="product-image relative"> <a href={product_info.productUrl} className="product-image relative">
{product_info.productImage.large ? ( {product_info.productImage.large ? (
<img <Image
src={product_info.productImage.large} src={product_info.productImage.large}
width="164" width="164"
height="164" height="164"
@@ -28,7 +23,7 @@ const ProductItem: React.FC<ProductItemProps> = ({ item }) => {
className="lazy" className="lazy"
/> />
) : ( ) : (
<img <Image
src="/static/assets/nguyencong_2023/images/not-image.png" src="/static/assets/nguyencong_2023/images/not-image.png"
width="164" width="164"
height="164" height="164"

View File

@@ -1,10 +1,14 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import Link from 'next/link';
import { Swiper, SwiperSlide } from 'swiper/react'; import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay, Navigation, Pagination } from 'swiper/modules'; import { Autoplay, Navigation, Pagination } from 'swiper/modules';
import { FaCaretRight } from 'react-icons/fa'; import { FaCaretRight } from 'react-icons/fa';
import { productDealData } from './productDealData';
import CounDown from './CounDown'; import CounDown from './CounDown';
import ProductItem from './ProductItem';
const BoxProductDeal: React.FC = () => { const BoxProductDeal: React.FC = () => {
return ( return (
@@ -18,9 +22,9 @@ const BoxProductDeal: React.FC = () => {
<CounDown /> <CounDown />
</div> </div>
</div> </div>
<a href="/deal" className="button-deal color-white mb-10 flex items-center"> <Link href="/deal" className="button-deal color-white mb-10 flex items-center">
Xem thêm khuyến mãi <FaCaretRight size={16} /> Xem thêm khuyến mãi <FaCaretRight size={16} />
</a> </Link>
</div> </div>
<div className="box-list-item-deal swiper-box-deal"> <div className="box-list-item-deal swiper-box-deal">
<Swiper <Swiper
@@ -29,7 +33,13 @@ const BoxProductDeal: React.FC = () => {
slidesPerView={6} slidesPerView={6}
loop={true} loop={true}
navigation={true} navigation={true}
></Swiper> >
{productDealData.map((Item, index) => (
<SwiperSlide key={index}>
<ProductItem item={Item} />
</SwiperSlide>
))}
</Swiper>
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,20 @@
import type { ProductDetailData } from '@/types';
export const BoxPrice = (item: ProductDetailData) => {
return (
<>
{item.product_info.sale_rules.type == 'deal' && (
<div className="box-flash-sale boder-radius-10 flex items-center">
<div className="box-left relative flex items-center">
<i className="sprite sprite-flashsale-detail"></i>
<p className="title-deal font-weight-800">flash sale</p>
</div>
<div className="box-middle product-time-holder global-time-deal flex gap-2"></div>
<div className="box-right">
<div className="box-product-deal"></div>
</div>
</div>
)}
</>
);
};

View File

@@ -0,0 +1,130 @@
import type { ProductDetailData } from '@/types';
import Link from 'next/link';
import { BoxPrice } from './BoxPrice';
export const BoxInfoRight = (item: ProductDetailData) => {
return (
<>
<h1 className="product-name color-black line-clamp-3">{item.product_info.productName}</h1>
<div className="list-basic-product-info flex flex-wrap items-center">
<div className="item-basic">
SP: <span className="color-primary">{item.product_info.productSKU}</span>
</div>
<div className="item-basic">
Đánh giá: <span className="color-primary">{item.product_info.review.summary.total}</span>
</div>
<div className="item-basic">
Bình luận:{' '}
<span className="color-primary">{item.product_info.comment.summary.total}</span>
</div>
<div className="item-basic">
Lượt xem: <span className="color-primary">{item.product_info.visit}</span>
</div>
{item.product_info.extend.buy_count?.length > 0 && (
<div className="item-basic last-item-basic position-relative">
Đã bán: <span className="color-primary">{item.product_info.extend.buy_count}</span>
</div>
)}
</div>
{/* tình trạng */}
<div className="list-basic-product-info flex flex-wrap items-center gap-6">
<div className="item-basic">
Bảo hành: <span className="color-red">{item.product_info.warranty}</span>
</div>
{item.product_info.quantity > '0' && (
<div className="item-basic last-item-basic position-relative">
Tình trạng: <span className="color-green">Còn hàng</span>
</div>
)}
</div>
{/* giá */}
<BoxPrice {...item} />
{item.product_info.specialOffer.all.length > 0 && (
<div className="box-offer-detail border-radius-10">
<div className="title-offer-detail flex items-center">
<i className="sprite sprite-gift-detail"></i>
<p className="font-weight-600">Khuyến mãi</p>
</div>
<div className="list-info-offter">
{item.product_info.specialOffer.all.map((_item, idx) => (
<div key={idx} className="item-offer">
<i className="icon"></i>
<div dangerouslySetInnerHTML={{ __html: _item.title }} />
</div>
))}
</div>
</div>
)}
{/* mua hàng */}
{(item.product_info.quantity > '0' || item.product_info.price > '0') && (
<>
<div className="product-buy-quantity flex items-center">
<p className="title-quantity">Số lượng:</p>
<div className="cart-quantity-select flex items-center justify-center">
<p className="js-quantity-change" data-value="-1">
{' '}
{' '}
</p>
<input
type="text"
className="js-buy-quantity js-quantity-change bk-product-qty font-bold"
defaultValue={1}
/>
<p className="js-quantity-change" data-value="1">
{' '}
+{' '}
</p>
</div>
<Link
href="#"
onClick={() => addProductToCart(item.product_info.id, 0, '')}
className="addCart flex flex-wrap items-center justify-center gap-3"
>
<i className="sprite sprite-cart-detail"></i>
<p className="title-cart">Thêm vào giỏ hàng</p>
</Link>
<input type="hidden" className="js-buy-quantity-temp" value="1" />
</div>
<div id="detail-buy-ads" className="detail-buy grid grid-cols-2 gap-2">
<Link
href="#"
className="detail-buy-now col-span-2"
onClick={() => buyNow(item.product_info.id, 0, '')}
>
<span>ĐT MUA NGAY</span>
Giao hàng tận nơi nhanh chóng
</Link>
<Link
href="#"
className="detail-add-cart"
onClick={() => buyPayInstall(item.product_info.id, 0, '')}
>
<span>TRẢ GÓP QUA HỒ </span>
Chỉ từ 2.665.000/ tháng
</Link>
<Link
href="#"
className="detail-add-cart"
onClick={() => buyAlepay(item.product_info.id, 0, '')}
>
<span>TRẢ GÓP QUA THẺ</span>
Chỉ từ 1.332.500/ tháng
</Link>
</div>
</>
)}
</>
);
};

View File

@@ -0,0 +1,78 @@
import React, { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { ComboProduct } from '@/types';
import { formatCurrency } from '@/lib/formatPrice';
interface ChangePopupProps {
titleGroup: string;
products: ComboProduct[];
open: boolean; // nhận trạng thái mở
onClose: () => void; // hàm đóng popup
onSelect: (product: ComboProduct) => void;
}
export const ChangeProductPopup: React.FC<ChangePopupProps> = ({
titleGroup,
products,
open,
onClose,
onSelect,
}) => {
if (!open) return null; // chỉ render khi open = true
return (
<dialog open className="modal">
<div className="modal-box max-w-5xl bg-white">
<div className="mb-4 flex items-center justify-between">
<h3 className="text-lg font-bold">Chọn {titleGroup} khác</h3>
<button className="btn btn-sm btn-circle btn-ghost" onClick={onClose}>
</button>
</div>
{/* Danh sách sản phẩm */}
<div className="grid grid-cols-4 gap-3">
{products.map((p) => (
<div key={p.id} className="product-item c-pro-item">
<Link href={p.url} className="product-image">
<Image
src={p.images.large || '/static/assets/not-image.png'}
alt={p.title}
className="mb-2 object-cover"
height={170}
width={170}
/>
</Link>
<div className="product-info">
<Link href={p.url}>
<h3 className="product-title line-clamp-2">{p.title}</h3>
</Link>
<div className="product-price-main flex items-center justify-between">
<div className='class="product-price"'>
<b className="price font-[600]">
{Number(p.price) > 0 ? `${formatCurrency(p.price)} đ` : 'Liên hệ'}
</b>
</div>
</div>
<span
className="c-btn js-c-btn"
onClick={() => {
onSelect(p);
}}
>
Chọn mua
</span>
</div>
</div>
))}
</div>
</div>
{/* Overlay */}
<form method="dialog" className="modal-backdrop">
<button onClick={onClose}>close</button>
</form>
</dialog>
);
};

View File

@@ -0,0 +1,102 @@
import React, { useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { ComboProduct } from '@/types';
import { formatCurrency } from '@/lib/formatPrice';
interface ItemComboProps {
item: ComboProduct;
keyGroup: string;
titleGroup: string;
setId: string;
products: ComboProduct[];
onOpenPopup: (titleGroup: string, products: ComboProduct[], item: ComboProduct) => void;
}
export const ItemComboSet: React.FC<ItemComboProps> = ({
item,
keyGroup,
titleGroup,
setId,
products,
onOpenPopup,
}) => {
const hasDiscount = Number(item.normal_price) > Number(item.price) && Number(item.price) > 0;
return (
<>
<div
className={`product-item c-pro-item ${
item.is_free === 'yes' ? 'w-select' : ''
} js-pro-${item.id}`}
>
<Link href={item.url} className="product-image">
{item.images?.large ? (
<Image src={item.images.large} alt={item.title} width={175} height={175} />
) : (
<Image
src="/static/assets/nguyencong_2023/images/not-image.png"
width={175}
height={175}
alt={item.title}
/>
)}
</Link>
<div className="product-info">
<Link href={item.url}>
<h3 className="product-title line-clamp-2">{item.title}</h3>
</Link>
<div className="product-price-main d-flex align-items-center justify-content-between">
<div className="product-price">
<b className="price font-weight-600">
{Number(item.price) > 0 ? `${formatCurrency(item.price)} đ` : 'Liên hệ'}
</b>
</div>
</div>
{hasDiscount ? (
<div className="product-martket-main d-flex align-items-center flex-wrap gap-4">
<p className="product-market-price">{item.normal_price} đ</p>
{item.discount.includes('%') ? (
<div
className="product-percent-price"
style={{ fontSize: '10px', padding: '0 8px' }}
>
-{item.discount}
</div>
) : (
<p style={{ fontSize: '10px', color: '#BE1F2D' }}>(-{item.discount} đ)</p>
)}
</div>
) : (
<div className="product-martket-main d-flex align-items-center"></div>
)}
<p
className="c-pro-change js-chagne-pro"
data-id={item.id}
onClick={() => onOpenPopup(titleGroup, products, item)}
>
Chọn {titleGroup} khác
</p>
<div className="check-box-comboset">
<input
type="checkbox"
className={`position-relative js-price js-check-select js-combo-set js-combo-set-select-product cursor-pointer ${
item.is_free === 'yes' ? 'product_free' : ''
}`}
data-price={item.price}
data-unprice={item.normal_price}
data-idpk={item.id}
data-set-id={setId}
data-group-key={keyGroup}
data-product-id={item.id}
/>
</div>
</div>
</div>
</>
);
};

View File

@@ -0,0 +1,114 @@
import React, { useState } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay, Navigation, Pagination } from 'swiper/modules';
import 'swiper/css';
import { ComboSet, ComboProduct, ComboGroup } from '@/types';
import { ItemComboSet } from './ItemComboset';
import { ChangeProductPopup } from './ChangeProductPopup';
interface ComboProps {
combo_set: ComboSet[];
}
interface PopupGroup {
title: string;
products: ComboProduct[];
}
export const ComboSetBox: React.FC<ComboProps> = ({ combo_set }) => {
const [openPopup, setOpenPopup] = useState(false);
const [popupGroup, setPopupGroup] = useState<PopupGroup>({
title: '',
products: [],
});
const [selectedProduct, setSelectedProduct] = useState<ComboProduct | null>(null);
const handleOpenPopup = (
titleGroup: string,
products: ComboProduct[],
currentItem: ComboProduct,
) => {
setPopupGroup({ title: titleGroup, products });
setSelectedProduct(currentItem); // lưu sản phẩm đang hiển thị
setOpenPopup(true);
};
const handleReplaceProduct = (newProduct: ComboProduct) => {
// cập nhật selectedProduct bằng sản phẩm mới
setSelectedProduct(newProduct);
setOpenPopup(false);
};
const getDisplayedProduct = (group: ComboGroup) => {
// Nếu selectedProduct thuộc group này thì hiển thị nó
if (selectedProduct && group.product_list.some((p) => p.id === selectedProduct.id)) {
return selectedProduct;
}
// Ngược lại lấy sản phẩm mặc định
return group.product_list.find((p) => p.is_first === 'yes') || group.product_list[0];
};
if (!combo_set || combo_set.length === 0) return null;
const setInfo = combo_set[0];
return (
<div className="box-comboset mb-8">
<p className="title-comboset font-weight-600">Mua theo combo</p>
<div id="comboset">
<Swiper
className="list-product-comboset swiper-comboset"
modules={[Autoplay, Navigation, Pagination]}
spaceBetween={16}
slidesPerView={3}
navigation
>
{setInfo.group_list.map((group, index) => {
// lấy sản phẩm đầu tiên theo logic "is_first" hoặc mặc định
const firstProduct =
group.product_list.find((p) => p.is_first === 'yes') || group.product_list[0];
return (
<SwiperSlide key={index}>
<ItemComboSet
item={getDisplayedProduct(group)}
keyGroup={group.key}
titleGroup={group.title}
setId={setInfo.id}
products={group.product_list}
onOpenPopup={handleOpenPopup}
/>
</SwiperSlide>
);
})}
</Swiper>
<div className="comboset-info mt-4 flex justify-between">
<div className="box-left">
<div className="total-comboset flex items-center gap-2">
<p>Tạm tính:</p>
<p className="js-pass-price price text-red font-weight-600">
{/* giả sử lấy giá từ product_info */}
3.050.000 đ
</p>
</div>
<p className="save-price-combo">
Tiết kiệm thêm <span className="save-price">215.000đ</span>
</p>
</div>
<div className="box-right flex items-center justify-end gap-2">
<p className="js-combo-set js-combo-set-checkout buy_combo" data-set-id={setInfo.id}>
Mua thêm <span id="count-pro-selected">0</span> sản phẩm
</p>
</div>
</div>
</div>
<ChangeProductPopup
titleGroup={popupGroup.title}
products={popupGroup.products}
open={openPopup}
onClose={() => setOpenPopup(false)}
onSelect={handleReplaceProduct}
/>
</div>
);
};

View File

@@ -5,8 +5,7 @@ import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { ProductImageGallery } from '@/types'; import { ProductImageGallery } from '@/types';
import type { Swiper as SwiperType } from 'swiper'; import type { Swiper as SwiperType } from 'swiper';
import '@fancyapps/ui/dist/fancybox/fancybox.css'; import useFancybox from '@/hooks/useFancybox';
import { Fancybox, type FancyboxOptions } from '@fancyapps/ui';
interface ImageProps { interface ImageProps {
ItemImage: ProductImageGallery[]; ItemImage: ProductImageGallery[];
@@ -15,30 +14,14 @@ interface ImageProps {
export const ImageProduct: React.FC<ImageProps> = ({ ItemImage }) => { export const ImageProduct: React.FC<ImageProps> = ({ ItemImage }) => {
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null); const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
useEffect(() => { const [fancyboxRef] = useFancybox({
if (typeof window === 'undefined') return; closeButton: 'auto',
Fancybox.bind("[data-fancybox='gallery']", {
dragToClose: true, dragToClose: true,
Toolbar: { });
display: {
left: [],
middle: ['counter'],
right: ['zoom', 'close'],
},
},
Thumbs: { autoStart: false },
} as any);
return () => {
Fancybox.unbind("[data-fancybox='gallery']");
Fancybox.close();
};
}, []);
return ( return (
<div className="product-images-show"> <div className="product-images-show">
<div className="gallery-top product-info-image"> <div className="gallery-top product-info-image" ref={fancyboxRef}>
<Swiper <Swiper
modules={[Autoplay, Navigation, Pagination, Thumbs]} modules={[Autoplay, Navigation, Pagination, Thumbs]}
spaceBetween={12} spaceBetween={12}
@@ -48,7 +31,7 @@ export const ImageProduct: React.FC<ImageProps> = ({ ItemImage }) => {
> >
{ItemImage?.map((item, index) => ( {ItemImage?.map((item, index) => (
<SwiperSlide key={index}> <SwiperSlide key={index}>
<Link href={item.size.original} data-fancybox="gallery" className="bigImage"> <Link href={item.size.original} className="bigImage" data-fancybox>
<Image src={item.size.original} alt={''} width="595" height="595" /> <Image src={item.size.original} alt={''} width="595" height="595" />
</Link> </Link>
</SwiperSlide> </SwiperSlide>

View File

@@ -0,0 +1,33 @@
import React, { useState } from 'react';
import { FaAngleDown, FaAngleUp } from 'react-icons/fa6';
interface SummaryProps {
ItemSummary: string;
}
export const ProductSummary: React.FC<SummaryProps> = ({ ItemSummary }) => {
const summaryArray = ItemSummary.split('\r\n');
const [expanded, setExpanded] = useState(false);
// Nếu chưa expanded thì chỉ hiển thị 3 dòng đầu
const visibleItems = expanded ? summaryArray : summaryArray.slice(0, 3);
return (
<div className="box-product-summary boder-radius-10">
<p className="title font-weight-600">Thông số sản phẩm</p>
<ul className="list-product-summary">
{visibleItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
{summaryArray.length > 3 && (
<div
className="button-showmore flex cursor-pointer items-center gap-2 font-[500]"
onClick={() => setExpanded(!expanded)}
>
<span>{expanded ? 'Thu gọn' : 'Xem thêm'}</span>
{expanded ? <FaAngleUp /> : <FaAngleDown />}
</div>
)}
</div>
);
};

View File

@@ -8,6 +8,9 @@ import { ErrorLink } from '@components/common/error';
import { Breadcrumb } from '@components/common/Breadcrumb'; import { Breadcrumb } from '@components/common/Breadcrumb';
import { ImageProduct } from './ImageProduct'; import { ImageProduct } from './ImageProduct';
import { ProductSummary } from './ProductSummary';
import { ComboSetBox } from './ComboSet';
import { BoxInfoRight } from './BoxInfoRight';
interface ProductDetailPageProps { interface ProductDetailPageProps {
slug: string; slug: string;
@@ -39,8 +42,14 @@ const ProductDetailPage: React.FC<ProductDetailPageProps> = ({ slug }) => {
<div className="box-left"> <div className="box-left">
{/* image product */} {/* image product */}
<ImageProduct ItemImage={Products.product_info.productImageGallery} /> <ImageProduct ItemImage={Products.product_info.productImageGallery} />
<ProductSummary ItemSummary={Products.product_info.productSummary} />
<ComboSetBox combo_set={Products.combo_set} />
</div>
<div className="box-right">
<BoxInfoRight {...Products} />
</div> </div>
<div className="box-right"></div>
</div> </div>
</div> </div>
</section> </section>

17
src/hooks/useFancybox.ts Normal file
View File

@@ -0,0 +1,17 @@
import { useState, useEffect } from 'react';
import { type FancyboxOptions, Fancybox } from '@fancyapps/ui/dist/fancybox/';
import '@fancyapps/ui/dist/fancybox/fancybox.css';
export default function useFancybox(options: Partial<FancyboxOptions> = {}) {
const [root, setRoot] = useState<HTMLElement | null>(null);
useEffect(() => {
if (root) {
Fancybox.bind(root, '[data-fancybox]', options);
return () => Fancybox.unbind(root);
}
}, [root, options]);
return [setRoot];
}

4
src/lib/formatPrice.ts Normal file
View File

@@ -0,0 +1,4 @@
export const formatCurrency = (value: number | string) => {
const num = typeof value === 'string' ? parseInt(value) : value;
return num.toLocaleString('vi-VN');
};

View File

@@ -3426,7 +3426,6 @@ textarea::placeholder {
transition: color 0.2s ease-out; transition: color 0.2s ease-out;
} }
.page-product-detail .box-content-product-detail .detail-buy a { .page-product-detail .box-content-product-detail .detail-buy a {
width: calc(50% - 6px);
padding: 8px 12px; padding: 8px 12px;
text-align: center; text-align: center;
background: #0a76e4; background: #0a76e4;
@@ -3541,20 +3540,7 @@ textarea::placeholder {
font-size: 13px; font-size: 13px;
margin-left: 10px; margin-left: 10px;
} }
.page-product-detail
.box-content-product-detail
.box-product-summary
.list-product-summary
li:nth-child(n + 4) {
display: none;
}
.page-product-detail
.box-content-product-detail
.box-product-summary
.list-product-summary.active
li:nth-child(n + 4) {
display: block;
}
.page-product-detail .box-content-product-detail .list-product-comboset { .page-product-detail .box-content-product-detail .list-product-comboset {
padding: 2px; padding: 2px;
margin-bottom: 16px; margin-bottom: 16px;
@@ -4308,7 +4294,7 @@ textarea::placeholder {
border-radius: 50%; border-radius: 50%;
text-align: center; text-align: center;
} }
.popup-change-pro .popup-main .c-btn { .c-pro-item .c-btn {
cursor: pointer; cursor: pointer;
display: block; display: block;
text-transform: uppercase; text-transform: uppercase;

View File

@@ -199,7 +199,7 @@ export interface ProductInfo {
} }
// Combo Set // Combo Set
interface ComboProduct { export interface ComboProduct {
id: string; id: string;
title: string; title: string;
url: string; url: string;
@@ -216,14 +216,14 @@ interface ComboProduct {
is_recommended: string; is_recommended: string;
} }
interface ComboGroup { export interface ComboGroup {
key: string; key: string;
title: string; title: string;
product_list: ComboProduct[]; product_list: ComboProduct[];
product_count: string; product_count: string;
} }
interface ComboSet { export interface ComboSet {
id: string; id: string;
title: string; title: string;
description: string; description: string;