完成了文章的管理
This commit is contained in:
parent
df92d99a51
commit
fbf3c4102d
|
@ -40,7 +40,7 @@ export async function login(state: FormState, formData: FormData) {
|
||||||
|
|
||||||
await createSession(user._id)
|
await createSession(user._id)
|
||||||
// 5. Redirect user
|
// 5. Redirect user
|
||||||
redirect('/admin')p
|
redirect('/admin')
|
||||||
|
|
||||||
}
|
}
|
||||||
export async function logout() {
|
export async function logout() {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
import { getCollection } from "../mongodb";
|
||||||
|
|
||||||
|
export type ArticleType = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
link: string;
|
||||||
|
_id: string;
|
||||||
|
content: string;
|
||||||
|
priority: number;
|
||||||
|
cover: string;
|
||||||
|
addTime: number;
|
||||||
|
}
|
||||||
|
export async function getArticleList({ page = 1, pageSize = 9999 }: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
}) {
|
||||||
|
const collection = await getCollection('article');
|
||||||
|
const startIndex = (page - 1) * pageSize;
|
||||||
|
// 构建查询条件对象
|
||||||
|
|
||||||
|
const pipeline = [
|
||||||
|
// 根据构建好的查询条件筛选文档
|
||||||
|
{ $sort: { priority: 1 } },
|
||||||
|
{ $skip: startIndex },
|
||||||
|
{ $limit: pageSize },
|
||||||
|
{
|
||||||
|
$addFields: {
|
||||||
|
_id: { $toString: "$_id" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const cursor = collection.aggregate<ArticleType>(pipeline);
|
||||||
|
const list = await cursor.toArray();
|
||||||
|
// 获取符合查询条件的文档总数
|
||||||
|
const total = await collection.countDocuments();
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function getArticle(id: string) {
|
||||||
|
const collection = await getCollection('article');
|
||||||
|
return collection.findOne<ArticleType>({ _id: new ObjectId(id) })
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ export type LinkType = {
|
||||||
iconElement?: ReactNode;
|
iconElement?: ReactNode;
|
||||||
_id: string;
|
_id: string;
|
||||||
href?: string;
|
href?: string;
|
||||||
priority: number;
|
priority?: number;
|
||||||
location?: string;
|
location?: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getCollection } from "../mongodb";
|
||||||
|
|
||||||
export type SearchTypeItem = {
|
export type SearchTypeItem = {
|
||||||
name: string;
|
name: string;
|
||||||
key: string;
|
key?: string;
|
||||||
includes: string[];
|
includes: string[];
|
||||||
_id: string;
|
_id: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
|
@ -63,7 +63,7 @@ export async function getSearchWayList({ page = 1, pageSize = 9999 }: {
|
||||||
const list = await cursor.toArray();
|
const list = await cursor.toArray();
|
||||||
|
|
||||||
// 计算总数量
|
// 计算总数量
|
||||||
const total = (await collection.find<SearchWayItemType>({}).toArray()).length
|
const total = await collection.countDocuments()
|
||||||
return {
|
return {
|
||||||
total,
|
total,
|
||||||
list
|
list
|
||||||
|
|
|
@ -16,12 +16,15 @@ export async function encrypt(payload: SessionPayload) {
|
||||||
|
|
||||||
export async function decrypt(session: string | undefined = '') {
|
export async function decrypt(session: string | undefined = '') {
|
||||||
try {
|
try {
|
||||||
|
if (!session) {
|
||||||
|
throw new Error('Session is empty or undefined');
|
||||||
|
}
|
||||||
const { payload } = await jwtVerify(session, encodedKey, {
|
const { payload } = await jwtVerify(session, encodedKey, {
|
||||||
algorithms: ['HS256'],
|
algorithms: ['HS256'],
|
||||||
})
|
})
|
||||||
return payload
|
return payload
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Failed to verify session', error)
|
console.log('Failed to verify session')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function createSession(userId: string) {
|
export async function createSession(userId: string) {
|
||||||
|
@ -42,21 +45,21 @@ export async function updateSession() {
|
||||||
const payload = await decrypt(session)
|
const payload = await decrypt(session)
|
||||||
|
|
||||||
if (!session || !payload) {
|
if (!session || !payload) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
||||||
|
|
||||||
const cookieStore = await cookies()
|
const cookieStore = await cookies()
|
||||||
cookieStore.set('session', session, {
|
cookieStore.set('session', session, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
expires: expires,
|
expires: expires,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
path: '/',
|
path: '/',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export async function deleteSession() {
|
export async function deleteSession() {
|
||||||
const cookieStore = await cookies()
|
const cookieStore = await cookies()
|
||||||
cookieStore.delete('session')
|
cookieStore.delete('session')
|
||||||
}
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export function doSearch(url: string, keyword: string) {
|
||||||
|
|
||||||
|
window.open(url.replace(/%s/g, keyword), "_blank")
|
||||||
|
|
||||||
|
}
|
|
@ -3,84 +3,51 @@
|
||||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import Logo from "./Logo";
|
import Logo from "./Logo";
|
||||||
export type SearchTypeItem = {
|
import { useRequest } from "ahooks";
|
||||||
name: string;
|
import { mRequest } from "../_lib/request";
|
||||||
key: string;
|
import { SearchTypeItem, SearchWayItemType } from "../_lib/data/search";
|
||||||
includes: string[];
|
import { doSearch } from "../_lib/utils";
|
||||||
priority: number;
|
import { log } from "node:console";
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
export type SearchWayItemType = {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
fullName: string;
|
|
||||||
key: string;
|
|
||||||
|
|
||||||
}
|
|
||||||
export const SearchTypeList: SearchTypeItem[] = [
|
|
||||||
{
|
|
||||||
name: '常用',
|
|
||||||
key: 'custom',
|
|
||||||
includes: ['inside', 'bing'],
|
|
||||||
priority: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '搜索',
|
|
||||||
key: 'search',
|
|
||||||
includes: ['101'],
|
|
||||||
priority: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '社区',
|
|
||||||
key: 'community',
|
|
||||||
includes: ['101'],
|
|
||||||
priority: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '图片',
|
|
||||||
key: 'picture',
|
|
||||||
includes: ['101'],
|
|
||||||
priority: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '生活',
|
|
||||||
key: 'life',
|
|
||||||
includes: ['101'],
|
|
||||||
priority: 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
export const SearchWayItem: SearchWayItemType[] = [
|
|
||||||
{
|
|
||||||
label: '站内',
|
|
||||||
value: '',
|
|
||||||
fullName: '站内资源',
|
|
||||||
key: 'inside',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Bing',
|
|
||||||
fullName: '站内资源',
|
|
||||||
value: 'https://bing.com',
|
|
||||||
key: 'bing'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
const [selectKey, setSelectKey] = useState(SearchTypeList[0].key)
|
|
||||||
|
const { data: searchWayList = [] } = useRequest(() => mRequest<{ list: SearchWayItemType[] }>('GET', '/api/search?page=1&pageSize=999').then(res => res.list))
|
||||||
|
const { data: searchTypeList = [] } = useRequest(() => mRequest<{ list: SearchTypeItem[] }>('GET', '/api/searchType?page=1&pageSize=999').then(res => res.list))
|
||||||
|
const [selectKey, setSelectKey] = useState<string | null>(null)
|
||||||
|
const [activeSearchKey, setActiveSearchKey] = useState<string | null>(null)
|
||||||
|
const [inputStr, setInputStr] = useState('')
|
||||||
const nowSelectConfig = useMemo(() => {
|
const nowSelectConfig = useMemo(() => {
|
||||||
const idx = SearchTypeList.findIndex(val => val.key === selectKey)
|
const idx = searchTypeList.findIndex(val => val._id === selectKey)
|
||||||
if (idx !== -1) return SearchTypeList[idx]
|
if (idx !== -1) return searchTypeList[idx]
|
||||||
else return null
|
else return null
|
||||||
}, [selectKey])
|
}, [selectKey, searchTypeList])
|
||||||
const [activeSearchKey, setActiveSearchKey] = useState(nowSelectConfig?.includes[0])
|
const filterSeachList = useMemo(() => {
|
||||||
|
return searchWayList.filter(val => nowSelectConfig?.includes.includes(val._id))
|
||||||
|
}, [searchWayList, nowSelectConfig])
|
||||||
const activeSearch = useMemo(() => {
|
const activeSearch = useMemo(() => {
|
||||||
const idx = SearchWayItem.findIndex(val => val.key === activeSearchKey)
|
const idx = searchWayList.findIndex(val => val._id === activeSearchKey)
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
return SearchWayItem[idx]
|
return searchWayList[idx]
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}, [activeSearchKey])
|
}, [activeSearchKey, searchWayList])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchTypeList.length > 0) {
|
||||||
|
setSelectKey(searchTypeList[0]._id)
|
||||||
|
|
||||||
|
}
|
||||||
|
}, [searchTypeList])
|
||||||
|
useEffect(() => {
|
||||||
|
if (filterSeachList.length > 0) {
|
||||||
|
setActiveSearchKey(filterSeachList[0]._id)
|
||||||
|
}
|
||||||
|
}, [selectKey])
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex justify-center flex-col items-center py-10 h-[500px]">
|
<div className="w-full flex justify-center flex-col items-center py-10 h-[500px]">
|
||||||
|
|
||||||
|
@ -90,12 +57,12 @@ export default function Search() {
|
||||||
<div className="w-full lg:w-[800px] flex flex-col gap-y-2">
|
<div className="w-full lg:w-[800px] flex flex-col gap-y-2">
|
||||||
<div className="flex w-full justify-center gap-x-5 text-[#666666]">
|
<div className="flex w-full justify-center gap-x-5 text-[#666666]">
|
||||||
{
|
{
|
||||||
SearchTypeList.map(item => (
|
searchTypeList.map(item => (
|
||||||
<div key={item.key}
|
<div key={item._id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectKey(item.key)
|
setSelectKey(item._id)
|
||||||
}}
|
}}
|
||||||
className={clsx(item.key === selectKey ?
|
className={clsx(item._id === selectKey ?
|
||||||
"text-[#333]" : "text-[#999] cursor-pointer")}>
|
"text-[#333]" : "text-[#999] cursor-pointer")}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,15 +70,28 @@ export default function Search() {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full relative">
|
<div className="w-full relative">
|
||||||
<input type="text" placeholder={`${activeSearch?.label}搜索`} className="w-full bg-[#C4C2C6] px-4 h-[50px] rounded-3xl outline-none" />
|
<input
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (!activeSearch?.value) return
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
doSearch(activeSearch?.value, inputStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
onChange={e => {
|
||||||
|
setInputStr(e.target.value)
|
||||||
|
|
||||||
|
}}
|
||||||
|
placeholder={`${activeSearch?.label}搜索`} className="w-full bg-[#C4C2C6] px-4 h-[50px] rounded-3xl outline-none" />
|
||||||
<FontAwesomeIcon className="text-white absolute top-1/2 -translate-y-1/2 right-6 text-xl" icon={faSearch}></FontAwesomeIcon>
|
<FontAwesomeIcon className="text-white absolute top-1/2 -translate-y-1/2 right-6 text-xl" icon={faSearch}></FontAwesomeIcon>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex justify-center gap-x-4 ">
|
<div className="w-full flex justify-center gap-x-4 ">
|
||||||
{
|
{
|
||||||
SearchWayItem.filter(val => nowSelectConfig?.includes.includes(val.key)).map(item => (
|
filterSeachList.map(item => (
|
||||||
<div key={item.key} onClick={() => {
|
<div key={item._id} onClick={() => {
|
||||||
setActiveSearchKey(item.key)
|
setActiveSearchKey(item._id)
|
||||||
}} className={clsx(activeSearchKey === item.key ?
|
}} className={clsx(activeSearchKey === item._id ?
|
||||||
"text-[#333]" : "text-[#999] cursor-pointer")}>{item.label}</div>
|
"text-[#333]" : "text-[#999] cursor-pointer")}>{item.label}</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,36 @@
|
||||||
|
"use client";
|
||||||
|
import { Button, Card, Space, Table } from "antd";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<>ad</>
|
<Card title="广告管理" extra={<Button type="primary">新增广告</Button>}>
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: '标题',
|
||||||
|
dataIndex: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '链接',
|
||||||
|
dataIndex: 'link',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '图片',
|
||||||
|
dataIndex: 'image',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
render: (_, row) => (
|
||||||
|
<Space>
|
||||||
|
<Button type="primary">修改</Button>
|
||||||
|
<Button type="primary">删除</Button>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
"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>
|
||||||
|
</>
|
||||||
|
}
|
|
@ -1,5 +1,42 @@
|
||||||
|
"use client"
|
||||||
|
import { Button, Card, Space, Table } from "antd";
|
||||||
|
import '@ant-design/v5-patch-for-react-19';
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
|
const router = useRouter()
|
||||||
return (
|
return (
|
||||||
<>文章管理</>
|
<Card title="文章管理" extra={<Button type="primary" onClick={() => {
|
||||||
|
router.push("/admin/dashboard/article/add")
|
||||||
|
}} >
|
||||||
|
新增文章
|
||||||
|
</Button>}>
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: '标题',
|
||||||
|
dataIndex: 'title',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '作者',
|
||||||
|
dataIndex: 'author',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '内容',
|
||||||
|
dataIndex: 'content',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
render: (_, row) => (
|
||||||
|
<Space>
|
||||||
|
<Button type="primary">修改</Button>
|
||||||
|
<Button type="primary">删除</Button>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
|
||||||
|
></Table>
|
||||||
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { Button, Card, Drawer, Form, Image, Input, InputNumber, message, Modal, Popconfirm, Space, Table } from "antd";
|
import { Button, Card, Drawer, Form, Image, Input, InputNumber, message, Modal, Popconfirm, Space, Table } from "antd";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import '@ant-design/v5-patch-for-react-19';
|
|
||||||
import { useAntdTable } from "ahooks";
|
import { useAntdTable } from "ahooks";
|
||||||
import { mRequest } from "@/app/_lib/request";
|
import { mRequest } from "@/app/_lib/request";
|
||||||
import { LinkTypeItem } from "@/app/_lib/types";
|
import { LinkTypeItem } from "@/app/_lib/types";
|
||||||
|
@ -9,6 +8,7 @@ import LinkTable from "./LinkTable";
|
||||||
import ImageUpload from "@/app/_ui/ImageUpload";
|
import ImageUpload from "@/app/_ui/ImageUpload";
|
||||||
import { useForm } from "antd/es/form/Form";
|
import { useForm } from "antd/es/form/Form";
|
||||||
import { LinkType } from "@/app/_lib/data/linkType";
|
import { LinkType } from "@/app/_lib/data/linkType";
|
||||||
|
import '@ant-design/v5-patch-for-react-19';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const { tableProps, refresh } = useAntdTable(
|
const { tableProps, refresh } = useAntdTable(
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { SearchWayItemType } from "@/app/_lib/data/search";
|
||||||
|
import { mRequest } from "@/app/_lib/request";
|
||||||
|
import { useRequest } from "ahooks";
|
||||||
|
import { Checkbox, Space } from "antd";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function SearchSelect({ value, onChange }: { value?: string[]; onChange?: (e: any) => void }) {
|
||||||
|
const { data } = useRequest(() => mRequest<{
|
||||||
|
list: SearchWayItemType[]
|
||||||
|
}>('GET', '/api/search?page=1&pageSize=999'))
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return data?.list.map(val => ({
|
||||||
|
label: val.fullName,
|
||||||
|
value: val._id
|
||||||
|
}))
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox.Group
|
||||||
|
value={value}
|
||||||
|
options={options}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,38 +1,41 @@
|
||||||
|
"use client";
|
||||||
import { SearchTypeItem, SearchWayItemType } from "@/app/_lib/data/search";
|
import { SearchTypeItem, SearchWayItemType } from "@/app/_lib/data/search";
|
||||||
import { mRequest } from "@/app/_lib/request";
|
import { mRequest } from "@/app/_lib/request";
|
||||||
import { useAntdTable, useRequest } from "ahooks";
|
import { useAntdTable } from "ahooks";
|
||||||
import { Button, Card, Space, Table } from "antd";
|
import { Button, Card, Drawer, Form, Input, InputNumber, message, Popconfirm, Space, Table } from "antd";
|
||||||
|
import { useState } from "react";
|
||||||
|
import '@ant-design/v5-patch-for-react-19';
|
||||||
|
import SearchSelect from "./SearchSelect";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const searchWayList = useRequest(async () => mRequest('GET', "/api/search"))
|
const { tableProps, refresh } = useAntdTable(async ({ current, pageSize }) => mRequest<{
|
||||||
const { data } = useAntdTable(async () => mRequest<{
|
total: number;
|
||||||
|
list: SearchWayItemType[]
|
||||||
|
}>('GET', `/api/search?page=${current}&pageSize=${pageSize}`))
|
||||||
|
const { tableProps: typeTableProps, refresh: refreshTypeList } = useAntdTable(async ({ current, pageSize }) => mRequest<{
|
||||||
total: number;
|
total: number;
|
||||||
list: SearchTypeItem[]
|
list: SearchTypeItem[]
|
||||||
}>('GET', "/api/"))
|
}>('GET', `/api/searchType?page=${current}&pageSize=${pageSize}`))
|
||||||
return <Space>
|
const [selected, setSelected] = useState<SearchWayItemType | null | undefined>()
|
||||||
{/*
|
const [selectedType, setSelectedType] = useState<SearchTypeItem | null | undefined>()
|
||||||
<Card title="搜索分类管理" extra={<Button type="primary">添加</Button>}>
|
const [viewMode, setViewMode] = useState(false)
|
||||||
<Table<SearchTypeItem>
|
return <>
|
||||||
columns={[
|
<Card title="搜索引擎管理"
|
||||||
{
|
extra={
|
||||||
title: '分类名称',
|
<Space>
|
||||||
dataIndex: 'name'
|
<Button onClick={() => {
|
||||||
},
|
refresh()
|
||||||
{
|
}}>刷新</Button>
|
||||||
title: '标示符',
|
<Button type="primary" onClick={() => {
|
||||||
dataIndex: 'key',
|
setSelected(null)
|
||||||
|
}}>新增搜索引擎</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title:
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
|
|
||||||
</Table>
|
}>
|
||||||
</Card> */}
|
|
||||||
<Card title="搜索引擎管理">
|
|
||||||
<Table<SearchWayItemType>
|
<Table<SearchWayItemType>
|
||||||
|
{...tableProps}
|
||||||
|
rowKey={'_id'}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
|
@ -46,6 +49,30 @@ export default function Page() {
|
||||||
{
|
{
|
||||||
title: '搜索链接',
|
title: '搜索链接',
|
||||||
dataIndex: 'value'
|
dataIndex: 'value'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
render: (_, row) => (
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" onClick={() => {
|
||||||
|
setSelected(row)
|
||||||
|
}}>修改</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除"
|
||||||
|
description="是否删除该项?"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
onConfirm={() => {
|
||||||
|
mRequest('DELETE', `/api/search/${row._id}`).then(res => {
|
||||||
|
refresh()
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button danger >删除</Button>
|
||||||
|
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -53,6 +80,214 @@ export default function Page() {
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card
|
||||||
|
className="mt-2"
|
||||||
|
title="搜索分类管理"
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => {
|
||||||
|
refreshTypeList()
|
||||||
|
}}>刷新</Button>
|
||||||
|
<Button type="primary" onClick={() => {
|
||||||
|
setSelectedType(null)
|
||||||
|
}}>新增搜索分类</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
|
||||||
|
}>
|
||||||
|
<Table<SearchTypeItem>
|
||||||
|
{...typeTableProps}
|
||||||
|
rowKey={'_id'}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
width: 400
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '优先级',
|
||||||
|
dataIndex: 'priority'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
render: (_, row) => (
|
||||||
|
<Space>
|
||||||
|
<Button onClick={() => {
|
||||||
|
console.log(row);
|
||||||
|
setViewMode(true)
|
||||||
|
setSelectedType(row)
|
||||||
|
|
||||||
|
}}>详请</Button>
|
||||||
|
<Button type="primary" onClick={() => {
|
||||||
|
setSelectedType(row)
|
||||||
|
}}>修改</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除"
|
||||||
|
description="是否删除该项?"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
onConfirm={() => {
|
||||||
|
mRequest('DELETE', `/api/searchType/${row._id}`).then(res => {
|
||||||
|
refreshTypeList()
|
||||||
|
message.success('删除成功')
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button danger >删除</Button>
|
||||||
|
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
</Card>
|
||||||
|
<Drawer
|
||||||
|
destroyOnClose
|
||||||
|
title={selected !== undefined ? selected === null ? "添加搜索引擎" : "修改搜索引擎" : ""}
|
||||||
|
open={selected !== undefined}
|
||||||
|
width={500}
|
||||||
|
onClose={() => {
|
||||||
|
setSelected(undefined)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
labelCol={{ span: 4 }}
|
||||||
|
initialValues={selected ?
|
||||||
|
selected
|
||||||
|
: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onFinish={async (res) => {
|
||||||
|
if (selected) {
|
||||||
|
await mRequest("PUT", "/api/search", {
|
||||||
|
_id: selected._id, ...res
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await mRequest("POST", "/api/search", {
|
||||||
|
...res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
setSelected(undefined)
|
||||||
|
message.success("操作成功")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="label"
|
||||||
|
label="简称"
|
||||||
|
rules={[{ required: true, message: "请输入名称" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="fullName"
|
||||||
|
label="全称"
|
||||||
|
rules={[{ required: true, message: "请输入名称" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="value"
|
||||||
|
label="链接"
|
||||||
|
|
||||||
|
rules={[{ required: true, message: "请输入名称" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="关键词以%s代替" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item className="flex justify-end">
|
||||||
|
<Space>
|
||||||
|
<Button>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
<Drawer
|
||||||
|
destroyOnClose
|
||||||
|
title={viewMode ? '查看详请' : (selectedType !== undefined ? selectedType === null ? "添加搜索分类" : "修改搜索分类" : "")}
|
||||||
|
open={selectedType !== undefined}
|
||||||
|
width={500}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedType(undefined)
|
||||||
|
setViewMode(false)
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
disabled={viewMode}
|
||||||
|
labelCol={{ span: 4 }}
|
||||||
|
initialValues={selectedType ?
|
||||||
|
selectedType
|
||||||
|
: {
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFinish={async (res) => {
|
||||||
|
if (selectedType) {
|
||||||
|
await mRequest("PUT", "/api/searchType", {
|
||||||
|
_id: selectedType._id, ...res
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await mRequest("POST", "/api/searchType", {
|
||||||
|
...res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refreshTypeList()
|
||||||
|
setSelectedType(undefined)
|
||||||
|
message.success("操作成功")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="分类名"
|
||||||
|
rules={[{ required: true, message: "请输入名称" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="includes"
|
||||||
|
label="启用引擎"
|
||||||
|
|
||||||
|
>
|
||||||
|
<SearchSelect ></SearchSelect>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="priority"
|
||||||
|
label="优先级"
|
||||||
|
|
||||||
|
>
|
||||||
|
<InputNumber ></InputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
{
|
||||||
|
!viewMode &&
|
||||||
|
<Form.Item className="flex justify-end">
|
||||||
|
<Space>
|
||||||
|
<Button>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Form>
|
||||||
|
</Drawer >
|
||||||
|
</>
|
||||||
|
|
||||||
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import LoginState from "@/app/_ui/LoginState";
|
import LoginState from "@/app/_ui/LoginState";
|
||||||
import SiderNav from "../../_ui/SiderNav";
|
import SiderNav from "../../_ui/SiderNav";
|
||||||
import { faAd, faArrowAltCircleLeft, faMagnet, faPenClip } 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"
|
||||||
|
|
||||||
export default function Layout({
|
export default function Layout({
|
||||||
|
@ -9,10 +9,10 @@ export default function Layout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<SiderNav
|
<SiderNav
|
||||||
|
|
||||||
linkList={[
|
linkList={[
|
||||||
{
|
{
|
||||||
label: '链接管理',
|
label: '链接管理',
|
||||||
|
@ -20,6 +20,12 @@ export default function Layout({
|
||||||
_id: 'addLink',
|
_id: 'addLink',
|
||||||
href: '/admin/dashboard'
|
href: '/admin/dashboard'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '搜索管理',
|
||||||
|
iconElement: <FontAwesomeIcon icon={faSearch}></FontAwesomeIcon>,
|
||||||
|
_id: 'searchMenagement',
|
||||||
|
href: '/admin/dashboard/search'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '广告管理',
|
label: '广告管理',
|
||||||
iconElement: <FontAwesomeIcon icon={faAd}></FontAwesomeIcon>,
|
iconElement: <FontAwesomeIcon icon={faAd}></FontAwesomeIcon>,
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { verifySession } from "@/app/_lib/dal";
|
||||||
|
import { getCollection } from "@/app/_lib/mongodb";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
|
try {
|
||||||
|
const session = await verifySession()
|
||||||
|
|
||||||
|
// Check if the user is authenticated
|
||||||
|
if (!session) {
|
||||||
|
// User is not authenticated
|
||||||
|
return new Response(null, { status: 401 })
|
||||||
|
}
|
||||||
|
// 获取路径参数
|
||||||
|
const slug = (await params).id
|
||||||
|
const collection = await getCollection('article')
|
||||||
|
collection.deleteOne({
|
||||||
|
_id: new ObjectId(slug)
|
||||||
|
})
|
||||||
|
return Response.json({ message: '删除成功' })
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { getArticleList } from "@/app/_lib/data/article";
|
||||||
|
import { getCollection } from "@/app/_lib/mongodb";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
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 })
|
||||||
|
return Response.json(res)
|
||||||
|
}
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const collection = await getCollection('article');
|
||||||
|
const data = await collection.insertOne(body);
|
||||||
|
return Response.json(data)
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function PUT(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const collection = await getCollection('article');
|
||||||
|
const data = await collection.updateOne({ _id: body._id }, { $set: body });
|
||||||
|
return Response.json(data)
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import { getCollection, getDb } from "@/app/_lib/mongodb";
|
import { getCollection, getDb } from "@/app/_lib/mongodb";
|
||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { Link } from "../route";
|
|
||||||
import { verifySession } from "@/app/_lib/dal";
|
import { verifySession } from "@/app/_lib/dal";
|
||||||
|
import { Link } from "@/app/_lib/data/link";
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -17,6 +17,7 @@ export async function GET(req: NextRequest) {
|
||||||
return Response.error()
|
return Response.error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
try {
|
try {
|
||||||
const session = await verifySession()
|
const session = await verifySession()
|
||||||
|
@ -28,7 +29,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
||||||
}
|
}
|
||||||
// 获取路径参数
|
// 获取路径参数
|
||||||
const slug = (await params).id
|
const slug = (await params).id
|
||||||
const collection = await getCollection('link')
|
const collection = await getCollection('search')
|
||||||
collection.deleteOne({
|
collection.deleteOne({
|
||||||
_id: new ObjectId(slug)
|
_id: new ObjectId(slug)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { verifySession } from "@/app/_lib/dal";
|
import { verifySession } from "@/app/_lib/dal";
|
||||||
import { Link } from "@/app/_lib/data/link";
|
import { Link } from "@/app/_lib/data/link";
|
||||||
import { getSearchWayList } from "@/app/_lib/data/search";
|
import { getSearchWayList } from "@/app/_lib/data/search";
|
||||||
import { User } from "@/app/_lib/data/user";
|
|
||||||
import { getCollection, getDb } from "@/app/_lib/mongodb";
|
import { getCollection, getDb } from "@/app/_lib/mongodb";
|
||||||
import { message } from "antd";
|
|
||||||
import bcrypt from 'bcrypt';
|
|
||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
@ -15,7 +12,7 @@ export async function GET(req: NextRequest) {
|
||||||
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
||||||
const pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
const pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
||||||
|
|
||||||
const res = getSearchWayList({ page, pageSize })
|
const res = await getSearchWayList({ page, pageSize })
|
||||||
return Response.json(res)
|
return Response.json(res)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error()
|
return Response.error()
|
||||||
|
@ -50,11 +47,15 @@ export async function PUT(req: NextRequest) {
|
||||||
// User is not authenticated
|
// User is not authenticated
|
||||||
return new Response(null, { status: 401 })
|
return new Response(null, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 获取待更新的对象
|
// 获取待更新的对象
|
||||||
const link = await req.json() as Link
|
const link = await req.json() as Link
|
||||||
const collection = await getCollection('link')
|
const collection = await getCollection('search')
|
||||||
await collection.replaceOne({ _id: new ObjectId(link._id) }, { ...link, _id: new ObjectId(link._id) })
|
await collection.replaceOne({ _id: new ObjectId(link._id) }, { ...link, _id: new ObjectId(link._id) })
|
||||||
|
|
||||||
return Response.json({ message: '成功' })
|
return Response.json({ message: '成功' })
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error()
|
return Response.error()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ export async function GET(req: NextRequest) {
|
||||||
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
||||||
const pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
const pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
||||||
|
|
||||||
const res = getSearchTypeList({ page, pageSize })
|
const res = await getSearchTypeList({ page, pageSize })
|
||||||
return Response.json(res)
|
return Response.json(res)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Response.error()
|
return Response.error()
|
||||||
|
@ -27,7 +27,7 @@ export async function POST(req: NextRequest) {
|
||||||
return new Response(null, { status: 401 })
|
return new Response(null, { status: 401 })
|
||||||
}
|
}
|
||||||
const collection = await getCollection('search-type')
|
const collection = await getCollection('search-type')
|
||||||
const item = req.json()
|
const item = await req.json()
|
||||||
|
|
||||||
const res = collection.insertOne(item)
|
const res = collection.insertOne(item)
|
||||||
return Response.json(res)
|
return Response.json(res)
|
||||||
|
|
|
@ -19,3 +19,12 @@ body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
.ant-input-disabled,
|
||||||
|
.ant-select-disabled .ant-select-selector {
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: rgba(0, 0, 0, 0.85) !important;
|
||||||
|
}
|
||||||
|
.ant-checkbox-disabled+span{
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: rgba(0, 0, 0, 0.85) !important;
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ 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",
|
||||||
|
|
10
app/page.tsx
10
app/page.tsx
|
@ -1,15 +1,5 @@
|
||||||
import HeaderNav from "./_ui/HeaderNav";
|
|
||||||
import SiderNav from "./_ui/SiderNav";
|
import SiderNav from "./_ui/SiderNav";
|
||||||
import Search from "./_ui/Search";
|
import Search from "./_ui/Search";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { faArrowRotateBack, faDeafness, faImage, faMagnet, faMessage, faPenClip, faSearch, faThumbsUp, faVideo } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import { LinkTypeItem } from "./_lib/types";
|
|
||||||
import PosterBox from "./_ui/PosterBox";
|
|
||||||
import { mRequest } from "./_lib/request";
|
|
||||||
import { LinkType } from "./api/linkType/route";
|
|
||||||
import { getCollection } from "./_lib/mongodb";
|
|
||||||
import { Link as _Link } from "./api/link/route";
|
|
||||||
import Link from "next/link";
|
|
||||||
import LinkListBox from "./_ui/LinkListBox";
|
import LinkListBox from "./_ui/LinkListBox";
|
||||||
import { getLinkTypeList } from "./_lib/data/linkType";
|
import { getLinkTypeList } from "./_lib/data/linkType";
|
||||||
import { getLinkList } from "./_lib/data/link";
|
import { getLinkList } from "./_lib/data/link";
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"@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",
|
||||||
|
"@uiw/react-md-editor": "^4.0.5",
|
||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"ali-oss": "^6.22.0",
|
"ali-oss": "^6.22.0",
|
||||||
"antd": "^5.23.2",
|
"antd": "^5.23.2",
|
||||||
|
|
1276
pnpm-lock.yaml
1276
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue