update
This commit is contained in:
50
package-lock.json
generated
50
package-lock.json
generated
@@ -8,7 +8,9 @@
|
|||||||
"name": "nguyencongpc",
|
"name": "nguyencongpc",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fancyapps/ui": "^6.1.7",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
|
"framer-motion": "^12.23.26",
|
||||||
"next": "16.0.10",
|
"next": "16.0.10",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"react": "19.2.1",
|
"react": "19.2.1",
|
||||||
@@ -462,6 +464,12 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fancyapps/ui": {
|
||||||
|
"version": "6.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fancyapps/ui/-/ui-6.1.7.tgz",
|
||||||
|
"integrity": "sha512-KHOvuy90JBFDgbNa2V1N9Jg7PE/lSQMXN9VbhR+WQSIxIEi4PV7kndeao7ezir5WeJ8OZRyDelNKJVLicXfBIg==",
|
||||||
|
"license": "SEE LICENSE IN LICENSE.md"
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
@@ -3650,6 +3658,33 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.23.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz",
|
||||||
|
"integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.23.23",
|
||||||
|
"motion-utils": "^12.23.6",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -4965,6 +5000,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
|
||||||
|
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fancyapps/ui": "^6.1.7",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
|
"framer-motion": "^12.23.26",
|
||||||
"next": "16.0.10",
|
"next": "16.0.10",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"react": "19.2.1",
|
"react": "19.2.1",
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { productCategoryData } from '@/data/product/category';
|
import CategoryPage from '@/components/product/Category';
|
||||||
|
import ProductDetailPage from '@/components/product/ProductDetail';
|
||||||
|
|
||||||
// import component
|
import { resolvePageType } from '@/lib/resolvePageType';
|
||||||
import CategoryPage from '@components/layout/product/Category';
|
|
||||||
|
|
||||||
export default function DynamicPage() {
|
export default function DynamicPage() {
|
||||||
const params = useParams();
|
const { slug } = useParams();
|
||||||
const slug = ('/' + params?.slug) as string;
|
const fullSlug = '/' + slug;
|
||||||
|
|
||||||
if (productCategoryData.find((c) => c.current_category.url == slug)) {
|
const pageType = resolvePageType(fullSlug);
|
||||||
return <CategoryPage slug={slug} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
switch (pageType) {
|
||||||
|
case 'category':
|
||||||
|
return <CategoryPage slug={fullSlug} />;
|
||||||
|
case 'product':
|
||||||
|
return <ProductDetailPage slug={fullSlug} />;
|
||||||
|
default:
|
||||||
return <div>404 Không tìm thấy</div>;
|
return <div>404 Không tìm thấy</div>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import 'swiper/css';
|
|||||||
import 'swiper/css/navigation';
|
import 'swiper/css/navigation';
|
||||||
import 'swiper/css/pagination';
|
import 'swiper/css/pagination';
|
||||||
import '@styles/globals.css';
|
import '@styles/globals.css';
|
||||||
import Header from '@/components/layout/other/Header';
|
import Header from '@/components/other/Header';
|
||||||
import Footer from '@/components/layout/other/Footer';
|
import Footer from '@/components/other/Footer';
|
||||||
|
|
||||||
import PreLoader from '@components/common/PreLoader';
|
import PreLoader from '@components/common/PreLoader';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Home from '@components/layout/home';
|
import Home from '@/components/home';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|||||||
59
src/components/common/error/index.tsx
Normal file
59
src/components/common/error/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
export const ErrorLink = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-50 via-white to-blue-100 px-4">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
className="w-full max-w-md rounded-3xl bg-white p-8 text-center shadow-xl"
|
||||||
|
>
|
||||||
|
{/* Icon lỗi link */}
|
||||||
|
<motion.div
|
||||||
|
animate={{ y: [0, -4, 0] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 1.8 }}
|
||||||
|
className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-orange-100"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="h-10 w-10 text-orange-500"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M13.828 10.172a4 4 0 00-5.656 5.656M7 7a7 7 0 019.9 9.9M12 12l.01.01"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<h1 className="text-2xl font-bold text-gray-800">Đường dẫn không hợp lệ</h1>
|
||||||
|
|
||||||
|
<p className="mt-3 text-sm text-gray-600">
|
||||||
|
Bạn truy cập không tồn tại hoặc đường dẫn đã bị thay đổi.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<div className="mt-8 flex flex-col gap-3">
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="rounded-xl bg-blue-600 px-6 py-3 text-sm font-semibold text-white transition hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
← Về trang chủ
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/products"
|
||||||
|
className="rounded-xl border border-gray-300 px-6 py-3 text-sm font-medium text-gray-700 transition hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
Xem tất cả sản phẩm
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,7 +8,7 @@ import ItemProduct from '@/components/common/ItemProduct';
|
|||||||
|
|
||||||
import { InfoCategory } from '@/types';
|
import { InfoCategory } from '@/types';
|
||||||
|
|
||||||
import { menuData } from '@/components/layout/other/Header/menuData';
|
import { menuData } from '@/components/other/Header/menuData';
|
||||||
import { productData } from './productData';
|
import { productData } from './productData';
|
||||||
|
|
||||||
const BoxListCategory: React.FC = () => {
|
const BoxListCategory: React.FC = () => {
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SliderHome from './SliderHome';
|
import SliderHome from './SliderHome';
|
||||||
import BoxProductDeal from './BoxDeal';
|
import BoxProductDeal from './Deal';
|
||||||
import CategoryFeature from './CategoryFeature';
|
import CategoryFeature from './CategoryFeature';
|
||||||
import BoxListCategory from './BoxCategory';
|
import BoxListCategory from './Category';
|
||||||
import BoxArticle from './BoxArticle';
|
import BoxArticle from './Article';
|
||||||
import BoxArticleVideo from './BoxArticleVideo';
|
import BoxArticleVideo from './ArticleVideo';
|
||||||
import BoxReviewCustomer from './BoxReviewCustomer';
|
import BoxReviewCustomer from './ReviewCustomer';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
@@ -13,7 +13,7 @@ const ItemCategoryChild: React.FC<BoxCategoryChildProps> = ({ item }) => {
|
|||||||
? item.big_image
|
? item.big_image
|
||||||
: item.thumnail
|
: item.thumnail
|
||||||
? item.thumnail
|
? item.thumnail
|
||||||
: '/static/assets/nguyencong_2023/images/favicon.png';
|
: 'https://nguyencongpc.vn/static/assets/nguyencong_2023/images/favicon.png';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import type { CategoryData } from '@/types';
|
import type { CategoryData } from '@/types';
|
||||||
import { productCategoryData } from '@/data/product/category';
|
import { productCategoryData } from '@/data/product/category';
|
||||||
import { findCategoryBySlug } from '@/lib/category';
|
import { findCategoryBySlug } from '@/lib/product/category';
|
||||||
|
|
||||||
// box
|
// box
|
||||||
import { Breadcrumb } from '@components/common/Breadcrumb';
|
import { Breadcrumb } from '@components/common/Breadcrumb';
|
||||||
@@ -18,7 +18,7 @@ interface CategoryPageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CategoryPage: React.FC<CategoryPageProps> = ({ slug }) => {
|
const CategoryPage: React.FC<CategoryPageProps> = ({ slug }) => {
|
||||||
// Ép kiểu dữ liệu từ index.ts về CategoryData[] nếu cần
|
// Ép kiểu dữ liệu từ index.ts về CategoryData[]
|
||||||
const categories = productCategoryData as unknown as CategoryData[];
|
const categories = productCategoryData as unknown as CategoryData[];
|
||||||
const currentCategory = findCategoryBySlug(slug, categories);
|
const currentCategory = findCategoryBySlug(slug, categories);
|
||||||
|
|
||||||
77
src/components/product/ProductDetail/ImageProduct/index.tsx
Normal file
77
src/components/product/ProductDetail/ImageProduct/index.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
|
import { Autoplay, Navigation, Pagination, Thumbs } from 'swiper/modules';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { ProductImageGallery } from '@/types';
|
||||||
|
import type { Swiper as SwiperType } from 'swiper';
|
||||||
|
import '@fancyapps/ui/dist/fancybox/fancybox.css';
|
||||||
|
import { Fancybox, type FancyboxOptions } from '@fancyapps/ui';
|
||||||
|
|
||||||
|
interface ImageProps {
|
||||||
|
ItemImage: ProductImageGallery[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ImageProduct: React.FC<ImageProps> = ({ ItemImage }) => {
|
||||||
|
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
|
||||||
|
Fancybox.bind("[data-fancybox='gallery']", {
|
||||||
|
dragToClose: true,
|
||||||
|
Toolbar: {
|
||||||
|
display: {
|
||||||
|
left: [],
|
||||||
|
middle: ['counter'],
|
||||||
|
right: ['zoom', 'close'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Thumbs: { autoStart: false },
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
Fancybox.unbind("[data-fancybox='gallery']");
|
||||||
|
Fancybox.close();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="product-images-show">
|
||||||
|
<div className="gallery-top product-info-image">
|
||||||
|
<Swiper
|
||||||
|
modules={[Autoplay, Navigation, Pagination, Thumbs]}
|
||||||
|
spaceBetween={12}
|
||||||
|
slidesPerView={1}
|
||||||
|
loop={true}
|
||||||
|
thumbs={{ swiper: thumbsSwiper }}
|
||||||
|
>
|
||||||
|
{ItemImage?.map((item, index) => (
|
||||||
|
<SwiperSlide key={index}>
|
||||||
|
<Link href={item.size.original} data-fancybox="gallery" className="bigImage">
|
||||||
|
<Image src={item.size.original} alt={''} width="595" height="595" />
|
||||||
|
</Link>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
<div className="gallery-thumbs product-images-slider mt-2">
|
||||||
|
<Swiper
|
||||||
|
modules={[Autoplay, Navigation, Pagination, Thumbs]}
|
||||||
|
spaceBetween={12}
|
||||||
|
slidesPerView={6}
|
||||||
|
loop={true}
|
||||||
|
onSwiper={setThumbsSwiper}
|
||||||
|
>
|
||||||
|
{ItemImage?.map((item, index) => (
|
||||||
|
<SwiperSlide key={index}>
|
||||||
|
<div className="smallImage">
|
||||||
|
<Image src={item.size.original} alt={''} width="90" height="60" />
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
51
src/components/product/ProductDetail/index.tsx
Normal file
51
src/components/product/ProductDetail/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
import React from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import type { ProductDetailData } from '@/types';
|
||||||
|
import { productDetailData } from '@/data/product/detail';
|
||||||
|
import { findProductDetailBySlug } from '@/lib/product/productdetail';
|
||||||
|
import { ErrorLink } from '@components/common/error';
|
||||||
|
|
||||||
|
import { Breadcrumb } from '@components/common/Breadcrumb';
|
||||||
|
import { ImageProduct } from './ImageProduct';
|
||||||
|
|
||||||
|
interface ProductDetailPageProps {
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductDetailPage: React.FC<ProductDetailPageProps> = ({ slug }) => {
|
||||||
|
const productDetails = productDetailData as unknown as ProductDetailData[];
|
||||||
|
const Products = findProductDetailBySlug(slug, productDetails);
|
||||||
|
|
||||||
|
const breadcrumbItems = Products?.product_info?.productPath?.[0]?.path.map((item) => ({
|
||||||
|
name: item.name,
|
||||||
|
url: item.url,
|
||||||
|
})) ?? [{ name: 'Trang chủ', url: '/' }];
|
||||||
|
|
||||||
|
// Trường hợp không tìm thấy chi tiết sản phẩm
|
||||||
|
// Không tìm thấy chi tiết sản phẩm
|
||||||
|
if (!Products) {
|
||||||
|
return <ErrorLink />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container">
|
||||||
|
<Breadcrumb items={breadcrumbItems} />
|
||||||
|
</div>
|
||||||
|
<section className="page-product-detail mt-2 bg-white">
|
||||||
|
<div className="container">
|
||||||
|
<div className="box-content-product-detail flex justify-between gap-5">
|
||||||
|
<div className="box-left">
|
||||||
|
{/* image product */}
|
||||||
|
<ImageProduct ItemImage={Products.product_info.productImageGallery} />
|
||||||
|
</div>
|
||||||
|
<div className="box-right"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductDetailPage;
|
||||||
5266
src/data/product/detail/index.ts
Normal file
5266
src/data/product/detail/index.ts
Normal file
File diff suppressed because one or more lines are too long
16
src/lib/product/productdetail/index.ts
Normal file
16
src/lib/product/productdetail/index.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { ProductDetailData } from '@/types';
|
||||||
|
|
||||||
|
// Hàm helper để lấy URL an toàn từ các cấu trúc dữ liệu khác nhau
|
||||||
|
function getSlug(url: string): string {
|
||||||
|
const parts = url.split('/').filter(Boolean);
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findProductDetailBySlug(
|
||||||
|
slug: string,
|
||||||
|
ProductDetail: ProductDetailData[],
|
||||||
|
): ProductDetailData | null {
|
||||||
|
const found = ProductDetail.find((item) => item.product_info.productUrl === slug);
|
||||||
|
|
||||||
|
return found ?? null;
|
||||||
|
}
|
||||||
14
src/lib/resolvePageType.ts
Normal file
14
src/lib/resolvePageType.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { productCategoryData } from '@/data/product/category';
|
||||||
|
import { productDetailData } from '@/data/product/detail';
|
||||||
|
|
||||||
|
export function resolvePageType(slug: string) {
|
||||||
|
// kiểm tra danh mục
|
||||||
|
if (productCategoryData.some((c) => c.current_category.url == slug)) {
|
||||||
|
return 'category';
|
||||||
|
}
|
||||||
|
// kiểm tra sản phẩm
|
||||||
|
if (productDetailData.some((c) => c.product_info.productUrl == slug)) {
|
||||||
|
return 'product';
|
||||||
|
}
|
||||||
|
return '404';
|
||||||
|
}
|
||||||
@@ -3140,28 +3140,22 @@ textarea::placeholder {
|
|||||||
}
|
}
|
||||||
.page-product-detail .box-content-product-detail .box-left .product-images-show .bigImage {
|
.page-product-detail .box-content-product-detail .box-left .product-images-show .bigImage {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
height: 595px;
|
||||||
margin-bottom: 6px;
|
margin: auto;
|
||||||
padding-bottom: 100%;
|
|
||||||
}
|
}
|
||||||
.page-product-detail .box-content-product-detail .box-left .product-images-show .smallImage {
|
.page-product-detail .box-content-product-detail .box-left .product-images-show .smallImage {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
padding-bottom: 15%;
|
|
||||||
border: 1px solid #d6d6d6;
|
border: 1px solid #d6d6d6;
|
||||||
|
height: 80px;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
.page-product-detail .box-content-product-detail .box-left .product-images-show .bigImage img,
|
.page-product-detail .box-content-product-detail .box-left .product-images-show .bigImage img,
|
||||||
.page-product-detail .box-content-product-detail .box-left .product-images-show .smallImage img {
|
.page-product-detail .box-content-product-detail .box-left .product-images-show .smallImage img {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
top: 0;
|
height: 100%;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: block;
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.page-product-detail .box-content-product-detail .box-right .product-name {
|
.page-product-detail .box-content-product-detail .box-right .product-name {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|||||||
249
src/types/product/detail/index.ts
Normal file
249
src/types/product/detail/index.ts
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// Brand
|
||||||
|
interface Brand {
|
||||||
|
id: string;
|
||||||
|
brand_index: string;
|
||||||
|
name: string;
|
||||||
|
summary: string;
|
||||||
|
image: string;
|
||||||
|
product: string;
|
||||||
|
status: string;
|
||||||
|
is_featured: string;
|
||||||
|
ordering: string;
|
||||||
|
letter: string;
|
||||||
|
lastUpdate: string;
|
||||||
|
brand_page_view: string;
|
||||||
|
meta_title: string;
|
||||||
|
meta_keywords: string;
|
||||||
|
meta_description: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product Image
|
||||||
|
export interface ProductImage {
|
||||||
|
small: string;
|
||||||
|
large: string;
|
||||||
|
original: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductImageGallery {
|
||||||
|
size: ProductImage;
|
||||||
|
alt: string;
|
||||||
|
folder: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product Path
|
||||||
|
interface ProductPathItem {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductPath {
|
||||||
|
path: ProductPathItem[];
|
||||||
|
path_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special Offer
|
||||||
|
interface SpecialOfferItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
thumbnail: string;
|
||||||
|
cash_value: string;
|
||||||
|
quantity: string;
|
||||||
|
from_time: string;
|
||||||
|
to_time: string;
|
||||||
|
url: string;
|
||||||
|
description: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpecialOffer {
|
||||||
|
other: SpecialOfferItem[];
|
||||||
|
all: SpecialOfferItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category Info
|
||||||
|
interface CategoryInfo {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
summary: string;
|
||||||
|
is_featured: string;
|
||||||
|
isParent: string;
|
||||||
|
url: string;
|
||||||
|
parentId: string;
|
||||||
|
thumnail: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal
|
||||||
|
interface Deal {
|
||||||
|
id: string;
|
||||||
|
pro_id: string;
|
||||||
|
title: string;
|
||||||
|
price: string;
|
||||||
|
quantity: string;
|
||||||
|
min_purchase: string;
|
||||||
|
max_purchase: string;
|
||||||
|
is_featured: string;
|
||||||
|
from_time: string;
|
||||||
|
to_time: string;
|
||||||
|
is_started: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pricing Trace
|
||||||
|
interface PricingTrace {
|
||||||
|
price: string;
|
||||||
|
type: string;
|
||||||
|
type_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag
|
||||||
|
interface Tag {
|
||||||
|
tag: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Review/Comment Summary
|
||||||
|
interface RateSummary {
|
||||||
|
rate: string;
|
||||||
|
total: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReviewSummary {
|
||||||
|
list_rate: RateSummary[];
|
||||||
|
avgRate: string;
|
||||||
|
total: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Review {
|
||||||
|
summary: ReviewSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Comment {
|
||||||
|
summary: ReviewSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product Info
|
||||||
|
export interface ProductInfo {
|
||||||
|
id: string;
|
||||||
|
productId: string;
|
||||||
|
isOn: string;
|
||||||
|
productPath: ProductPath[];
|
||||||
|
productModel: string;
|
||||||
|
productSKU: string;
|
||||||
|
productUrl: string;
|
||||||
|
productName: string;
|
||||||
|
productImage: ProductImage;
|
||||||
|
price: string;
|
||||||
|
quantity: string;
|
||||||
|
currency: string;
|
||||||
|
priceUnit: string;
|
||||||
|
marketPrice: string;
|
||||||
|
brand: Brand;
|
||||||
|
productSummary: string;
|
||||||
|
package_accessory: string;
|
||||||
|
productImageGallery: ProductImageGallery[];
|
||||||
|
productImageCount: string;
|
||||||
|
warranty: string;
|
||||||
|
specialOffer: SpecialOffer;
|
||||||
|
specialOfferGroup: [];
|
||||||
|
shipping: string;
|
||||||
|
visit: string;
|
||||||
|
status: string;
|
||||||
|
configCount: string;
|
||||||
|
buy_count: string;
|
||||||
|
has_video: string;
|
||||||
|
manual_url: string;
|
||||||
|
hasVAT: string;
|
||||||
|
productType: Record<string, string>;
|
||||||
|
condition: string;
|
||||||
|
extend: {
|
||||||
|
buy_count: string;
|
||||||
|
pixel_code: string;
|
||||||
|
review_count: string;
|
||||||
|
review_score: string;
|
||||||
|
};
|
||||||
|
variant_option: [];
|
||||||
|
meta_title: string;
|
||||||
|
meta_keyword: string;
|
||||||
|
meta_description: string;
|
||||||
|
bulk_price: [];
|
||||||
|
thum_poster: string;
|
||||||
|
thum_poster_type: string;
|
||||||
|
productDescription: string;
|
||||||
|
productSpec: string;
|
||||||
|
multipartSpec: string;
|
||||||
|
video_code: [];
|
||||||
|
instruction: string;
|
||||||
|
sale_rules: {
|
||||||
|
price: string;
|
||||||
|
normal_price: string;
|
||||||
|
min_purchase: string;
|
||||||
|
max_purchase: string;
|
||||||
|
remain_quantity: string;
|
||||||
|
from_time: string;
|
||||||
|
to_time: string;
|
||||||
|
type: string;
|
||||||
|
type_id: string;
|
||||||
|
};
|
||||||
|
categoryInfo: CategoryInfo[];
|
||||||
|
productTechnicalTable: [];
|
||||||
|
productTechnicalRaw: Record<string, []>;
|
||||||
|
deal_list: Deal[];
|
||||||
|
pricing_traces: PricingTrace[];
|
||||||
|
variant_built: string;
|
||||||
|
tag_list: Tag[];
|
||||||
|
addon: [];
|
||||||
|
review: Review;
|
||||||
|
comment: Comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combo Set
|
||||||
|
interface ComboProduct {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
sku: string;
|
||||||
|
price: string;
|
||||||
|
normal_price: string;
|
||||||
|
discount: string;
|
||||||
|
images: {
|
||||||
|
small: string;
|
||||||
|
large: string;
|
||||||
|
};
|
||||||
|
is_first: string;
|
||||||
|
is_free: string;
|
||||||
|
is_recommended: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComboGroup {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
product_list: ComboProduct[];
|
||||||
|
product_count: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComboSet {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
group_list: ComboGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductDetailData {
|
||||||
|
keywords: string;
|
||||||
|
description: string;
|
||||||
|
title: string;
|
||||||
|
favicon: string;
|
||||||
|
canonical: string;
|
||||||
|
image: string;
|
||||||
|
product_info: ProductInfo;
|
||||||
|
attribute_summary: [];
|
||||||
|
product_config_group: [];
|
||||||
|
combo_set: ComboSet[];
|
||||||
|
user_choice: {
|
||||||
|
like: string;
|
||||||
|
save: string;
|
||||||
|
};
|
||||||
|
structured_data: string;
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from '@/types/product/category';
|
export * from '@/types/product/category';
|
||||||
|
export * from '@/types/product/detail';
|
||||||
|
|||||||
Reference in New Issue
Block a user