save
This commit is contained in:
parent
fbf3c4102d
commit
a5637efccb
|
@ -41,5 +41,8 @@ export async function getArticleList({ page = 1, pageSize = 9999 }: {
|
|||
}
|
||||
export async function getArticle(id: string) {
|
||||
const collection = await getCollection('article');
|
||||
return collection.findOne<ArticleType>({ _id: new ObjectId(id) })
|
||||
return collection.findOne({ _id: new ObjectId(id) }).then(res => res ? {
|
||||
...res,
|
||||
_id: res._id.toString()
|
||||
} as ArticleType : null)
|
||||
}
|
|
@ -18,7 +18,7 @@ export async function getLinkTypeList({ page = 1, pageSize = 9999 }: {
|
|||
}) {
|
||||
const collection = await getCollection('link-type');
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const pipeline = [
|
||||
const list = await collection.aggregate<LinkType>( [
|
||||
{ $sort: { priority: 1 } },
|
||||
{ $skip: startIndex },
|
||||
{ $limit: pageSize },
|
||||
|
@ -27,12 +27,10 @@ export async function getLinkTypeList({ page = 1, pageSize = 9999 }: {
|
|||
_id: { $toString: "$_id" }
|
||||
}
|
||||
}
|
||||
];
|
||||
const cursor = collection.aggregate<LinkType>(pipeline);
|
||||
const list = await cursor.toArray();
|
||||
]).toArray();
|
||||
|
||||
// 计算总数量
|
||||
const total = (await collection.find<LinkType>({}).toArray()).length
|
||||
const total = await collection.countDocuments()
|
||||
return {
|
||||
total,
|
||||
list
|
||||
|
|
|
@ -10,25 +10,34 @@ if (!uri) {
|
|||
throw new Error('Please add your Mongo URI to.env.local');
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// In development mode, use a global variable so that the value
|
||||
// is preserved across module reloads caused by HMR (Hot Module Replacement).
|
||||
if (!global._mongoClientPromise) {
|
||||
client = new MongoClient(uri);
|
||||
global._mongoClientPromise = client.connect();
|
||||
}
|
||||
clientPromise = global._mongoClientPromise;
|
||||
} else {
|
||||
// In production mode, it's best to not use a global variable.
|
||||
client = new MongoClient(uri);
|
||||
clientPromise = client.connect();
|
||||
}
|
||||
|
||||
export const getDb = async (): Promise<Db> => {
|
||||
const client = await clientPromise;
|
||||
return client.db('ai-bot');
|
||||
// client = new MongoClient(uri,{
|
||||
// maxPoolSize: 5,
|
||||
// maxConnecting:5,
|
||||
// maxIdleTimeMS: 3000,
|
||||
// waitQueueTimeoutMS: 1000
|
||||
// });
|
||||
// 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 () => {
|
||||
return ins
|
||||
};
|
||||
export const getCollection = async (collection: string) => {
|
||||
const client = await clientPromise;
|
||||
return client.db('ai-bot').collection(collection);
|
||||
return ins.collection(collection);
|
||||
};
|
|
@ -9,6 +9,7 @@ export default function ImageUpload(props: {
|
|||
height?: number
|
||||
value?: string
|
||||
background?: string
|
||||
hiddenCover?: boolean
|
||||
onChange?: (val: string) => void
|
||||
}) {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
@ -31,18 +32,23 @@ export default function ImageUpload(props: {
|
|||
}
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="bg-center bg-no-repeat bg-contain rounded-lg shadow-lg flex items-center"
|
||||
style={{
|
||||
width: props.width || 240 + "px",
|
||||
height: props.height || 120 + "px",
|
||||
backgroundImage: `url('${props.value}')`,
|
||||
backgroundColor: props.background || "rgba(0,0,0,0.2)",
|
||||
}}
|
||||
/>
|
||||
<Space className="mt-2">
|
||||
{
|
||||
!props?.hiddenCover
|
||||
&&
|
||||
<div
|
||||
className="bg-center bg-no-repeat bg-contain rounded-lg shadow-lg flex items-center"
|
||||
style={{
|
||||
width: props.width || 240 + "px",
|
||||
height: props.height || 120 + "px",
|
||||
backgroundImage: `url('${props.value}')`,
|
||||
backgroundColor: props.background || "rgba(0,0,0,0.2)",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
<Input value={props.value} onChange={(e) => {
|
||||
<Space className={!props?.hiddenCover ? "mt-2" : ""}>
|
||||
|
||||
<Input value={props.value} onChange={(e) => {
|
||||
props.onChange?.(e.target.value)
|
||||
}}></Input>
|
||||
<Button
|
||||
|
|
|
@ -7,6 +7,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||
import { faClock, faFire, faTimeline, faTimesCircle, faTimesRectangle, faVolumeTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useAtom } from "jotai";
|
||||
import { linkTypeAtom } from "../_lib/atom";
|
||||
import Image from "next/image";
|
||||
|
||||
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), [])
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Link } from "@/app/_lib/data/link"
|
||||
import { LinkType } from "@/app/_lib/data/linkType"
|
||||
import { mRequest } from "@/app/_lib/request"
|
||||
import ImageUpload from "@/app/_ui/ImageUpload"
|
||||
import { Link } from "@/app/api/link/route"
|
||||
import { LinkType } from "@/app/api/linkType/route"
|
||||
import { useAntdTable, useRequest } from "ahooks"
|
||||
import {
|
||||
Button,
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { ArticleType } from '@/app/_lib/data/article';
|
||||
import ImageUpload from '@/app/_ui/ImageUpload';
|
||||
import { faArrowLeft, faRibbon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { BackTop, Button, Card, Col, Form, Grid, Input, Row, Upload } from 'antd';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import { useState } from 'react';
|
||||
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
return <>
|
||||
<div className='w-full bg-white p-2 flex gap-x-1 items-center rounded shadow'>
|
||||
<div className='flex gap-x-1 items-center text-[#666] hover:text-[#333] cursor-pointer '
|
||||
onClick={() => {
|
||||
router?.back()
|
||||
}}
|
||||
>
|
||||
|
||||
<FontAwesomeIcon icon={faArrowLeft}></FontAwesomeIcon>
|
||||
返回
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Card title="新增文章" className='mt-2'>
|
||||
<Form<ArticleType>>
|
||||
<Row gutter={24}>
|
||||
|
||||
<Col span={12}>
|
||||
<Form.Item label="标题" name={"title"} >
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="副标题" name={"description"}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="链接" name={"link"}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="封面" name={"cover"}>
|
||||
<ImageUpload
|
||||
accept="image/*"
|
||||
width={80}
|
||||
height={80}
|
||||
></ImageUpload>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
<Form.Item label="内容" name={"content"}>
|
||||
<MDEditor extraCommands={[
|
||||
{
|
||||
name: 'image',
|
||||
icon: <span>1</span>, // 可以使用图标库的图标
|
||||
execute: () => {
|
||||
|
||||
}, // 点击时调用插入图片的函数
|
||||
},
|
||||
]} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
</Card>
|
||||
</>
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
"use client"
|
||||
|
||||
import { ArticleType } from '@/app/_lib/data/article';
|
||||
import ImageUpload from '@/app/_ui/ImageUpload';
|
||||
import { faArrowLeft, faRibbon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { BackTop, Button, Card, Col, DatePicker, Form, Grid, Input, InputNumber, message, Row, Space, Upload } from 'antd';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
|
||||
import { useState } from 'react';
|
||||
// const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
|
||||
import { MdEditor } from 'md-editor-rt';
|
||||
import 'md-editor-rt/lib/style.css';
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
import { mRequest } from '@/app/_lib/request';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default function AddOrEdit({ editData }: { editData?: ArticleType | null }) {
|
||||
const router = useRouter()
|
||||
console.log(editData);
|
||||
|
||||
|
||||
return <>
|
||||
<div className='w-full bg-white p-2 flex gap-x-1 items-center rounded shadow'>
|
||||
<div className='flex gap-x-1 items-center text-[#666] hover:text-[#333] cursor-pointer '
|
||||
onClick={() => {
|
||||
router?.back()
|
||||
}}
|
||||
>
|
||||
|
||||
<FontAwesomeIcon icon={faArrowLeft}></FontAwesomeIcon>
|
||||
返回
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Card title="新增文章" className='mt-2'>
|
||||
<Form<ArticleType>
|
||||
|
||||
onFinish={async (res) => {
|
||||
if (editData) {
|
||||
await mRequest("PUT", "/api/article", {
|
||||
...res,
|
||||
_id: editData._id
|
||||
})
|
||||
} else {
|
||||
await mRequest("POST", "/api/article", {
|
||||
...res,
|
||||
})
|
||||
}
|
||||
message.success("操作成功")
|
||||
}}
|
||||
initialValues={editData ? {
|
||||
...editData,
|
||||
addTime: dayjs(editData.addTime)
|
||||
} : {
|
||||
priority: 0,
|
||||
addTime: dayjs()
|
||||
}}
|
||||
>
|
||||
<Row gutter={24}>
|
||||
|
||||
<Col span={12}>
|
||||
<Form.Item label="标题" name={"title"} rules={[{ required: true, message: '请输入标题' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="副标题" name={"description"}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入副标题'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item label="链接" name={"link"}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入链接'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="封面" name={"cover"}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入封面'
|
||||
}
|
||||
]}
|
||||
|
||||
>
|
||||
<ImageUpload
|
||||
accept="image/*"
|
||||
width={80}
|
||||
height={80}
|
||||
hiddenCover
|
||||
></ImageUpload>
|
||||
</Form.Item>
|
||||
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
|
||||
<Form.Item label="修改时间" name={"addTime"}>
|
||||
<DatePicker showTime />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
|
||||
<Form.Item label="优先级" name={"priority"}>
|
||||
<InputNumber></InputNumber>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item label="内容" name={"content"} rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入内容'
|
||||
}
|
||||
]}>
|
||||
{/* <MDEditor className='my-markdown ' /> */}
|
||||
<MdEditor />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="flex justify-end">
|
||||
<Space>
|
||||
<Button>
|
||||
重置
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit">
|
||||
提交
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
</Card>
|
||||
</>
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { getArticle } from '@/app/_lib/data/article';
|
||||
import AddOrEdit from '../AddOrEdit';
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: { params: Promise<{ id: string }> }) {
|
||||
const id = (await params).id
|
||||
const data = await getArticle(id)
|
||||
return <>
|
||||
<AddOrEdit editData={data}></AddOrEdit>
|
||||
|
||||
</>
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
"use client"
|
||||
|
||||
|
||||
import AddOrEdit from './AddOrEdit';
|
||||
|
||||
export default function Page() {
|
||||
|
||||
|
||||
return <>
|
||||
<AddOrEdit></AddOrEdit>
|
||||
</>
|
||||
}
|
|
@ -1,36 +1,58 @@
|
|||
"use client"
|
||||
import { Button, Card, Space, Table } from "antd";
|
||||
import { Button, Card, Image, Space, Table } from "antd";
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useAntdTable } from "ahooks";
|
||||
import { mRequest } from "@/app/_lib/request";
|
||||
import { ArticleType } from "@/app/_lib/data/article";
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
const { tableProps, refresh } = useAntdTable(async ({ current, pageSize }) => mRequest<{
|
||||
total: number;
|
||||
list: ArticleType[]
|
||||
}>('GET', `/api/article?page=${current}&pageSize=${pageSize}`))
|
||||
return (
|
||||
<Card title="文章管理" extra={<Button type="primary" onClick={() => {
|
||||
router.push("/admin/dashboard/article/add")
|
||||
}} >
|
||||
新增文章
|
||||
</Button>}>
|
||||
<Card title="文章管理" extra={
|
||||
<Space>
|
||||
<Button onClick={() => refresh()}>刷新</Button>
|
||||
<Button type="primary" onClick={() => {
|
||||
router.push("/admin/dashboard/article/detail")
|
||||
}} >
|
||||
新增文章
|
||||
</Button>
|
||||
</Space>
|
||||
}>
|
||||
<Table
|
||||
{...tableProps}
|
||||
rowKey="_id"
|
||||
columns={[
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
title: '作者',
|
||||
dataIndex: 'author',
|
||||
title: '副标题',
|
||||
dataIndex: 'description',
|
||||
},
|
||||
|
||||
{
|
||||
title: '内容',
|
||||
dataIndex: 'content',
|
||||
title: "封面",
|
||||
dataIndex: "content",
|
||||
render: (_, item) => (
|
||||
<>
|
||||
<Image src={item.content} width={70} ></Image>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
render: (_, row) => (
|
||||
<Space>
|
||||
<Button type="primary">修改</Button>
|
||||
<Button type="primary">删除</Button>
|
||||
<Button type="primary" onClick={() => {
|
||||
router.push(`/admin/dashboard/article/detail/${row._id}`)
|
||||
}} >修改</Button>
|
||||
<Button type="primary" danger>删除</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import SiderNav from "../_ui/SiderNav";
|
||||
import { AIAppLinkList } from "./page";
|
||||
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<div >
|
||||
<SiderNav linkList={AIAppLinkList}></SiderNav>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { faBackward } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
||||
export const AIAppLinkList = [
|
||||
{
|
||||
label: '返回',
|
||||
icon: <FontAwesomeIcon icon={faBackward} className="fa-fw " />,
|
||||
href: '/',
|
||||
id: 1,
|
||||
},
|
||||
]
|
||||
export default function Page() {
|
||||
return <div className="bg-red-300">AI Apps</div>
|
||||
}
|
|
@ -7,7 +7,7 @@ export async function GET(request: NextRequest) {
|
|||
const page = parseInt(request.nextUrl.searchParams.get('page') || '1') || 1;
|
||||
const pageSize = parseInt(request.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
||||
// 计算起始索引和结束索引
|
||||
const res = getArticleList({ page, pageSize })
|
||||
const res = await getArticleList({ page, pageSize })
|
||||
return Response.json(res)
|
||||
}
|
||||
export async function POST(request: NextRequest) {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
|
@ -13,7 +11,15 @@
|
|||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
|
||||
margin: 1em 0;
|
||||
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
|
@ -24,7 +30,7 @@ body {
|
|||
background-color: transparent !important;
|
||||
color: rgba(0, 0, 0, 0.85) !important;
|
||||
}
|
||||
.ant-checkbox-disabled+span{
|
||||
.ant-checkbox-disabled + span {
|
||||
background-color: transparent !important;
|
||||
color: rgba(0, 0, 0, 0.85) !important;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export default function RootLayout({
|
|||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`pl-[220px] antialiased bg-[#F9F9F9]`}
|
||||
className={`pl-[220px] antialiased bg-[#F9F9F9]`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
|
@ -13,6 +13,7 @@
|
|||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@tailwindcss/postcss": "^4.0.4",
|
||||
"@uiw/react-md-editor": "^4.0.5",
|
||||
"ahooks": "^3.8.4",
|
||||
"ali-oss": "^6.22.0",
|
||||
|
@ -23,6 +24,7 @@
|
|||
"icons": "link:@awesome.me/kit-KIT_CODE/icons",
|
||||
"jose": "^5.9.6",
|
||||
"jotai": "^2.11.1",
|
||||
"md-editor-rt": "^5.2.2",
|
||||
"mongodb": "^6.12.0",
|
||||
"next": "15.1.4",
|
||||
"proxy-agent": "^6.5.0",
|
||||
|
@ -42,8 +44,8 @@
|
|||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.4",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"postcss": "^8.5.1",
|
||||
"tailwindcss": "^4.0.4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
1485
pnpm-lock.yaml
1485
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue