完成了网站的广告和详请展示

This commit is contained in:
expdsn 2025-02-13 15:54:18 +08:00
parent a5637efccb
commit f532a992e1
20 changed files with 1310 additions and 123 deletions

5
.env.local Normal file
View File

@ -0,0 +1,5 @@
MONGODB_URI=mongodb://expdsn:58662@expdsn.cloud:27017
# MONGODB_URI=mongodb://expdsn:58662@localhost:27017
SESSION_SECRET=lREDRcaFwZIzM7Rjw63XGj8trTyMqhVUsVwwhuTQnFs=
ALIYUN_RAM_ACCESS_KEY_ID=LTAI5tNzopZHJFa2Q9vqr1u5
ALIYUN_RAM_ACCESS_KEY_SECRET=qPu7fyft0KJ1l6SGqbS71IW0vDbRlr

2
.gitignore vendored
View File

@ -31,7 +31,7 @@ yarn-error.log*
.pnpm-debug.log* .pnpm-debug.log*
# env files (can opt-in for committing if needed) # env files (can opt-in for committing if needed)
.env* .env
# vercel # vercel
.vercel .vercel

View File

@ -0,0 +1,35 @@
import { getArticle } from "@/app/_lib/data/article"
import MarkdownView from "@/app/_ui/MarkdownView"
import { faArrowRight } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { MdEditor } from "md-editor-rt"
import Link from "next/link"
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const id = (await params).id
const article = await getArticle(id)
if (!article) return <></>
return <div className="w-full h-full min-h-[100vh] flex justify-center">
<div className="w-full max-w-[800px] px-4 h-full pb-[50px] pt-[100px] ">
<div className="w-full h-full ">
<div className="flex gap-x-5">
<div className="p-4 rounded-lg">
<img src={article?.cover} className=" rounded-lg shadow object-cover w-[200px] h-[200px]">
</img>
</div>
<div className="flex flex-col gap-y-3 py-5">
<h1 className="text-2xl">{article.title}</h1>
<article className=" line-clamp-3 overflow-hidden text-ellipsis">{article.description}</article>
<Link href={article.link} className="px-3 py-2 duration-150 bg-red-500 hover:bg-red-500/80 flex items-center justify-center rounded-lg text-white">
访
<FontAwesomeIcon icon={faArrowRight} className="ml-2" />
</Link>
</div>
</div>
<div className="w-full">
<MarkdownView value={article.content}></MarkdownView>
</div>
</div>
</div>
</div>
}

24
app/(main)/layout.tsx Normal file
View File

@ -0,0 +1,24 @@
import SiderNav from "../_ui/SiderNav";
import { getLinkTypeList } from "../_lib/data/linkType";
export default async function Layout({ children }: Readonly<{ children: React.ReactNode }>) {
const { list: linkTypeList } = await getLinkTypeList({})
return (
<div className="pl-[220px] flex min-h-full w-full font-[family-name:var(--font-geist-sans)] relative">
<SiderNav linkList={linkTypeList} />
<div className="w-0 flex-1 relative">
<div className="absolute z-[-10] from-[#E6EEF4] h-[50vh] w-full bg-gradient-to-br via-[#F1ECF4] to-[#F5ECEA]">
<div className="absolute z-[-9] from-[#F9F9F9] left-0 to-transparent bg-gradient-to-t w-full h-[80px] bottom-[0px]">
</div>
</div>
{children}
</div>
</div>
);
}

31
app/(main)/page.tsx Normal file
View File

@ -0,0 +1,31 @@
import SiderNav from "../_ui/SiderNav";
import Search from "../_ui/Search";
import LinkListBox from "../_ui/LinkListBox";
import { getLinkTypeList } from "../_lib/data/linkType";
import { getLinkListAll } from "../_lib/data/link";
import Footer from "../_ui/footer";
export default async function Page() {
const { list: linkTypeList } = await getLinkTypeList({})
const linkList = await getLinkListAll()
return (
<div className="flex min-h-full w-full font-[family-name:var(--font-geist-sans)] relative">
<SiderNav linkList={linkTypeList} />
<div className="w-full">
<main className="flex-1 relative flex flex-col p-5 gap-y-4">
{/* <HeaderNav></HeaderNav> */}
<Search></Search>
{/* <PosterBox posterList={[]} /> */}
<LinkListBox linkList={linkList} linkTypeList={linkTypeList} showHot showRecent></LinkListBox>
</main>
<Footer></Footer>
</div>
</div>
);
}

View File

@ -10,11 +10,66 @@ export type Link = {
_id: string; _id: string;
type: string; type: string;
priority: number; priority: number;
articleId?: string;
logoLink: string; logoLink: string;
isHot?: boolean; isHot?: boolean;
addTime: number; addTime: number;
} }
export async function getLinkListAll() {
const linkCollection = await getCollection('link');
const list = await linkCollection.aggregate<Link>([
{
'$addFields': {
'cleanedLink': {
'$substrBytes': [
{
'$arrayElemAt': [
{
'$split': [
'$link', '?'
]
}, 0
]
}, 0, 1000
]
}
}
}, {
'$lookup': {
'from': 'article',
'localField': 'link',
'foreignField': 'link',
'as': 'article'
}
}, {
'$addFields': {
'articleId': {
'$toString':
{
'$arrayElemAt': [
'$article._id', 0
]
}
},
'_id': {
'$toString': "$_id"
}
}
}, {
'$project': {
'article': 0
}
}
]).toArray();
return list;
}
// Link 类型定义
export async function getLinkList({ page = 1, pageSize = 9999, typeId }: { export async function getLinkList({ page = 1, pageSize = 9999, typeId }: {
page?: number; page?: number;
pageSize?: number; pageSize?: number;

View File

@ -3,6 +3,7 @@ import { MongoClient, Db } from 'mongodb';
const uri = process.env.MONGODB_URI; const uri = process.env.MONGODB_URI;
const options = {};
let client: MongoClient; let client: MongoClient;
let clientPromise: Promise<MongoClient>; let clientPromise: Promise<MongoClient>;
@ -11,33 +12,21 @@ if (!uri) {
} }
// client = new MongoClient(uri,{ if (process.env.NODE_ENV === 'development') {
// maxPoolSize: 5, if (!(global as any)._mongoClientPromise) {
// maxConnecting:5, client = new MongoClient(uri, options);
// maxIdleTimeMS: 3000, (global as any)._mongoClientPromise = client.connect();
// waitQueueTimeoutMS: 1000 }
// }); clientPromise = (global as any)._mongoClientPromise;
// clientPromise = client.connect(); } else {
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
// export const getDb = async () => {
// const client = await clientPromise;
// return client.db('ai-bot')
// };
// export const getCollection = async (collection: string) => {
// const client = await clientPromise;
// return client.db('ai-bot').collection(collection);
// };
const ins = new MongoClient(uri,{
maxPoolSize: 5,
maxConnecting:5,
maxIdleTimeMS: 3000,
waitQueueTimeoutMS: 1000
}).db('ai-bot')
export const getDb = async () => { export const getDb = async () => {
return ins return (await clientPromise).db('ai-bot');
}; };
export const getCollection = async (collection: string) => { export const getCollection = async (collection: string) => {
const ins = await getDb();
return ins.collection(collection); return ins.collection(collection);
}; };

View File

@ -3,11 +3,7 @@ import { v4 as uuid } from "uuid";
import { mRequest } from "./request"; import { mRequest } from "./request";
const ossBase = "https://aihlp.com.cn"; const ossBase = "https://aihlp.com.cn";
const accessKeyId = process.env.ALIYUN_RAM_ACCESS_KEY_ID || ''
const accessKeySecret = process.env.ALIYUN_RAM_ACCESS_KEY_SECRET || ''
console.log('id');
console.log(accessKeyId);
export default async function uploadOss(file: File, root: string) { export default async function uploadOss(file: File, root: string) {
const path = `/admin/${root}/${uuid()}_${file.name}`; const path = `/admin/${root}/${uuid()}_${file.name}`;

View File

@ -9,13 +9,23 @@ import { useAtom } from "jotai";
import { linkTypeAtom } from "../_lib/atom"; import { linkTypeAtom } from "../_lib/atom";
import Image from "next/image"; import Image from "next/image";
const LinkBlock = ({ val }: { val: _Link }) => {
return <Link className="flex gap-x-2 bg-white rounded-lg py-4 pl-2 cursor-pointer duration-150 hover:-translate-y-1 shadow-sm"
href={val.articleId ? `/article/${val.articleId}` : val.link || ''} target="_blank">
<img src={val.logoLink} className="w-[40px] h-[40px]"></img>
<div className="flex-1 w-0 flex flex-col justify-between">
<span className=" font-bold text-ellipsis overflow-hidden whitespace-nowrap">{val.name}</span>
<span className=" text-ellipsis overflow-hidden whitespace-nowrap text-[#666] text-xs" title={val.description}>{val.description}</span>
</div>
</Link>
}
export default function LinkListBox({ linkTypeList, linkList, showHot, showRecent }: { linkTypeList: LinkType[]; linkList: _Link[]; showHot: boolean; showRecent: boolean }) { export default function LinkListBox({ linkTypeList, linkList, showHot, showRecent }: { linkTypeList: LinkType[]; linkList: _Link[]; showHot: boolean; showRecent: boolean }) {
const hotList = useMemo(() => linkList.filter((val, index) => val.isHot && index < 12), []) const hotList = useMemo(() => linkList.filter((val, index) => val.isHot && index < 12), [])
const recentList = useMemo(() => linkList.map(val => val).sort((a, b) => b.addTime - a.addTime).filter((_, idx) => idx < 12), []) const recentList = useMemo(() => linkList.map(val => val).sort((a, b) => b.addTime - a.addTime).filter((_, idx) => idx < 12), [])
const [currentId] = useAtom(linkTypeAtom) const [currentId] = useAtom(linkTypeAtom)
useEffect(() => { useEffect(() => {
console.log(currentId); console.log(currentId);
if (currentId) { if (currentId) {
// 根据 targetId 查找对应的元素 // 根据 targetId 查找对应的元素
const element = document.getElementById(currentId); const element = document.getElementById(currentId);
@ -35,14 +45,7 @@ export default function LinkListBox({ linkTypeList, linkList, showHot, showRecen
<div className=" grid grid-cols-3 lg:grid-cols-6 gap-4 "> <div className=" grid grid-cols-3 lg:grid-cols-6 gap-4 ">
{ {
hotList.map(val => ( hotList.map(val => (
<Link key={val._id} className="flex gap-x-2 bg-white rounded-lg py-4 pl-2 cursor-pointer duration-150 hover:-translate-y-1 shadow-sm" <LinkBlock val={val} key={val._id} />
href={val.link || ''} target="_blank">
<img src={val.logoLink} className="w-[40px] h-[40px]"></img>
<div className="flex-1 w-0 flex flex-col justify-between">
<span className=" font-bold text-ellipsis overflow-hidden whitespace-nowrap">{val.name}</span>
<span className=" text-ellipsis overflow-hidden whitespace-nowrap text-[#666] text-xs" title={val.description}>{val.description}</span>
</div>
</Link>
)) ))
} }
</div> </div>
@ -58,14 +61,8 @@ export default function LinkListBox({ linkTypeList, linkList, showHot, showRecen
<div className=" grid grid-cols-3 lg:grid-cols-6 gap-4 "> <div className=" grid grid-cols-3 lg:grid-cols-6 gap-4 ">
{ {
recentList.map(val => ( recentList.map(val => (
<Link key={val._id} className="flex gap-x-2 bg-white rounded-lg py-4 pl-2 cursor-pointer duration-150 hover:-translate-y-1 shadow-sm" <LinkBlock val={val} key={val._id} />
href={val.link || ''} target="_blank">
<img src={val.logoLink} className="w-[40px] h-[40px]"></img>
<div className="flex-1 w-0 flex flex-col justify-between">
<span className=" font-bold text-ellipsis overflow-hidden whitespace-nowrap">{val.name}</span>
<span className=" text-ellipsis overflow-hidden whitespace-nowrap text-[#666] text-xs" title={val.description}>{val.description}</span>
</div>
</Link>
)) ))
} }
</div> </div>
@ -82,14 +79,8 @@ export default function LinkListBox({ linkTypeList, linkList, showHot, showRecen
<div className=" grid grid-cols-3 lg:grid-cols-6 gap-4 "> <div className=" grid grid-cols-3 lg:grid-cols-6 gap-4 ">
{ {
linkList.filter(val => val.type === item._id).map(val => ( linkList.filter(val => val.type === item._id).map(val => (
<Link key={val._id} className="flex gap-x-2 bg-white rounded-lg py-4 pl-2 cursor-pointer duration-150 hover:-translate-y-1 shadow-sm" <LinkBlock val={val} key={val._id} />
href={val.link || ''} target="_blank">
<img src={val.logoLink} className="w-[40px] h-[40px]"></img>
<div className="flex-1 w-0 flex flex-col justify-between">
<span className=" font-bold text-ellipsis overflow-hidden whitespace-nowrap">{val.name}</span>
<span className=" text-ellipsis overflow-hidden whitespace-nowrap text-[#666] text-xs" title={val.description}>{val.description}</span>
</div>
</Link>
)) ))
} }
</div> </div>

8
app/_ui/MarkdownView.tsx Normal file
View File

@ -0,0 +1,8 @@
"use client";
import { MdPreview, MdCatalog } from 'md-editor-rt';
export default function MarkdownView({ value = '' }: { value?: string }) {
return (
<MdPreview id='markdown-view' value={value} />
)
}

View File

@ -20,8 +20,8 @@ import {
Table, Table,
} from "antd" } from "antd"
import dayjs from "dayjs" import dayjs from "dayjs"
import { useCallback, useEffect, useState } from "react" import { useState } from "react"
import useSWR from "swr"
export default function LinkTable(props: { id: string }) { export default function LinkTable(props: { id: string }) {

View File

@ -4,7 +4,7 @@ import { ArticleType } from '@/app/_lib/data/article';
import ImageUpload from '@/app/_ui/ImageUpload'; import ImageUpload from '@/app/_ui/ImageUpload';
import { faArrowLeft, faRibbon } from '@fortawesome/free-solid-svg-icons'; import { faArrowLeft, faRibbon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { BackTop, Button, Card, Col, DatePicker, Form, Grid, Input, InputNumber, message, Row, Space, Upload } from 'antd'; import { BackTop, Button, Card, Col, DatePicker, Form, Grid, Input, InputNumber, message, Row, Select, Space, Upload } from 'antd';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useParams, useRouter } from 'next/navigation' import { useParams, useRouter } from 'next/navigation'
@ -48,13 +48,16 @@ export default function AddOrEdit({ editData }: { editData?: ArticleType | null
} else { } else {
await mRequest("POST", "/api/article", { await mRequest("POST", "/api/article", {
...res, ...res,
addTime: dayjs().unix()
}) })
} }
router?.push("/admin/dashboard/article")
message.success("操作成功") message.success("操作成功")
}} }}
initialValues={editData ? { initialValues={editData ? {
...editData, ...editData,
addTime: dayjs(editData.addTime) addTime: dayjs(editData.addTime * 1000)
} : { } : {
priority: 0, priority: 0,
addTime: dayjs() addTime: dayjs()
@ -66,7 +69,7 @@ export default function AddOrEdit({ editData }: { editData?: ArticleType | null
<Form.Item label="标题" name={"title"} rules={[{ required: true, message: '请输入标题' }]}> <Form.Item label="标题" name={"title"} rules={[{ required: true, message: '请输入标题' }]}>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label="副标题" name={"description"} <Form.Item label="介绍" name={"description"}
rules={[ rules={[
{ {
required: true, required: true,
@ -108,6 +111,22 @@ export default function AddOrEdit({ editData }: { editData?: ArticleType | null
</Col> </Col>
</Row> </Row>
<Row>
<Col span={12}>
<Form.Item label="关联网址"
name="linkId"
rules={[
{
required: true,
message: '请输入副标题'
}
]}>
<Select showSearch>
</Select>
</Form.Item>
</Col>
</Row>
<Row> <Row>
<Col span={12}> <Col span={12}>
@ -134,9 +153,7 @@ export default function AddOrEdit({ editData }: { editData?: ArticleType | null
<Form.Item className="flex justify-end"> <Form.Item className="flex justify-end">
<Space> <Space>
<Button>
</Button>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
</Button> </Button>

View File

@ -1,6 +0,0 @@
export default function Loading() {
return <>
loading
</>
}

View File

@ -2,16 +2,16 @@ import LoginState from "@/app/_ui/LoginState";
import SiderNav from "../../_ui/SiderNav"; import SiderNav from "../../_ui/SiderNav";
import { faAd, faMagnet, faPenClip, faSearch } from "@fortawesome/free-solid-svg-icons" import { faAd, faMagnet, faPenClip, faSearch } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { AntdRegistry } from '@ant-design/nextjs-registry';
export default function Layout({ export default function Layout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col pl-[220px]">
<SiderNav <SiderNav
linkList={[ linkList={[
{ {
@ -33,7 +33,7 @@ export default function Layout({
href: '/admin/dashboard/ad' href: '/admin/dashboard/ad'
}, },
{ {
label: '文章管理', label: '文章管理',
iconElement: <FontAwesomeIcon icon={faPenClip}></FontAwesomeIcon>, iconElement: <FontAwesomeIcon icon={faPenClip}></FontAwesomeIcon>,
_id: 'articleMenagement', _id: 'articleMenagement',
@ -49,8 +49,9 @@ export default function Layout({
<LoginState></LoginState> <LoginState></LoginState>
</div> </div>
<main className="p-2"> <main className="p-2">
<AntdRegistry>
{children} {children}
</AntdRegistry>
</main> </main>
</div> </div>

View File

@ -3,7 +3,6 @@ import { getCollection } from "@/app/_lib/mongodb";
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
const page = parseInt(request.nextUrl.searchParams.get('page') || '1') || 1; const page = parseInt(request.nextUrl.searchParams.get('page') || '1') || 1;
const pageSize = parseInt(request.nextUrl.searchParams.get('pageSize') || '10') || 10; const pageSize = parseInt(request.nextUrl.searchParams.get('pageSize') || '10') || 10;
// 计算起始索引和结束索引 // 计算起始索引和结束索引

View File

@ -1,7 +1,7 @@
@import "tailwindcss"; @import "tailwindcss";
:root { :root {
--background: #ffffff; --background: #f9f9f9;
--foreground: #171717; --foreground: #171717;
} }
@ -11,6 +11,44 @@
--foreground: #ededed; --foreground: #ededed;
} }
} }
@theme {
--prose-link-color: #1e90ff;
}
@layer components {
#markdown-view-preview {
p {
font-size: var(--background);
color: var(--color-gray-700);
margin: 4px 0;
letter-spacing: 1px;
line-height: 1.6;
}
blockquote {
border-left: 4px solid #1e90ff;
padding-left: 10px;
color: #333;
font-size: 20px;
margin: 10px 0;
}
a {
color: var(--color-gray-800);
font-weight: 600;
padding-bottom: 2px;
border-bottom: 1px solid var(--prose-link-color);
}
a:hover {
border-bottom: 2px solid var(--prose-link-color);
}
ul {
margin-left: 30px;
list-style: disc;
}
ul > li {
margin-bottom: 10px;
}
}
}
@layer base { @layer base {
ol { ol {
list-style-type: decimal; list-style-type: decimal;

View File

@ -2,7 +2,6 @@ import type { Metadata } from "next";
import "./globals.css"; import "./globals.css";
import '@fortawesome/fontawesome-svg-core/styles.css' import '@fortawesome/fontawesome-svg-core/styles.css'
import '@ant-design/v5-patch-for-react-19'; import '@ant-design/v5-patch-for-react-19';
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Create Next App",
description: "Generated by create next app", description: "Generated by create next app",
@ -14,9 +13,9 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="zh-CN">
<body <body
className={`pl-[220px] antialiased bg-[#F9F9F9]`} className={`antialiased`}
> >
{children} {children}
</body> </body>

View File

@ -1,36 +0,0 @@
import SiderNav from "./_ui/SiderNav";
import Search from "./_ui/Search";
import LinkListBox from "./_ui/LinkListBox";
import { getLinkTypeList } from "./_lib/data/linkType";
import { getLinkList } from "./_lib/data/link";
import Footer from "./_ui/footer";
export default async function Home() {
const { list: linkTypeList } = await getLinkTypeList({})
const { list: linkList } = await getLinkList({})
return (
<div className="flex min-h-full w-full font-[family-name:var(--font-geist-sans)] relative">
<SiderNav linkList={linkTypeList} />
<div className="w-full">
<div className="absolute -z-10 from-[#E6EEF4] h-[50vh] w-full bg-gradient-to-br via-[#F1ECF4] to-[#F5ECEA]">
<div className="absolute z-10 from-[#F9F9F9] left-0 to-transparent bg-gradient-to-t w-full h-[100px] bottom-[0px]">
</div>
</div>
<main className="flex-1 relative flex flex-col p-5 gap-y-4">
{/* <HeaderNav></HeaderNav> */}
<Search></Search>
{/* <PosterBox posterList={[]} /> */}
<LinkListBox linkList={linkList} linkTypeList={linkTypeList} showHot showRecent></LinkListBox>
</main>
<Footer></Footer>
</div>
</div>
);
}

View File

@ -5,11 +5,13 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"pro-build": "node --max-old-space-size=1024 node_modules/next/dist/bin/next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.5.2", "@ant-design/icons": "^5.5.2",
"@ant-design/nextjs-registry": "^1.0.2",
"@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2", "@fortawesome/react-fontawesome": "^0.2.2",

File diff suppressed because it is too large Load Diff