完成了文章的管理
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)
|
||||
// 5. Redirect user
|
||||
redirect('/admin')p
|
||||
redirect('/admin')
|
||||
|
||||
}
|
||||
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;
|
||||
_id: string;
|
||||
href?: string;
|
||||
priority: number;
|
||||
priority?: number;
|
||||
location?: string;
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { getCollection } from "../mongodb";
|
|||
|
||||
export type SearchTypeItem = {
|
||||
name: string;
|
||||
key: string;
|
||||
key?: string;
|
||||
includes: string[];
|
||||
_id: string;
|
||||
priority: number;
|
||||
|
@ -63,7 +63,7 @@ export async function getSearchWayList({ page = 1, pageSize = 9999 }: {
|
|||
const list = await cursor.toArray();
|
||||
|
||||
// 计算总数量
|
||||
const total = (await collection.find<SearchWayItemType>({}).toArray()).length
|
||||
const total = await collection.countDocuments()
|
||||
return {
|
||||
total,
|
||||
list
|
||||
|
|
|
@ -16,12 +16,15 @@ export async function encrypt(payload: SessionPayload) {
|
|||
|
||||
export async function decrypt(session: string | undefined = '') {
|
||||
try {
|
||||
if (!session) {
|
||||
throw new Error('Session is empty or undefined');
|
||||
}
|
||||
const { payload } = await jwtVerify(session, encodedKey, {
|
||||
algorithms: ['HS256'],
|
||||
})
|
||||
return payload
|
||||
} catch (error) {
|
||||
console.log('Failed to verify session', error)
|
||||
console.log('Failed to verify session')
|
||||
}
|
||||
}
|
||||
export async function createSession(userId: string) {
|
||||
|
@ -40,23 +43,23 @@ export async function createSession(userId: string) {
|
|||
export async function updateSession() {
|
||||
const session = (await cookies()).get('session')?.value
|
||||
const payload = await decrypt(session)
|
||||
|
||||
|
||||
if (!session || !payload) {
|
||||
return null
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
|
||||
|
||||
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.set('session', session, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
expires: expires,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
expires: expires,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
})
|
||||
}
|
||||
export async function deleteSession() {
|
||||
}
|
||||
export async function deleteSession() {
|
||||
const cookieStore = await cookies()
|
||||
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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import clsx from "clsx";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import Logo from "./Logo";
|
||||
export type SearchTypeItem = {
|
||||
name: string;
|
||||
key: string;
|
||||
includes: string[];
|
||||
priority: number;
|
||||
import { useRequest } from "ahooks";
|
||||
import { mRequest } from "../_lib/request";
|
||||
import { SearchTypeItem, SearchWayItemType } from "../_lib/data/search";
|
||||
import { doSearch } from "../_lib/utils";
|
||||
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() {
|
||||
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 idx = SearchTypeList.findIndex(val => val.key === selectKey)
|
||||
if (idx !== -1) return SearchTypeList[idx]
|
||||
const idx = searchTypeList.findIndex(val => val._id === selectKey)
|
||||
if (idx !== -1) return searchTypeList[idx]
|
||||
else return null
|
||||
}, [selectKey])
|
||||
const [activeSearchKey, setActiveSearchKey] = useState(nowSelectConfig?.includes[0])
|
||||
}, [selectKey, searchTypeList])
|
||||
const filterSeachList = useMemo(() => {
|
||||
return searchWayList.filter(val => nowSelectConfig?.includes.includes(val._id))
|
||||
}, [searchWayList, nowSelectConfig])
|
||||
const activeSearch = useMemo(() => {
|
||||
const idx = SearchWayItem.findIndex(val => val.key === activeSearchKey)
|
||||
const idx = searchWayList.findIndex(val => val._id === activeSearchKey)
|
||||
if (idx !== -1) {
|
||||
return SearchWayItem[idx]
|
||||
return searchWayList[idx]
|
||||
} else {
|
||||
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 (
|
||||
<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="flex w-full justify-center gap-x-5 text-[#666666]">
|
||||
{
|
||||
SearchTypeList.map(item => (
|
||||
<div key={item.key}
|
||||
searchTypeList.map(item => (
|
||||
<div key={item._id}
|
||||
onClick={() => {
|
||||
setSelectKey(item.key)
|
||||
setSelectKey(item._id)
|
||||
}}
|
||||
className={clsx(item.key === selectKey ?
|
||||
className={clsx(item._id === selectKey ?
|
||||
"text-[#333]" : "text-[#999] cursor-pointer")}>
|
||||
{item.name}
|
||||
</div>
|
||||
|
@ -103,15 +70,28 @@ export default function Search() {
|
|||
}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div className="w-full flex justify-center gap-x-4 ">
|
||||
{
|
||||
SearchWayItem.filter(val => nowSelectConfig?.includes.includes(val.key)).map(item => (
|
||||
<div key={item.key} onClick={() => {
|
||||
setActiveSearchKey(item.key)
|
||||
}} className={clsx(activeSearchKey === item.key ?
|
||||
filterSeachList.map(item => (
|
||||
<div key={item._id} onClick={() => {
|
||||
setActiveSearchKey(item._id)
|
||||
}} className={clsx(activeSearchKey === item._id ?
|
||||
"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() {
|
||||
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() {
|
||||
const router = useRouter()
|
||||
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";
|
||||
import { Button, Card, Drawer, Form, Image, Input, InputNumber, message, Modal, Popconfirm, Space, Table } from "antd";
|
||||
import { useRef, useState } from "react";
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
import { useAntdTable } from "ahooks";
|
||||
import { mRequest } from "@/app/_lib/request";
|
||||
import { LinkTypeItem } from "@/app/_lib/types";
|
||||
|
@ -9,6 +8,7 @@ import LinkTable from "./LinkTable";
|
|||
import ImageUpload from "@/app/_ui/ImageUpload";
|
||||
import { useForm } from "antd/es/form/Form";
|
||||
import { LinkType } from "@/app/_lib/data/linkType";
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
|
||||
export default function Page() {
|
||||
const { tableProps, refresh } = useAntdTable(
|
||||
|
@ -187,7 +187,7 @@ export default function Page() {
|
|||
</Form.Item>
|
||||
</Form>
|
||||
</Drawer>
|
||||
|
||||
|
||||
{/* <Drawer
|
||||
forceRender
|
||||
title={'添加图标'}
|
||||
|
|
|
@ -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 { mRequest } from "@/app/_lib/request";
|
||||
import { useAntdTable, useRequest } from "ahooks";
|
||||
import { Button, Card, Space, Table } from "antd";
|
||||
import { useAntdTable } from "ahooks";
|
||||
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() {
|
||||
const searchWayList = useRequest(async () => mRequest('GET', "/api/search"))
|
||||
const { data } = useAntdTable(async () => mRequest<{
|
||||
const { tableProps, refresh } = useAntdTable(async ({ current, pageSize }) => mRequest<{
|
||||
total: number;
|
||||
list: SearchWayItemType[]
|
||||
}>('GET', `/api/search?page=${current}&pageSize=${pageSize}`))
|
||||
const { tableProps: typeTableProps, refresh: refreshTypeList } = useAntdTable(async ({ current, pageSize }) => mRequest<{
|
||||
total: number;
|
||||
list: SearchTypeItem[]
|
||||
}>('GET', "/api/"))
|
||||
return <Space>
|
||||
{/*
|
||||
<Card title="搜索分类管理" extra={<Button type="primary">添加</Button>}>
|
||||
<Table<SearchTypeItem>
|
||||
columns={[
|
||||
{
|
||||
title: '分类名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '标示符',
|
||||
dataIndex: 'key',
|
||||
}>('GET', `/api/searchType?page=${current}&pageSize=${pageSize}`))
|
||||
const [selected, setSelected] = useState<SearchWayItemType | null | undefined>()
|
||||
const [selectedType, setSelectedType] = useState<SearchTypeItem | null | undefined>()
|
||||
const [viewMode, setViewMode] = useState(false)
|
||||
return <>
|
||||
<Card title="搜索引擎管理"
|
||||
extra={
|
||||
<Space>
|
||||
<Button onClick={() => {
|
||||
refresh()
|
||||
}}>刷新</Button>
|
||||
<Button type="primary" onClick={() => {
|
||||
setSelected(null)
|
||||
}}>新增搜索引擎</Button>
|
||||
</Space>
|
||||
|
||||
},
|
||||
{
|
||||
title:
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
</Table>
|
||||
</Card> */}
|
||||
<Card title="搜索引擎管理">
|
||||
}>
|
||||
<Table<SearchWayItemType>
|
||||
{...tableProps}
|
||||
rowKey={'_id'}
|
||||
columns={[
|
||||
{
|
||||
title: '名称',
|
||||
|
@ -46,6 +49,30 @@ export default function Page() {
|
|||
{
|
||||
title: '搜索链接',
|
||||
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>
|
||||
|
||||
</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 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"
|
||||
|
||||
export default function Layout({
|
||||
|
@ -9,10 +9,10 @@ export default function Layout({
|
|||
children: React.ReactNode;
|
||||
}>) {
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<SiderNav
|
||||
|
||||
linkList={[
|
||||
{
|
||||
label: '链接管理',
|
||||
|
@ -20,6 +20,12 @@ export default function Layout({
|
|||
_id: 'addLink',
|
||||
href: '/admin/dashboard'
|
||||
},
|
||||
{
|
||||
label: '搜索管理',
|
||||
iconElement: <FontAwesomeIcon icon={faSearch}></FontAwesomeIcon>,
|
||||
_id: 'searchMenagement',
|
||||
href: '/admin/dashboard/search'
|
||||
},
|
||||
{
|
||||
label: '广告管理',
|
||||
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 { ObjectId } from "mongodb";
|
||||
import { NextRequest } from "next/server";
|
||||
import { Link } from "../route";
|
||||
import { verifySession } from "@/app/_lib/dal";
|
||||
import { Link } from "@/app/_lib/data/link";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
|
|
|
@ -17,6 +17,7 @@ export async function GET(req: NextRequest) {
|
|||
return Response.error()
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const session = await verifySession()
|
||||
|
@ -28,7 +29,7 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
|
|||
}
|
||||
// 获取路径参数
|
||||
const slug = (await params).id
|
||||
const collection = await getCollection('link')
|
||||
const collection = await getCollection('search')
|
||||
collection.deleteOne({
|
||||
_id: new ObjectId(slug)
|
||||
})
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { verifySession } from "@/app/_lib/dal";
|
||||
import { Link } from "@/app/_lib/data/link";
|
||||
import { getSearchWayList } from "@/app/_lib/data/search";
|
||||
import { User } from "@/app/_lib/data/user";
|
||||
import { getCollection, getDb } from "@/app/_lib/mongodb";
|
||||
import { message } from "antd";
|
||||
import bcrypt from 'bcrypt';
|
||||
import { ObjectId } from "mongodb";
|
||||
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 pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
||||
|
||||
const res = getSearchWayList({ page, pageSize })
|
||||
const res = await getSearchWayList({ page, pageSize })
|
||||
return Response.json(res)
|
||||
} catch (e) {
|
||||
return Response.error()
|
||||
|
@ -50,11 +47,15 @@ export async function PUT(req: NextRequest) {
|
|||
// User is not authenticated
|
||||
return new Response(null, { status: 401 })
|
||||
}
|
||||
|
||||
|
||||
// 获取待更新的对象
|
||||
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) })
|
||||
|
||||
return Response.json({ message: '成功' })
|
||||
|
||||
} catch (e) {
|
||||
return Response.error()
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export async function GET(req: NextRequest) {
|
|||
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
||||
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)
|
||||
} catch (e) {
|
||||
return Response.error()
|
||||
|
@ -27,8 +27,8 @@ export async function POST(req: NextRequest) {
|
|||
return new Response(null, { status: 401 })
|
||||
}
|
||||
const collection = await getCollection('search-type')
|
||||
const item = req.json()
|
||||
|
||||
const item = await req.json()
|
||||
|
||||
const res = collection.insertOne(item)
|
||||
return Response.json(res)
|
||||
} catch (e) {
|
||||
|
|
|
@ -19,3 +19,12 @@ body {
|
|||
background: var(--background);
|
||||
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 '@ant-design/v5-patch-for-react-19';
|
||||
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "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 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 { getLinkTypeList } from "./_lib/data/linkType";
|
||||
import { getLinkList } from "./_lib/data/link";
|
||||
|
|
|
@ -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",
|
||||
"@uiw/react-md-editor": "^4.0.5",
|
||||
"ahooks": "^3.8.4",
|
||||
"ali-oss": "^6.22.0",
|
||||
"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