update
This commit is contained in:
@@ -3,9 +3,11 @@ import { useParams } from 'next/navigation';
|
|||||||
import NotFound from '../pages/404';
|
import NotFound from '../pages/404';
|
||||||
import { resolvePageType } from '@/lib/resolvePageType';
|
import { resolvePageType } from '@/lib/resolvePageType';
|
||||||
|
|
||||||
import CategoryPage from '@/components/Product/Category';
|
import CategoryPage from '@/app/pages/Product/Category';
|
||||||
import ProductDetailPage from '@/components/Product/ProductDetail';
|
import ProductDetailPage from '@/app/pages/Product/ProductDetail';
|
||||||
import ArticlePage from '@/components/Article';
|
import ArticlePage from '@/app/pages/Article/HomeArticlePage';
|
||||||
|
import ArticleCategoryPage from '@/app/pages/Article/CategoryPage';
|
||||||
|
import ArticleDetailPage from '@/app/pages/Article/DetailPage';
|
||||||
|
|
||||||
export default function DynamicPage() {
|
export default function DynamicPage() {
|
||||||
const { slug } = useParams();
|
const { slug } = useParams();
|
||||||
@@ -20,6 +22,10 @@ export default function DynamicPage() {
|
|||||||
return <ProductDetailPage slug={fullSlug} />;
|
return <ProductDetailPage slug={fullSlug} />;
|
||||||
case 'article-home':
|
case 'article-home':
|
||||||
return <ArticlePage />;
|
return <ArticlePage />;
|
||||||
|
case 'article-category':
|
||||||
|
return <ArticleCategoryPage slug={fullSlug} />;
|
||||||
|
case 'article-detail':
|
||||||
|
return <ArticleDetailPage slug={fullSlug} />;
|
||||||
default:
|
default:
|
||||||
return <NotFound />;
|
return <NotFound />;
|
||||||
}
|
}
|
||||||
|
|||||||
127
src/app/deal/page.tsx
Normal file
127
src/app/deal/page.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
|
import { Breadcrumb } from '@components/Common/Breadcrumb';
|
||||||
|
import { bannerData } from '@/data/banner';
|
||||||
|
import { ListDealData } from '@/data/deal';
|
||||||
|
import { formatCurrency } from '@/lib/formatPrice';
|
||||||
|
import CounDown from '@/components/Common/CounDown';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Danh sách deal',
|
||||||
|
description: 'Sản phẩm khuyễn mãi giá ưu đãi',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DealPage() {
|
||||||
|
const breadcrumbItems = [{ name: 'Danh sách deal', url: '/deal' }];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container">
|
||||||
|
<Breadcrumb items={breadcrumbItems} />
|
||||||
|
</div>
|
||||||
|
<section className="page-deal container">
|
||||||
|
<div className="box-product-deal">
|
||||||
|
{bannerData[0].header.banner_page_deal_2023 && (
|
||||||
|
<div className="banner-deal-page mb-5">
|
||||||
|
{bannerData[0].header.banner_page_deal_2023.map((item, index) => (
|
||||||
|
<Link href={item.desUrl} className="item-banner" key={index}>
|
||||||
|
<Image
|
||||||
|
src={item.fileUrl}
|
||||||
|
width={1200}
|
||||||
|
height={325}
|
||||||
|
alt={item.title}
|
||||||
|
style={{ display: 'block' }}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="box-list-item-deal grid grid-cols-4 gap-3" id="js-deal-page">
|
||||||
|
{ListDealData.map((Item, index) => (
|
||||||
|
<div className="product-item" key={index}>
|
||||||
|
<div className="item-deal">
|
||||||
|
<Link
|
||||||
|
href={Item.product_info.productUrl}
|
||||||
|
className="product-image position-relative"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={Item.product_info.productImage.large}
|
||||||
|
width={250}
|
||||||
|
height={250}
|
||||||
|
alt={Item.product_info.productName}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<div className="product-info flex-1">
|
||||||
|
<Link href={Item.product_info.productUrl}>
|
||||||
|
<h3 className="product-title line-clamp-3">
|
||||||
|
{Item.product_info.productName}
|
||||||
|
</h3>
|
||||||
|
</Link>
|
||||||
|
<div className="product-martket-main flex items-center">
|
||||||
|
{Item.product_info.marketPrice > 0 && (
|
||||||
|
<>
|
||||||
|
<p className="product-market-price">
|
||||||
|
{Item.product_info.marketPrice.toLocaleString()} ₫
|
||||||
|
</p>
|
||||||
|
<div className="product-percent-price">
|
||||||
|
-{Item.product_info.price_off || 0}%
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="product-price-main font-bold">
|
||||||
|
{Item.product_info.price > '0'
|
||||||
|
? `${formatCurrency(Item.product_info.price)}đ`
|
||||||
|
: 'Liên hệ'}
|
||||||
|
</div>
|
||||||
|
<div className="p-quantity-sale">
|
||||||
|
<i className="sprite sprite-fire-deal"></i>
|
||||||
|
<div className="bg-gradient"></div>
|
||||||
|
{(() => {
|
||||||
|
const percentRemaining =
|
||||||
|
((Number(Item.quantity) - Number(Item.sale_quantity)) /
|
||||||
|
Number(Item.quantity)) *
|
||||||
|
100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p
|
||||||
|
className="js-line-deal-left"
|
||||||
|
style={{ width: `${percentRemaining}%` }}
|
||||||
|
></p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
<span>
|
||||||
|
Còn {Number(Item.quantity) - Number(Item.sale_quantity)}/
|
||||||
|
{Number(Item.quantity)} sản phẩm
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="js-item-deal-time js-item-time-25404">
|
||||||
|
<div className="time-deal-page flex items-center">
|
||||||
|
<div>Kết thúc sau </div>
|
||||||
|
<CounDown deadline={new Date(Item.to_time)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="javascript:buyNow(25404)" className="buy-now-deal">
|
||||||
|
Mua giá sốc
|
||||||
|
</a>
|
||||||
|
<Link
|
||||||
|
href="/bts-gaming-02"
|
||||||
|
className="text-deal-item color-primary mt-3 hidden font-bold"
|
||||||
|
>
|
||||||
|
Xem sản phẩm
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Home from '@/components/Home';
|
import Home from '@/app/pages/Home';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|||||||
116
src/app/pages/Article/CategoryPage/index.tsx
Normal file
116
src/app/pages/Article/CategoryPage/index.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
'use client';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
import type { TypeArticleCatePage } from '@/types/article/TypeArticleCatePage';
|
||||||
|
import { ArticleCateDetailPageData } from '@/data/article/ArticleCateDetailPageData';
|
||||||
|
import { DataArticleCategory } from '@/data/article/ListCategory';
|
||||||
|
import { DataListArticleNews } from '@/data/article/ListArticleNews';
|
||||||
|
|
||||||
|
import { findCategoryBySlug } from '@/lib/article/category';
|
||||||
|
import { Breadcrumb } from '@components/Common/Breadcrumb';
|
||||||
|
import { ErrorLink } from '@components/Common/error';
|
||||||
|
import { ArticleTopLeft } from '../ArticleTopLeft';
|
||||||
|
import { ArticleTopRight } from '../ArticleTopRight';
|
||||||
|
import ItemArticle from '@/components/Common/ItemArticle';
|
||||||
|
|
||||||
|
interface CategoryPageProps {
|
||||||
|
slug: string; // khai báo prop slug
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArticleCategoryPage: React.FC<CategoryPageProps> = ({ slug }) => {
|
||||||
|
// Ép kiểu dữ liệu từ index.ts về CategoryData[]
|
||||||
|
const categories = ArticleCateDetailPageData as TypeArticleCatePage[];
|
||||||
|
const currentCategory = findCategoryBySlug(slug, categories);
|
||||||
|
|
||||||
|
const breadcrumbItems = [
|
||||||
|
{ name: 'Tin tức', url: '/tin-tuc' },
|
||||||
|
{ name: currentCategory?.category_info.name, url: currentCategory?.category_info.request_path },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Trường hợp không tìm thấy danh mục
|
||||||
|
if (!currentCategory) {
|
||||||
|
return <ErrorLink />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lấy danh sách tin tức
|
||||||
|
const articleList = Object.values(currentCategory.article_list);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container">
|
||||||
|
<Breadcrumb items={breadcrumbItems} />
|
||||||
|
</div>
|
||||||
|
<section className="page-article page-article-category container">
|
||||||
|
<div className="tabs-category-article flex items-center">
|
||||||
|
{DataArticleCategory.map((item, index) => (
|
||||||
|
<Link
|
||||||
|
href={item.url}
|
||||||
|
key={index}
|
||||||
|
className={`item-tab-article ${currentCategory.title === item.title ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
<h2 className="title-cate-article font-[400]">{item.title}</h2>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="box-article-home-top grid grid-cols-3 gap-3">
|
||||||
|
<div className="col-left-article border-box-article box-new-article boder-radius-10 col-span-2">
|
||||||
|
<ArticleTopLeft />
|
||||||
|
</div>
|
||||||
|
<ArticleTopRight />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="box-article-home-middle mt-5 grid grid-cols-3 gap-3">
|
||||||
|
<div className="box-article-tech col-left-article boder-radius-10 border-box-article col-span-2">
|
||||||
|
<p className="title-box-article font-[600]">{currentCategory.title}</p>
|
||||||
|
<div className="list-article-tech">
|
||||||
|
{articleList.slice(0, 9).map((item, index) => (
|
||||||
|
<ItemArticle item={item} key={index} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href="/tin-cong-nghe"
|
||||||
|
className="btn-article-col flex items-center justify-center gap-2 font-[500]"
|
||||||
|
>
|
||||||
|
Xem tất cả
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="col-right-article page-hompage flex-1">
|
||||||
|
<div className="box-article-global border-box-article boder-radius-10">
|
||||||
|
<p className="title-box-article font-bold">Tin nổi bật</p>
|
||||||
|
<div className="list-article-global flex flex-col gap-2">
|
||||||
|
{DataListArticleNews.slice(0, 5).map((item, index) => (
|
||||||
|
<div className="item-article flex gap-4" key={index}>
|
||||||
|
<Link href={item.url} className="img-article boder-radius-10 relative">
|
||||||
|
<Image
|
||||||
|
className="boder-radius-10"
|
||||||
|
src={item.image.original}
|
||||||
|
fill
|
||||||
|
alt={item.title}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<i className="sprite sprite-icon-play-video-detail icon-video-feature incon-play-youtube"></i>
|
||||||
|
<i className="sprite sprite-play-youtube incon-play-youtube"></i>
|
||||||
|
</Link>
|
||||||
|
<div className="content-article content-article-item flex flex-1 flex-col">
|
||||||
|
<Link href="/tuyen-dung-nhan-vien-ky-thuat-1-2" className="title-article">
|
||||||
|
<h3 className="line-clamp-2 font-[400]">{item.title}</h3>
|
||||||
|
</Link>
|
||||||
|
<p className="time-article flex items-center gap-2">
|
||||||
|
<i className="sprite sprite-clock-item-article"></i>
|
||||||
|
<span>{item.createDate}</span>
|
||||||
|
</p>
|
||||||
|
<p className="descreption-article line-clamp-2">{item.summary}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ArticleCategoryPage;
|
||||||
113
src/app/pages/Article/DetailPage/TocBox/index.tsx
Normal file
113
src/app/pages/Article/DetailPage/TocBox/index.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
'use client';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
type HeadingItem = {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
level: number;
|
||||||
|
children?: HeadingItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function convertToSlug(text: string) {
|
||||||
|
return text
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.replace(/đ/g, 'd')
|
||||||
|
.replace(/[^\w ]+/g, '')
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hàm xây dựng cây TOC từ danh sách heading
|
||||||
|
function buildTree(headings: HeadingItem[]): HeadingItem[] {
|
||||||
|
const root: HeadingItem[] = [];
|
||||||
|
const stack: HeadingItem[] = [];
|
||||||
|
|
||||||
|
headings.forEach((h) => {
|
||||||
|
const node = { ...h, children: [] };
|
||||||
|
|
||||||
|
while (stack.length && stack[stack.length - 1].level >= node.level) {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.length === 0) {
|
||||||
|
root.push(node);
|
||||||
|
} else {
|
||||||
|
stack[stack.length - 1].children!.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.push(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTree(nodes: HeadingItem[]) {
|
||||||
|
return (
|
||||||
|
<ol>
|
||||||
|
{nodes.map((n) => (
|
||||||
|
<li key={n.id}>
|
||||||
|
<a
|
||||||
|
href={`#${n.id}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const el = document.getElementById(n.id);
|
||||||
|
if (el) {
|
||||||
|
const y = el.getBoundingClientRect().top + window.scrollY - 120;
|
||||||
|
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
{n.text}
|
||||||
|
</a>
|
||||||
|
{n.children && n.children.length > 0 && renderTree(n.children)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TocBox({ htmlContent }: { htmlContent: string }) {
|
||||||
|
const { headingsTree, contentWithIds } = useMemo(() => {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(htmlContent, 'text/html');
|
||||||
|
const nodes = doc.querySelectorAll('h1,h2,h3,h4,h5,h6');
|
||||||
|
|
||||||
|
const flat: HeadingItem[] = Array.from(nodes).map((node) => {
|
||||||
|
const text = node.textContent || '';
|
||||||
|
const id = convertToSlug(text);
|
||||||
|
node.setAttribute('id', id);
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
text,
|
||||||
|
level: parseInt(node.tagName.substring(1)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
headingsTree: buildTree(flat),
|
||||||
|
contentWithIds: doc.body.innerHTML,
|
||||||
|
};
|
||||||
|
}, [htmlContent]);
|
||||||
|
|
||||||
|
if (!headingsTree.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="archor-text-group">
|
||||||
|
<div className="toc_title flex items-center justify-between gap-2">
|
||||||
|
<b className="text-fint-toc flex items-center text-base font-bold">
|
||||||
|
<span>Nội dung chính</span>
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
<div id="js-outp">{renderTree(headingsTree)}</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="box-article-detail-ct nd js_find"
|
||||||
|
dangerouslySetInnerHTML={{ __html: contentWithIds }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
src/app/pages/Article/DetailPage/index.tsx
Normal file
109
src/app/pages/Article/DetailPage/index.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
'use client';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
import type { TypeArticleDetailPage } from '@/types/article/TypeArticleDetailPage';
|
||||||
|
import { ArticleDetailPageData } from '@/data/article/ArticleDetailPageData';
|
||||||
|
import { DataArticleCategory } from '@/data/article/ListCategory';
|
||||||
|
import { ErrorLink } from '@components/Common/error';
|
||||||
|
|
||||||
|
import { findDetailBySlug } from '@/lib/article/detail';
|
||||||
|
import { Breadcrumb } from '@components/Common/Breadcrumb';
|
||||||
|
import TocBox from './TocBox';
|
||||||
|
|
||||||
|
interface DetailPageProps {
|
||||||
|
slug: string; // khai báo prop slug
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArticleDetailPage: React.FC<DetailPageProps> = ({ slug }) => {
|
||||||
|
// Ép kiểu dữ liệu từ index.ts về CategoryData[]
|
||||||
|
const details = ArticleDetailPageData as TypeArticleDetailPage[];
|
||||||
|
const page = findDetailBySlug(slug, details);
|
||||||
|
|
||||||
|
const breadcrumbItems = [
|
||||||
|
{ name: 'Tin tức', url: '/tin-tuc' },
|
||||||
|
{ name: page?.article_detail.title, url: page?.article_detail.url },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Trường hợp không tìm thấy danh mục
|
||||||
|
if (!page) {
|
||||||
|
return <ErrorLink />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lấy danh sách tin tức liên quan mới
|
||||||
|
const ListRelayNew = Object.values(page.article_other_same_category.new);
|
||||||
|
// lấy danh sách tin tức liên quan cũ
|
||||||
|
const ListRelayOld = Object.values(page.article_other_same_category.old);
|
||||||
|
|
||||||
|
const combinedList = [...ListRelayNew.slice(0, 6), ...ListRelayOld.slice(0, 6)];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="container">
|
||||||
|
<Breadcrumb items={breadcrumbItems} />
|
||||||
|
</div>
|
||||||
|
<section className="page-article box-article-detail container">
|
||||||
|
<div className="tabs-category-article flex items-center">
|
||||||
|
{DataArticleCategory.map((item, index) => (
|
||||||
|
<Link
|
||||||
|
href={item.url}
|
||||||
|
key={index}
|
||||||
|
className={`item-tab-article ${page?.article_detail.categoryInfo[0].id === item.id ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
<h2 className="title-cate-article font-[400]">{item.title}</h2>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="row article-detail-page mt-5">
|
||||||
|
<div className="col-md-8">
|
||||||
|
<div className="box-article-detail-title">
|
||||||
|
<h1 className="font-weight-700">{page.article_detail.title}</h1>
|
||||||
|
<div className="post__user border-bottom my-5 flex items-center gap-2">
|
||||||
|
<span className="author-name">{page.article_detail.author}</span>
|
||||||
|
<span className="post-time">{page.article_detail.createDate}</span>
|
||||||
|
</div>
|
||||||
|
{/* nội dung */}
|
||||||
|
<TocBox htmlContent={page.article_detail.content} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{page.article_other_same_category && (
|
||||||
|
<div className="col-md-4">
|
||||||
|
<div className="box-article-relay">
|
||||||
|
<p className="title-ar">
|
||||||
|
Bài viết <span>liên quan</span>
|
||||||
|
</p>
|
||||||
|
<div className="article-list list-article-relative flex flex-wrap gap-3">
|
||||||
|
{combinedList.map((item, index) => (
|
||||||
|
<div className="item-article d-flex flex-column gap-12" key={index}>
|
||||||
|
<Link href={item.url} className="img-article boder-radius-10">
|
||||||
|
<Image
|
||||||
|
className="boder-radius-10"
|
||||||
|
src={item.image.original}
|
||||||
|
fill
|
||||||
|
alt={item.title}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<div className="content-article flex-1">
|
||||||
|
<a href={item.url} className="title-article">
|
||||||
|
<h3 className="font-weight-400 line-clamp-2">{item.title}</h3>
|
||||||
|
</a>
|
||||||
|
<p className="time-article d-flex align-items-center gap-4">
|
||||||
|
<i className="sprite sprite-clock-item-article"></i>
|
||||||
|
<span>{item.createDate}</span>
|
||||||
|
</p>
|
||||||
|
<p className="descreption-article line-clamp-2">{item.summary}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ArticleDetailPage;
|
||||||
@@ -5,8 +5,8 @@ import Image from 'next/image';
|
|||||||
|
|
||||||
export const BoxArticleMid = () => {
|
export const BoxArticleMid = () => {
|
||||||
return (
|
return (
|
||||||
<div className="box-article-home-middle flex justify-between gap-2">
|
<div className="box-article-home-middle grid grid-cols-3 gap-2">
|
||||||
<div className="box-article-tech col-left-article boder-radius-10 border-box-article">
|
<div className="box-article-tech col-left-article boder-radius-10 border-box-article col-span-2">
|
||||||
<p className="title-box-article font-[600]">Tin công nghệ</p>
|
<p className="title-box-article font-[600]">Tin công nghệ</p>
|
||||||
<div className="list-article-tech">
|
<div className="list-article-tech">
|
||||||
{DataListArticleNews.slice(0, 9).map((item, index) => (
|
{DataListArticleNews.slice(0, 9).map((item, index) => (
|
||||||
@@ -4,11 +4,11 @@ import Link from 'next/link';
|
|||||||
import { Breadcrumb } from '@components/Common/Breadcrumb';
|
import { Breadcrumb } from '@components/Common/Breadcrumb';
|
||||||
import { DataArticleCategory } from '@/data/article/ListCategory';
|
import { DataArticleCategory } from '@/data/article/ListCategory';
|
||||||
|
|
||||||
import { ArticleTopLeft } from './HomeArticle/ArticleTopLeft';
|
import { ArticleTopLeft } from '../ArticleTopLeft';
|
||||||
import { ArticleTopRight } from './HomeArticle/ArticleTopRight';
|
import { ArticleTopRight } from '../ArticleTopRight';
|
||||||
import { BoxVideoArticle } from './HomeArticle/BoxVideoArticle';
|
import { BoxVideoArticle } from './BoxVideoArticle';
|
||||||
import { BoxArticleMid } from './HomeArticle/BoxArticleMid';
|
import { BoxArticleMid } from './BoxArticleMid';
|
||||||
import { BoxArticleReview } from './HomeArticle/BoxArticleReview';
|
import { BoxArticleReview } from './BoxArticleReview';
|
||||||
|
|
||||||
const ArticleHome = () => {
|
const ArticleHome = () => {
|
||||||
const breadcrumbItems = [{ name: 'Tin tức', url: '/tin-tuc' }];
|
const breadcrumbItems = [{ name: 'Tin tức', url: '/tin-tuc' }];
|
||||||
@@ -26,11 +26,9 @@ const ArticleHome = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="box-article-home-top flex gap-3">
|
<div className="box-article-home-top grid grid-cols-3 gap-3">
|
||||||
<div className="col-left-article border-box-article box-new-article boder-radius-10">
|
<div className="col-left-article border-box-article box-new-article boder-radius-10 col-span-2">
|
||||||
<div className="flex gap-12">
|
<ArticleTopLeft />
|
||||||
<ArticleTopLeft />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ArticleTopRight />
|
<ArticleTopRight />
|
||||||
</div>
|
</div>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { menuData } from '../../Other/Header/menuData';
|
import { menuData } from '../../../../components/Other/Header/menuData';
|
||||||
import ItemCategory from './ItemCategory';
|
import ItemCategory from './ItemCategory';
|
||||||
import { InfoCategory } from '@/types';
|
import { InfoCategory } from '@/types';
|
||||||
|
|
||||||
@@ -5,9 +5,9 @@ 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 { ListDealData } from '@/data/deal';
|
||||||
|
|
||||||
import CounDown from '../../Common/CounDown';
|
import CounDown from '../../../../components/Common/CounDown';
|
||||||
import ProductItem from './ProductItem';
|
import ProductItem from './ProductItem';
|
||||||
|
|
||||||
const BoxProductDeal: React.FC = () => {
|
const BoxProductDeal: React.FC = () => {
|
||||||
@@ -34,7 +34,7 @@ const BoxProductDeal: React.FC = () => {
|
|||||||
loop={true}
|
loop={true}
|
||||||
navigation={true}
|
navigation={true}
|
||||||
>
|
>
|
||||||
{productDealData.map((Item, index) => (
|
{ListDealData.map((Item, index) => (
|
||||||
<SwiperSlide key={index}>
|
<SwiperSlide key={index}>
|
||||||
<ProductItem item={Item} />
|
<ProductItem item={Item} />
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
0
src/app/pages/Product/ProductSearch/index.tsx
Normal file
0
src/app/pages/Product/ProductSearch/index.tsx
Normal file
@@ -31,13 +31,13 @@ export const Breadcrumb = ({ items }: { items: BreadcrumbItem[] }) => {
|
|||||||
itemProp="itemListElement"
|
itemProp="itemListElement"
|
||||||
itemScope
|
itemScope
|
||||||
itemType="http://schema.org/ListItem"
|
itemType="http://schema.org/ListItem"
|
||||||
className="flex items-center"
|
className="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Link href={item.url ?? '/'} itemProp="item">
|
<Link href={item.url ?? '/'} itemProp="item">
|
||||||
<span itemProp="name">{item?.name}</span>
|
<span itemProp="name">{item?.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
{idx < items.length - 1 && <FaAngleRight className="text-gray-700" />}
|
||||||
<meta itemProp="position" content={(idx + 1).toString()} />
|
<meta itemProp="position" content={(idx + 1).toString()} />
|
||||||
{idx < items.length - 1 && <span className="mx-1">/</span>}
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ol>
|
</ol>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Article } from '@/types';
|
import { Article } from '@/types';
|
||||||
|
import { ArticleItem } from '@/types/article/TypeArticleCatePage';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
type ItemArticleProps = {
|
type ItemArticleProps = {
|
||||||
item: Article;
|
item: Article | ArticleItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ItemArticle: React.FC<ItemArticleProps> = ({ item }) => {
|
const ItemArticle: React.FC<ItemArticleProps> = ({ item }) => {
|
||||||
|
|||||||
2124
src/data/article/ArticleCateDetailPageData.ts
Normal file
2124
src/data/article/ArticleCateDetailPageData.ts
Normal file
File diff suppressed because it is too large
Load Diff
3429
src/data/article/ArticleDetailPageData.ts
Normal file
3429
src/data/article/ArticleDetailPageData.ts
Normal file
File diff suppressed because one or more lines are too long
@@ -1,187 +1,186 @@
|
|||||||
|
import { TypeArticleCategory } from '@/types/article/ListCategoryArticle';
|
||||||
import { TypeArticleCategory } from '@/types/article/ListCategoryArticle'
|
|
||||||
|
|
||||||
export const DataArticleCategory: TypeArticleCategory[] = [
|
export const DataArticleCategory: TypeArticleCategory[] = [
|
||||||
{
|
{
|
||||||
"id": "243",
|
id: '243',
|
||||||
"title": "C\u00f4ng ngh\u1ec7",
|
title: 'C\u00f4ng ngh\u1ec7',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/tin-cong-nghe",
|
url: '\/tin-cong-nghe',
|
||||||
"item_count": "2787",
|
item_count: '2787',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2490",
|
id: '2490',
|
||||||
"title": "Review",
|
title: 'Review',
|
||||||
"summary": "0",
|
summary: '0',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/tin-tuc-review",
|
url: '\/tin-tuc-review',
|
||||||
"item_count": "1235",
|
item_count: '1235',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2491",
|
id: '2491',
|
||||||
"title": "H\u01b0\u1edbng d\u1eabn",
|
title: 'H\u01b0\u1edbng d\u1eabn',
|
||||||
"summary": "0",
|
summary: '0',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "1",
|
isParent: '1',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/tin-tuc-huong-dan",
|
url: '\/tin-tuc-huong-dan',
|
||||||
"item_count": "1828",
|
item_count: '1828',
|
||||||
"children": [
|
children: [
|
||||||
{
|
{
|
||||||
"id": "2493",
|
id: '2493',
|
||||||
"title": "Ki\u1ebfn th\u1ee9c m\u00e1y t\u00ednh",
|
title: 'Ki\u1ebfn th\u1ee9c m\u00e1y t\u00ednh',
|
||||||
"summary": "0",
|
summary: '0',
|
||||||
"parentId": "2491",
|
parentId: '2491',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/kien-thuc-may-tinh",
|
url: '\/kien-thuc-may-tinh',
|
||||||
"item_count": "1558",
|
item_count: '1558',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2494",
|
id: '2494',
|
||||||
"title": "Ph\u1ea7n m\u1ec1m \u0111\u1ed3 h\u1ecda",
|
title: 'Ph\u1ea7n m\u1ec1m \u0111\u1ed3 h\u1ecda',
|
||||||
"summary": "0",
|
summary: '0',
|
||||||
"parentId": "2491",
|
parentId: '2491',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/phan-mem-do-hoa",
|
url: '\/phan-mem-do-hoa',
|
||||||
"item_count": "174",
|
item_count: '174',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2495",
|
id: '2495',
|
||||||
"title": "Ph\u1ea7n m\u1ec1m v\u0103n ph\u00f2ng",
|
title: 'Ph\u1ea7n m\u1ec1m v\u0103n ph\u00f2ng',
|
||||||
"summary": "0",
|
summary: '0',
|
||||||
"parentId": "2491",
|
parentId: '2491',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/phan-mem-van-phong",
|
url: '\/phan-mem-van-phong',
|
||||||
"item_count": "71",
|
item_count: '71',
|
||||||
"children": []
|
children: [],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "263",
|
id: '263',
|
||||||
"title": "Tuy\u1ec3n d\u1ee5ng",
|
title: 'Tuy\u1ec3n d\u1ee5ng',
|
||||||
"summary": "0",
|
summary: '0',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/tuyen-dung",
|
url: '\/tuyen-dung',
|
||||||
"item_count": "19",
|
item_count: '19',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2488",
|
id: '2488',
|
||||||
"title": "Tin t\u1ee9c khuy\u1ebfn m\u1ea1i",
|
title: 'Tin t\u1ee9c khuy\u1ebfn m\u1ea1i',
|
||||||
"summary": "0",
|
summary: '0',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "0",
|
thumbnail: '0',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/tin-tuc-khuyen-mai",
|
url: '\/tin-tuc-khuyen-mai',
|
||||||
"item_count": "90",
|
item_count: '90',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2497",
|
id: '2497',
|
||||||
"title": "Tin t\u1ee9c build PC",
|
title: 'Tin t\u1ee9c build PC',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "",
|
thumbnail: '',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/tin-tuc-build-pc",
|
url: '\/tin-tuc-build-pc',
|
||||||
"item_count": "36",
|
item_count: '36',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2505",
|
id: '2505',
|
||||||
"title": "Game",
|
title: 'Game',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "",
|
thumbnail: '',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/game",
|
url: '\/game',
|
||||||
"item_count": "16",
|
item_count: '16',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2501",
|
id: '2501',
|
||||||
"title": "S\u1ef1 ki\u1ec7n",
|
title: 'S\u1ef1 ki\u1ec7n',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "0",
|
parentId: '0',
|
||||||
"isParent": "1",
|
isParent: '1',
|
||||||
"thumbnail": "",
|
thumbnail: '',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/su-kien",
|
url: '\/su-kien',
|
||||||
"item_count": "65",
|
item_count: '65',
|
||||||
"children": [
|
children: [
|
||||||
{
|
{
|
||||||
"id": "2504",
|
id: '2504',
|
||||||
"title": "Chung",
|
title: 'Chung',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "2501",
|
parentId: '2501',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "",
|
thumbnail: '',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/chung",
|
url: '\/chung',
|
||||||
"item_count": "30",
|
item_count: '30',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2500",
|
id: '2500',
|
||||||
"title": "COMPUTEX 2025",
|
title: 'COMPUTEX 2025',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "2501",
|
parentId: '2501',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "",
|
thumbnail: '',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/computex-2025",
|
url: '\/computex-2025',
|
||||||
"item_count": "16",
|
item_count: '16',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2502",
|
id: '2502',
|
||||||
"title": "Ng\u00e0y h\u1ed9i tuy\u1ec3n sinh 2025",
|
title: 'Ng\u00e0y h\u1ed9i tuy\u1ec3n sinh 2025',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "2501",
|
parentId: '2501',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "",
|
thumbnail: '',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/ngay-hoi-tuyen-sinh-2025",
|
url: '\/ngay-hoi-tuyen-sinh-2025',
|
||||||
"item_count": "6",
|
item_count: '6',
|
||||||
"children": []
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2503",
|
id: '2503',
|
||||||
"title": "Ch\u00e0o t\u00e2n sinh vi\u00ean",
|
title: 'Ch\u00e0o t\u00e2n sinh vi\u00ean',
|
||||||
"summary": "",
|
summary: '',
|
||||||
"parentId": "2501",
|
parentId: '2501',
|
||||||
"isParent": "0",
|
isParent: '0',
|
||||||
"thumbnail": "",
|
thumbnail: '',
|
||||||
"type": "article",
|
type: 'article',
|
||||||
"url": "\/chao-tan-sinh-vien",
|
url: '\/chao-tan-sinh-vien',
|
||||||
"item_count": "15",
|
item_count: '15',
|
||||||
"children": []
|
children: [],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
]
|
];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TypeListProductDeal } from '@/types';
|
import { TypeListProductDeal } from '@/types';
|
||||||
|
|
||||||
export const productDealData: TypeListProductDeal = [
|
export const ListDealData: TypeListProductDeal = [
|
||||||
{
|
{
|
||||||
id: '565',
|
id: '565',
|
||||||
pro_id: '25404',
|
pro_id: '25404',
|
||||||
18
src/lib/article/category/index.ts
Normal file
18
src/lib/article/category/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { TypeArticleCatePage } from '@/types/article/TypeArticleCatePage';
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tìm danh mục theo mảng slug (ví dụ: ["pc-gaming","cao-cap","rtx-4090"])
|
||||||
|
*/
|
||||||
|
export function findCategoryBySlug(
|
||||||
|
slug: string,
|
||||||
|
categories: TypeArticleCatePage[],
|
||||||
|
): TypeArticleCatePage | null {
|
||||||
|
const found = categories.find((item) => item.category_info.request_path == slug);
|
||||||
|
return found ?? null;
|
||||||
|
}
|
||||||
18
src/lib/article/detail/index.tsx
Normal file
18
src/lib/article/detail/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { TypeArticleDetailPage } from '@/types/article/TypeArticleDetailPage';
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tìm danh mục theo mảng slug (ví dụ: ["pc-gaming","cao-cap","rtx-4090"])
|
||||||
|
*/
|
||||||
|
export function findDetailBySlug(
|
||||||
|
slug: string,
|
||||||
|
categories: TypeArticleDetailPage[],
|
||||||
|
): TypeArticleDetailPage | null {
|
||||||
|
const found = categories.find((item) => item.article_detail.url == slug);
|
||||||
|
return found ?? null;
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { productCategoryData } from '@/data/product/category';
|
import { productCategoryData } from '@/data/product/category';
|
||||||
import { productDetailData } from '@/data/product/detail';
|
import { productDetailData } from '@/data/product/detail';
|
||||||
|
import { ArticleCateDetailPageData } from '@/data/article/ArticleCateDetailPageData';
|
||||||
|
import { ArticleDetailPageData } from '@/data/article/ArticleDetailPageData';
|
||||||
|
|
||||||
export function resolvePageType(slug: string) {
|
export function resolvePageType(slug: string) {
|
||||||
// kiểm tra danh mục
|
// kiểm tra danh mục
|
||||||
@@ -12,7 +14,17 @@ export function resolvePageType(slug: string) {
|
|||||||
}
|
}
|
||||||
// tin tức
|
// tin tức
|
||||||
if ('/tin-tuc' == slug) {
|
if ('/tin-tuc' == slug) {
|
||||||
return 'article-home'
|
return 'article-home';
|
||||||
}
|
}
|
||||||
|
// danh mục tin tức
|
||||||
|
if (ArticleCateDetailPageData.some((c) => c.category_info.request_path == slug)) {
|
||||||
|
return 'article-category';
|
||||||
|
}
|
||||||
|
|
||||||
|
// chi tiết tin tức
|
||||||
|
if (ArticleDetailPageData.some((c) => c.article_detail.url == slug)) {
|
||||||
|
return 'article-detail';
|
||||||
|
}
|
||||||
|
|
||||||
return '404';
|
return '404';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2730,6 +2730,7 @@ textarea::placeholder {
|
|||||||
left: 12px;
|
left: 12px;
|
||||||
text-shadow: 1px 1px 1px #000;
|
text-shadow: 1px 1px 1px #000;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
.page-hompage .box-article-global .box-left .content-article-item .title {
|
.page-hompage .box-article-global .box-left .content-article-item .title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@@ -4380,7 +4381,6 @@ textarea::placeholder {
|
|||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
.page-deal .box-list-item-deal .product-item {
|
.page-deal .box-list-item-deal .product-item {
|
||||||
width: calc(100% / 4 - 12px);
|
|
||||||
}
|
}
|
||||||
.page-deal .box-list-item-deal .product-item .icon-cart-deal {
|
.page-deal .box-list-item-deal .product-item .icon-cart-deal {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
@@ -4409,10 +4409,10 @@ textarea::placeholder {
|
|||||||
}
|
}
|
||||||
.page-deal .box-list-item-deal .product-item:first-child,
|
.page-deal .box-list-item-deal .product-item:first-child,
|
||||||
.page-deal .box-list-item-deal .product-item:nth-child(2) {
|
.page-deal .box-list-item-deal .product-item:nth-child(2) {
|
||||||
width: calc(100% / 2 - 6px);
|
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
background: #0f5b9a;
|
background: #0f5b9a;
|
||||||
padding: 10px 8px 28px;
|
padding: 10px 8px 28px;
|
||||||
|
grid-column: span 2 / span 2;
|
||||||
}
|
}
|
||||||
.page-deal .box-list-item-deal .product-item:first-child .item-deal,
|
.page-deal .box-list-item-deal .product-item:first-child .item-deal,
|
||||||
.page-deal .box-list-item-deal .product-item:nth-child(2) .item-deal {
|
.page-deal .box-list-item-deal .product-item:nth-child(2) .item-deal {
|
||||||
@@ -4881,9 +4881,6 @@ textarea::placeholder {
|
|||||||
background-size: 155px 131.5px !important;
|
background-size: 155px 131.5px !important;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
.col-left-article {
|
|
||||||
width: 66.5%;
|
|
||||||
}
|
|
||||||
.page-article .box-article-home-top .swiper-pagination {
|
.page-article .box-article-home-top .swiper-pagination {
|
||||||
position: relative;
|
position: relative;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -5207,7 +5204,6 @@ textarea::placeholder {
|
|||||||
}
|
}
|
||||||
.box-article-detail .box-article-detail-ct {
|
.box-article-detail .box-article-detail-ct {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
float: left;
|
|
||||||
margin-bottom: 50px;
|
margin-bottom: 50px;
|
||||||
}
|
}
|
||||||
.box-article-detail img:hover {
|
.box-article-detail img:hover {
|
||||||
@@ -5248,7 +5244,6 @@ textarea::placeholder {
|
|||||||
}
|
}
|
||||||
.box-article-detail .title-ar {
|
.box-article-detail .title-ar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
float: left;
|
|
||||||
border-bottom: solid 1px #e1e1e1;
|
border-bottom: solid 1px #e1e1e1;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@@ -6050,3 +6045,52 @@ textarea::placeholder {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.box-article-detail .archor-text-group {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e1e4e6;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.box-article-detail .toc_title {
|
||||||
|
padding: 16px 14px 12px;
|
||||||
|
border-bottom: 1px solid #e1e4e6;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 28px;
|
||||||
|
color: #32373d;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.box-article-detail #js-outp ol li {
|
||||||
|
list-style: none !important;
|
||||||
|
line-height: 27px;
|
||||||
|
}
|
||||||
|
.box-article-detail #js-outp ol {
|
||||||
|
padding-left: 20px !important;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.box-article-detail #js-outp ol ol {
|
||||||
|
padding-left: 18px !important;
|
||||||
|
margin: 3px 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.box-article-detail #js-outp a {
|
||||||
|
color: #444b52;
|
||||||
|
font-size: 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.box-article-detail #js-outp a:hover {
|
||||||
|
color: #0664f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-article-detail .box-article-detail-ct {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
.box-article-detail .box-article-detail-cmt {
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.box-article-detail .box-article-detail-cmt iframe {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|||||||
94
src/types/article/TypeArticleCatePage.ts
Normal file
94
src/types/article/TypeArticleCatePage.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// Kiểu cho từng bài viết
|
||||||
|
export interface ArticleItem {
|
||||||
|
id: string | number;
|
||||||
|
title: string;
|
||||||
|
extend: {
|
||||||
|
pixel_code: string;
|
||||||
|
};
|
||||||
|
summary: string;
|
||||||
|
createDate: string;
|
||||||
|
createBy: string;
|
||||||
|
lastUpdate: string;
|
||||||
|
lastUpdateBy: string;
|
||||||
|
visit: string;
|
||||||
|
is_featured: string;
|
||||||
|
article_time: string;
|
||||||
|
review_rate: string;
|
||||||
|
review_count: string;
|
||||||
|
video_code: string;
|
||||||
|
external_url: string;
|
||||||
|
author: string;
|
||||||
|
counter: string;
|
||||||
|
url: string;
|
||||||
|
image: {
|
||||||
|
thum: string;
|
||||||
|
original: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểu cho thông tin đường dẫn
|
||||||
|
interface PathItem {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Path {
|
||||||
|
path: PathItem[];
|
||||||
|
path_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểu cho category_info
|
||||||
|
interface Info {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
catPath: string;
|
||||||
|
childListId: string;
|
||||||
|
sellerId: string;
|
||||||
|
url: string;
|
||||||
|
url_hash: string;
|
||||||
|
name: string;
|
||||||
|
summary: string;
|
||||||
|
description: string;
|
||||||
|
isParent: string;
|
||||||
|
imgUrl: string;
|
||||||
|
parentId: string;
|
||||||
|
status: string;
|
||||||
|
ordering: string;
|
||||||
|
item_count: string;
|
||||||
|
display_option: string;
|
||||||
|
createDate: string;
|
||||||
|
createBy: string;
|
||||||
|
lastUpdate: string;
|
||||||
|
lastUpdateBy: string;
|
||||||
|
meta_title: string;
|
||||||
|
meta_keyword: string;
|
||||||
|
meta_description: string;
|
||||||
|
request_path: string;
|
||||||
|
relate_product: string;
|
||||||
|
visit: string;
|
||||||
|
path: Path;
|
||||||
|
related: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểu cho phân trang
|
||||||
|
interface PagingItem {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
is_active: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểu tổng thể cho dữ liệu
|
||||||
|
export interface TypeArticleCatePage {
|
||||||
|
keywords: string;
|
||||||
|
description: string;
|
||||||
|
title: string;
|
||||||
|
favicon: string;
|
||||||
|
canonical: string;
|
||||||
|
image: string;
|
||||||
|
category_info: Info;
|
||||||
|
paging_collection: PagingItem[];
|
||||||
|
paging: string;
|
||||||
|
paging_count: string;
|
||||||
|
article_list: Record<string, ArticleItem>;
|
||||||
|
}
|
||||||
125
src/types/article/TypeArticleDetailPage.ts
Normal file
125
src/types/article/TypeArticleDetailPage.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
// Kiểu cho hình ảnh
|
||||||
|
interface ImageInfo {
|
||||||
|
thum: string;
|
||||||
|
original: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểu cho category
|
||||||
|
interface CategoryInfo {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
catPath: string;
|
||||||
|
childListId: string;
|
||||||
|
sellerId: string;
|
||||||
|
url: string;
|
||||||
|
url_hash: string;
|
||||||
|
name: string;
|
||||||
|
summary: string;
|
||||||
|
description: string;
|
||||||
|
isParent: string;
|
||||||
|
imgUrl: string;
|
||||||
|
parentId: string;
|
||||||
|
status: string;
|
||||||
|
ordering: string;
|
||||||
|
item_count: string;
|
||||||
|
display_option: string;
|
||||||
|
createDate: string;
|
||||||
|
createBy: string;
|
||||||
|
lastUpdate: string;
|
||||||
|
lastUpdateBy: string;
|
||||||
|
meta_title: string;
|
||||||
|
meta_keyword: string;
|
||||||
|
meta_description: string;
|
||||||
|
request_path: string;
|
||||||
|
relate_product: string;
|
||||||
|
visit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểu cho đường dẫn bài viết
|
||||||
|
interface ArticlePath {
|
||||||
|
path: { id: string; url: string; name: string }[];
|
||||||
|
path_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImageItem {
|
||||||
|
caption: string;
|
||||||
|
file_location: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chi tiết bài viết
|
||||||
|
interface ArticleDetail {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
type: string;
|
||||||
|
content: string;
|
||||||
|
article_category: string[];
|
||||||
|
image_background: string;
|
||||||
|
extend: { pixel_code: string };
|
||||||
|
meta_title: string;
|
||||||
|
meta_keywords: string;
|
||||||
|
meta_description: string;
|
||||||
|
album_id: string;
|
||||||
|
url: string;
|
||||||
|
video_code: string;
|
||||||
|
external_url: string;
|
||||||
|
allow_se_index: string;
|
||||||
|
url_canonical: string;
|
||||||
|
tags: string;
|
||||||
|
review_rate: string;
|
||||||
|
review_count: string;
|
||||||
|
article_time: string;
|
||||||
|
relate_article: string;
|
||||||
|
visit: string;
|
||||||
|
createDate: string;
|
||||||
|
lastUpdate: string;
|
||||||
|
author: string;
|
||||||
|
image: ImageInfo;
|
||||||
|
image_list: ImageItem[];
|
||||||
|
categoryInfo: CategoryInfo[];
|
||||||
|
articlePath: ArticlePath[];
|
||||||
|
related: [];
|
||||||
|
tag_list: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bài viết khác cùng chuyên mục
|
||||||
|
interface OtherArticle {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
extend: { pixel_code: string };
|
||||||
|
summary: string;
|
||||||
|
createDate: string;
|
||||||
|
createBy: string;
|
||||||
|
lastUpdate: string;
|
||||||
|
lastUpdateBy: string;
|
||||||
|
visit: string;
|
||||||
|
is_featured: string;
|
||||||
|
lastUpdateByUser: string;
|
||||||
|
article_time: string;
|
||||||
|
review_rate: string;
|
||||||
|
review_count: string;
|
||||||
|
video_code: string;
|
||||||
|
external_url: string;
|
||||||
|
author: string;
|
||||||
|
counter: string;
|
||||||
|
url: string;
|
||||||
|
image: ImageInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArticleOtherSameCategory {
|
||||||
|
new: Record<string, OtherArticle>;
|
||||||
|
old: Record<string, OtherArticle>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root type
|
||||||
|
export interface TypeArticleDetailPage {
|
||||||
|
keywords: string;
|
||||||
|
description: string;
|
||||||
|
title: string;
|
||||||
|
canonical: string;
|
||||||
|
image: string;
|
||||||
|
article_detail: ArticleDetail;
|
||||||
|
article_other_same_category: ArticleOtherSameCategory;
|
||||||
|
comment_list: [];
|
||||||
|
article_album: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user