新增文章
This commit is contained in:
parent
0df3237d21
commit
cc27475304
@ -16,7 +16,7 @@ const nextConfig = {
|
||||
pathname: "/_next/image/**",
|
||||
},
|
||||
],
|
||||
domains: ['www.typeframes.com'], // 允许从该域名加载图片资源
|
||||
domains: ['www.typeframes.com', 'www.typeframes.ai', 'www.typeframes.com.cn', 'www.typeframes.cc', 'static.wixstatic.com', 'images.pexels.com'], // 允许从该域名加载图片资源
|
||||
|
||||
},
|
||||
sassOptions: {
|
||||
|
@ -18,4 +18,15 @@ export const GetVideoList = () => {
|
||||
}
|
||||
export const GetMetadata = () => {
|
||||
return request.get<any>('/website/');
|
||||
}
|
||||
export const GetPostList = (params) => {
|
||||
return request.get<any>(`/article-list/`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
export const GetPost = (id, lang) => {
|
||||
return request.get<any>(`/article/${id}/${lang}`);
|
||||
}
|
||||
export const GetRandomPosts = () => {
|
||||
return request.get<any>('/random_articles/');
|
||||
}
|
7
src/app/(main)/(article)/article-list/[page]/loading.tsx
Normal file
7
src/app/(main)/(article)/article-list/[page]/loading.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="z-0 relative w-full h-screen flex justify-center items-center">
|
||||
<span className="loading loading-spinner loading-lg text-secondary"></span>
|
||||
</div>
|
||||
);
|
||||
}
|
153
src/app/(main)/(article)/article-list/[page]/page.tsx
Normal file
153
src/app/(main)/(article)/article-list/[page]/page.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import { GetPostList } from "@/apis/auth";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function ArticleList({ params, searchParams }) {
|
||||
const page = parseInt(params.page, 10) || 1; // 获取当前页码
|
||||
const locale = await getLocale();
|
||||
const limit = 9; // 每页文章数
|
||||
const tag = searchParams.tag || ""; // 获取 URL 中的 tag 参数
|
||||
|
||||
interface Article {
|
||||
id: number;
|
||||
title: string;
|
||||
keywords: string;
|
||||
image_url: string;
|
||||
published_at: string;
|
||||
}
|
||||
|
||||
let list: Article[] = [];
|
||||
let totalPages = 1;
|
||||
let keywords = [];
|
||||
|
||||
try {
|
||||
const data = await GetPostList({
|
||||
t: new Date().getTime(),
|
||||
page,
|
||||
limit,
|
||||
tag, // 如果有 tag,带上 tag 参数
|
||||
});
|
||||
if (data.code === 200) {
|
||||
list = data.data.articles;
|
||||
totalPages = Math.ceil(data.data.count / limit); // 计算总页数
|
||||
keywords = data.data[`${locale}_keywords`];
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
{/* 关键词筛选部分 */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-xl font-bold text-white mb-4">
|
||||
{" "}
|
||||
{locale === "en" ? "Filtering tags" : "筛选标签"}:
|
||||
</h3>
|
||||
<div className="flex flex-wrap">
|
||||
{/* 全部标签 */}
|
||||
<Link
|
||||
href={`/article-list/1`}
|
||||
className={`mr-4 mb-2 px-4 py-1 rounded text-sm ${
|
||||
!tag
|
||||
? "bg-secondary text-white"
|
||||
: "bg-gray-800 text-gray-300 hover:bg-gray-700"
|
||||
}`}
|
||||
>
|
||||
{locale === "en" ? "all" : "全部"}
|
||||
</Link>
|
||||
{keywords.map((keyword, index) => (
|
||||
<Link
|
||||
href={`/article-list/1?tag=${keyword}`}
|
||||
key={index}
|
||||
className={`mr-4 mb-2 px-4 py-1 rounded text-sm ${
|
||||
tag === keyword
|
||||
? "bg-secondary text-white"
|
||||
: "bg-gray-800 text-gray-300 hover:bg-gray-700"
|
||||
}`}
|
||||
>
|
||||
#{keyword}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 文章列表部分 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{list.map((item, index) => (
|
||||
<div
|
||||
className="bg-[#15171a] rounded-lg overflow-hidden shadow-lg"
|
||||
key={index}
|
||||
>
|
||||
<Link href={`/article/${item.id}`}>
|
||||
<Image
|
||||
src={item["image_url"]}
|
||||
width={1920}
|
||||
height={1080}
|
||||
alt={item[`${locale}_keywords`].join()}
|
||||
className="w-full h-48 object-cover"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<div className="p-6">
|
||||
<Link
|
||||
href={`/article/${item.id}`}
|
||||
className="text-white hover:text-secondary hover:underline"
|
||||
>
|
||||
<h2 className="text-xl font-bold mb-2">
|
||||
{item[`${locale}_title`]}
|
||||
</h2>
|
||||
</Link>
|
||||
<p className="text-gray-400 text-sm mb-4">
|
||||
{new Date(
|
||||
item["published_at"]
|
||||
).toLocaleDateString(locale, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</p>
|
||||
<p className="text-gray-300 mb-6">
|
||||
{item[`${locale}_keywords`].map(
|
||||
(kitem, kindex) => (
|
||||
<Link
|
||||
href={`/article-list/1?tag=${kitem}`}
|
||||
key={kindex}
|
||||
className="mr-2 text-sm hover:underline hover:text-secondary"
|
||||
>
|
||||
#{kitem}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
<Link
|
||||
href={`/article/${item.id}`}
|
||||
className="text-secondary hover:underline"
|
||||
>
|
||||
Read More
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 分页按钮 */}
|
||||
<div className="join mt-8 flex justify-center">
|
||||
{Array.from({ length: totalPages }, (_, index) => (
|
||||
<Link
|
||||
href={`/article-list/${index + 1}${
|
||||
tag ? `?tag=${tag}` : ""
|
||||
}`}
|
||||
key={index}
|
||||
className={`join-item btn btn-md ${
|
||||
page === index + 1
|
||||
? "bg-secondary text-white"
|
||||
: "bg-gray-800 text-gray-300 hover:bg-gray-700"
|
||||
}`}
|
||||
>
|
||||
{index + 1}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
11
src/app/(main)/(article)/article-list/page.tsx
Normal file
11
src/app/(main)/(article)/article-list/page.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function Page({ params }) {
|
||||
const { page } = params;
|
||||
|
||||
if (!page) {
|
||||
// 如果page参数不存在,重定向到第一页
|
||||
redirect("/article-list/1");
|
||||
}
|
||||
return <></>;
|
||||
}
|
159
src/app/(main)/(article)/article/[id]/page.tsx
Normal file
159
src/app/(main)/(article)/article/[id]/page.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import { GetPost, GetRandomPosts } from "@/apis/auth";
|
||||
import PageRemark from "@/ui/page/page-remark";
|
||||
import { Metadata } from "next";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "",
|
||||
keywords: "",
|
||||
};
|
||||
|
||||
export default async function Article({ params }) {
|
||||
const postID = params.id;
|
||||
const locale = await getLocale();
|
||||
let postContent = "";
|
||||
let title = "";
|
||||
let keyword = "";
|
||||
let time = "";
|
||||
let imgUrl = "";
|
||||
let randomPosts: any = [];
|
||||
|
||||
try {
|
||||
const data = await GetPost(postID, locale);
|
||||
if (data.code === 200) {
|
||||
postContent = data.data.content;
|
||||
title = data.data.title;
|
||||
keyword = data.data.keywords;
|
||||
time = data.data.published_at;
|
||||
imgUrl = data.data.image_url;
|
||||
console.log("imgUrl", imgUrl);
|
||||
metadata.title = title;
|
||||
metadata.keywords = keyword;
|
||||
}
|
||||
|
||||
// 获取随机文章推荐
|
||||
const randomData = await GetRandomPosts(); // 假设返回3篇随机文章
|
||||
if (randomData.code === 200) {
|
||||
randomPosts = randomData.data.articles;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return (
|
||||
<>
|
||||
<article className="rounded-lg px-4 sm:px-6 md:px-8 border border-[#252629] w-full bg-[#15171a] text-white ">
|
||||
<div className="relative z-10 mx-auto py-12 lg:py-24">
|
||||
<div className="space-y-4 mb-14">
|
||||
<h2 className='font-["Euclid_Circular_A"] text-3xl lg:text-4xl xl:text-5xl text-center'>
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Image
|
||||
src={imgUrl}
|
||||
alt={keyword}
|
||||
width={1920}
|
||||
height={1080}
|
||||
className="w-[90%] lg:w-[85%] xl:w-[75%] mx-auto my-4"
|
||||
/>
|
||||
|
||||
<article className="w-[90%] lg:w-[85%] xl:w-[75%] mx-auto space-y-6">
|
||||
<section>
|
||||
<div
|
||||
className="whitespace-pre-line indent-[2em] leading-relaxed text-lg text-gray-200"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: postContent.replace(
|
||||
/<p>/g,
|
||||
'<p class="mb-4">'
|
||||
),
|
||||
}}
|
||||
></div>
|
||||
</section>
|
||||
</article>
|
||||
<div className="w-[90%] lg:w-[85%] xl:w-[75%] mx-auto mt-10 text-gray-400">
|
||||
<div>
|
||||
{keyword.split(",").map((item, index) => {
|
||||
return (
|
||||
<Link
|
||||
href={`/article-list/1?tag=${item}`}
|
||||
key={index}
|
||||
className=" underline mr-4"
|
||||
>
|
||||
#{item}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>{time}</div>
|
||||
</div>
|
||||
<PageRemark />
|
||||
|
||||
{/* 随机文章推荐 */}
|
||||
<div className="w-[90%] lg:w-[85%] xl:w-[75%] mx-auto mt-14">
|
||||
<h3 className="text-2xl font-bold mb-6 text-white">
|
||||
{locale === "en" ? "Related Articles" : "相关文章"}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{randomPosts.map((item, index) => (
|
||||
<div
|
||||
className="bg-[#15171a] rounded-lg overflow-hidden shadow-lg"
|
||||
key={index}
|
||||
>
|
||||
<Link href={`/article/${item.id}`}>
|
||||
<Image
|
||||
src={item["image_url"]}
|
||||
width={1920}
|
||||
height={1080}
|
||||
alt={item["keywords"]}
|
||||
className="w-full h-48 object-cover"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<div className="p-6">
|
||||
<Link
|
||||
href={`/article/${item.id}`}
|
||||
className="text-white hover:text-secondary hover:underline"
|
||||
>
|
||||
<h2 className="text-xl font-bold mb-2">
|
||||
{item[`${locale}_title`]}
|
||||
</h2>
|
||||
</Link>
|
||||
<p className="text-gray-400 text-sm mb-4">
|
||||
{new Date(
|
||||
item["published_at"]
|
||||
).toLocaleDateString(locale, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
</p>
|
||||
<p className="text-gray-300 mb-6">
|
||||
{item[`${locale}_keywords`].map(
|
||||
(kitem, kindex) => (
|
||||
<Link
|
||||
href={`/article-list/1?tag=${kitem}`}
|
||||
key={kindex}
|
||||
className="mr-2 text-sm hover:underline hover:text-secondary"
|
||||
>
|
||||
#{kitem}
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
<Link
|
||||
href={`/article/${item.id}`}
|
||||
className="text-secondary hover:underline"
|
||||
>
|
||||
Read More
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
7
src/app/(main)/(article)/loading.tsx
Normal file
7
src/app/(main)/(article)/loading.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="z-0 relative w-full h-screen flex justify-center items-center">
|
||||
<span className="loading loading-spinner loading-lg text-secondary"></span>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -10,7 +10,7 @@ export default function MainLayout({
|
||||
<>
|
||||
<main
|
||||
data-theme="typeframes"
|
||||
className="z-0 bg-almostblack relative flex flex-col items-center overflow-x-hidden"
|
||||
className="min-h-screen z-0 bg-almostblack relative flex flex-col items-center overflow-x-hidden"
|
||||
>
|
||||
<PageHeader />
|
||||
<section className="max-w-screen-2xl w-full relative py-24 px-4 md:px-16 ">
|
||||
|
@ -32,6 +32,14 @@ export default function PageHeader() {
|
||||
>
|
||||
<div className="lg:pr-4 lg:ml-8">
|
||||
<ul className="divide-y lg:divide-y-0 divide-white/5 gap-8 py-4 lg:hover:bg-transparent lg:hover:pl-0 tracking-wide lg:flex lg:space-y-0 lg:text-sm lg:items-center">
|
||||
<li>
|
||||
<Link
|
||||
className="btn-tf hover:underline lg:flex"
|
||||
href="/article-list/1"
|
||||
>
|
||||
{t("blog")}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
className="btn-tf btn-tf-secondary lg:flex"
|
||||
@ -43,20 +51,6 @@ export default function PageHeader() {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/* <button
|
||||
aria-label="hamburger"
|
||||
id="hamburger"
|
||||
className="relative -mr-6 p-6 lg:hidden"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="m-auto h-0.5 w-5 rounded bg-sky-50 transition duration-300 dark:bg-gray-300"
|
||||
></div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="m-auto mt-1.5 h-0.5 w-5 rounded bg-sky-50 transition duration-300 dark:bg-gray-300"
|
||||
></div>
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { redirect } from 'next/navigation'; // 如果在 SSR 中需要使用
|
||||
|
||||
import queryString from 'query-string';
|
||||
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
@ -32,8 +31,8 @@ class Request {
|
||||
? cacheTime > 0
|
||||
? { next: { revalidate: cacheTime } }
|
||||
: { cache: 'no-store' }
|
||||
: { cache: 'force-cache' };
|
||||
|
||||
: { cache: 'no-store' };
|
||||
// 最后一行 : { cache: 'no-store' }; 原本是 force-cache
|
||||
if (method === 'GET' || method === 'DELETE') {
|
||||
//fetch对GET请求等,不支持将参数传在body上,只能拼接url
|
||||
if (params) {
|
||||
@ -97,13 +96,14 @@ class Request {
|
||||
});
|
||||
}
|
||||
|
||||
async httpFactory<T>({ url = '', params = {}, method }: Props): Promise<T> {
|
||||
async httpFactory<T>({ url = '', params = { cacheTime: 0 }, method }: Props): Promise<T> {
|
||||
const req = this.interceptorsRequest({
|
||||
url: process.env.NEXT_PUBLIC_BASEURL + url,
|
||||
method,
|
||||
params: params.params,
|
||||
cacheTime: params.cacheTime,
|
||||
});
|
||||
console.log("req", req)
|
||||
const res = await fetch(req.url, req.options);
|
||||
return this.interceptorsResponse<T>(res);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user