完成了基本功能,网站热维护功能,完成测试基本的操作
This commit is contained in:
parent
115dc19e3d
commit
abfbfbb95e
|
@ -0,0 +1,20 @@
|
||||||
|
const OSS = require('ali-oss');
|
||||||
|
|
||||||
|
// 创建OSS实例
|
||||||
|
const client = new OSS({
|
||||||
|
accessKeyId: 'LTAI5tNzopZHJFa2Q9vqr1u5',
|
||||||
|
accessKeySecret: 'qPu7fyft0KJ1l6SGqbS71IW0vDbRlr'
|
||||||
|
// 先不指定region和bucket,以便后续查询
|
||||||
|
});
|
||||||
|
|
||||||
|
// 列出所有Bucket
|
||||||
|
client.listBuckets().then((result) => {
|
||||||
|
console.log('所有的Bucket:', result.buckets);
|
||||||
|
// 打印每个Bucket的信息
|
||||||
|
result.buckets.forEach((bucket) => {
|
||||||
|
console.log('Bucket名称:', bucket.name);
|
||||||
|
console.log('Bucket所在区域:', bucket.location);
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error('列出Bucket失败:', error);
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
import OSS from "ali-oss";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { mRequest } from "./request";
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const path = `/admin/${root}/${uuid()}_${file.name}`;
|
||||||
|
const res = await mRequest<{
|
||||||
|
accessKeyId: string;
|
||||||
|
accessKeySecret: string;
|
||||||
|
region: string;
|
||||||
|
bucket: string;
|
||||||
|
}>('GET', '/api/uploadKey')
|
||||||
|
const client = new OSS({
|
||||||
|
...res
|
||||||
|
})
|
||||||
|
return client
|
||||||
|
.put(path, file, {
|
||||||
|
mime: file.type,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": file.type,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
return ossBase + "/" + res.name;
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { UploadOutlined } from "@ant-design/icons"
|
||||||
|
import { Button, Input, Space } from "antd"
|
||||||
|
import { useRef, useState } from "react"
|
||||||
|
import uploadOss from "../_lib/upload"
|
||||||
|
|
||||||
|
export default function ImageUpload(props: {
|
||||||
|
accept: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
value?: string
|
||||||
|
background?: string
|
||||||
|
onChange?: (val: string) => void
|
||||||
|
}) {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const handleFile = async (file: File) => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
|
||||||
|
const url = await uploadOss(file, "aibot")
|
||||||
|
setLoading(false)
|
||||||
|
if (url) {
|
||||||
|
props.onChange?.(url)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
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">
|
||||||
|
|
||||||
|
<Input value={props.value} onChange={(e) => {
|
||||||
|
props.onChange?.(e.target.value)
|
||||||
|
}}></Input>
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
inputRef.current?.click()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
上传文件
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept={props.accept}
|
||||||
|
ref={inputRef}
|
||||||
|
style={{
|
||||||
|
display: "none",
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
const file = e.target.files?.[0]
|
||||||
|
e.target.value = ""
|
||||||
|
if (!file) return
|
||||||
|
handleFile(file)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
"use client";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { Link as _Link } from "../api/link/route";
|
||||||
|
import { LinkType } from "../api/linkType/route";
|
||||||
|
|
||||||
|
export default function LinkListBox({ linkTypeList, linkList }: { linkTypeList: LinkType[]; linkList: _Link[] }) {
|
||||||
|
return <div className="flex w-full flex-col gap-y-2">
|
||||||
|
{
|
||||||
|
linkTypeList.map(item => (
|
||||||
|
<div className="flex flex-col gap-y-2" key={item._id}>
|
||||||
|
<div className="flex items-center text-[#555555] gap-x-1 text-xl">
|
||||||
|
<img src={item.icon as string} className="w-[30px] h-[30px] object-cover"></img>
|
||||||
|
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</div>
|
||||||
|
<div className=" grid grid-cols-3 lg:grid-cols-6 gap-4 ">
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
href={val.link || ''}>
|
||||||
|
<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">{val.description}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -1,8 +1,15 @@
|
||||||
|
"use client";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { LinkTypeItem } from "../_lib/types";
|
import { LinkTypeItem } from "../_lib/types";
|
||||||
import Logo from "./Logo";
|
import Logo from "./Logo";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { LinkType } from "../api/linkType/route";
|
||||||
|
|
||||||
|
export default function SiderNav({ linkList }: { linkList: LinkType[] }) {
|
||||||
|
const pathname = usePathname()
|
||||||
|
console.log(pathname);
|
||||||
|
|
||||||
export default function SiderNav({ linkList }: { linkList: LinkTypeItem[] }) {
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[220px] flex flex-col gap-y-2 fixed left-0 top-0 h-[100vh] bg-[#F9F9F9]">
|
<div className="w-[220px] flex flex-col gap-y-2 fixed left-0 top-0 h-[100vh] bg-[#F9F9F9]">
|
||||||
<div>
|
<div>
|
||||||
|
@ -13,16 +20,17 @@ export default function SiderNav({ linkList }: { linkList: LinkTypeItem[] }) {
|
||||||
linkList.map((item) => {
|
linkList.map((item) => {
|
||||||
return (
|
return (
|
||||||
item?.href ?
|
item?.href ?
|
||||||
<Link className="cursor-pointer py-3 flex gap-x-2 items-center hover:bg-[#E0E0E0] rounded pl-3 text-[#515C6B] hover:text-[#5961F9] text-[14px]" href={item.href} key={item.id}>
|
<Link className={clsx("cursor-pointer py-3 flex gap-x-2 items-center hover:bg-[#E0E0E0] rounded pl-3 hover:text-[#5961F9] text-[14px]",
|
||||||
|
pathname === item.href ? "text-[#5961F9]" : "text-[#515C6B]")
|
||||||
|
} href={item.href} key={item._id}>
|
||||||
{
|
{
|
||||||
item.icon
|
item.iconElement
|
||||||
}
|
}
|
||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
</Link> :
|
</Link> :
|
||||||
<div className="cursor-pointer py-3 flex gap-x-2 items-center hover:bg-[#E0E0E0] rounded pl-3 text-[#515C6B] hover:text-[#5961F9] text-[14px]" key={item.id}>
|
<div className="cursor-pointer py-3 flex gap-x-2 items-center hover:bg-[#E0E0E0] rounded pl-3 text-[#515C6B] hover:text-[#5961F9] text-[14px]" key={item._id}>
|
||||||
{
|
<img src={item.icon as string} className="w-[20px] h-[20px] object-cover"></img>
|
||||||
item.icon
|
|
||||||
}
|
|
||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { Button, Form, Input, message } from "antd";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { mRequest } from "@/app/_lib/request";
|
||||||
|
|
||||||
|
export default function EditIconContent({
|
||||||
|
item,
|
||||||
|
onRefresh,
|
||||||
|
}: {
|
||||||
|
item: | null;
|
||||||
|
onRefresh: () => void;
|
||||||
|
}) {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
console.log(item);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (item) {
|
||||||
|
form.setFieldsValue({ ...item });
|
||||||
|
} else {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [item, form]);
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
layout="vertical"
|
||||||
|
form={form}
|
||||||
|
initialValues={{
|
||||||
|
// ...item,
|
||||||
|
// name: "",
|
||||||
|
// icons: [],
|
||||||
|
// link: "",
|
||||||
|
}}
|
||||||
|
onFinish={(res) => {
|
||||||
|
console.log(res);
|
||||||
|
mRequest(item ? "PUT" : "POST", "/app/linkIcon", {
|
||||||
|
data: item ? { id: item.id, ...res } : res,
|
||||||
|
returnType: "text",
|
||||||
|
}).then(() => {
|
||||||
|
message.success((item ? "修改" : "新增") + "成功");
|
||||||
|
onRefresh();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label="名称"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: "名称必填" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
{
|
||||||
|
!item ?
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label="链接名称"
|
||||||
|
name="linkName"
|
||||||
|
rules={[{ required: true, message: "链接必填" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="链接标签"
|
||||||
|
name="linkTag"
|
||||||
|
rules={[{ required: true, message: "链接必填" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</> : ''
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<Form.Item className="flex justify-end">
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
确认{item ? "修改" : "添加"}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
import { mRequest } from "@/app/_lib/request"
|
||||||
|
import ImageUpload from "@/app/_ui/ImageUpload"
|
||||||
|
import { Link } from "@/app/api/link/route"
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Form,
|
||||||
|
Image,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Modal,
|
||||||
|
Popconfirm,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
} from "antd"
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
export default function LinkTable(props: { id: string }) {
|
||||||
|
const [list, setList] = useState<Link[]>([])
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
setLoading(true)
|
||||||
|
const res = await mRequest<{ list: Link[] }>(
|
||||||
|
"GET",
|
||||||
|
`/api/link?typeId=${props.id}&page=1&pageSize=9999`
|
||||||
|
)
|
||||||
|
setList(res.list)
|
||||||
|
setLoading(false)
|
||||||
|
}, [props.id])
|
||||||
|
useEffect(() => {
|
||||||
|
refresh()
|
||||||
|
}, [refresh])
|
||||||
|
const [selected, setSelected] = useState<undefined | null | Link>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div className="p-4">
|
||||||
|
<Card
|
||||||
|
title="链接列表"
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button onClick={refresh}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" onClick={() => setSelected(null)}>
|
||||||
|
添加链接
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Table<Link>
|
||||||
|
loading={loading}
|
||||||
|
dataSource={list}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="_id"
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "名称",
|
||||||
|
dataIndex: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "图标",
|
||||||
|
dataIndex: "logoLink",
|
||||||
|
render: (_, row) => (
|
||||||
|
<Image width={80} src={row.logoLink}></Image>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
fixed: "right",
|
||||||
|
width: 200,
|
||||||
|
render: (_, row) => (
|
||||||
|
<Space>
|
||||||
|
<Button type="link" onClick={() => setSelected(row)}>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确认删除?"
|
||||||
|
onConfirm={async () => {
|
||||||
|
await mRequest("DELETE", "/app/link/" + row.id, {
|
||||||
|
returnType: "text",
|
||||||
|
})
|
||||||
|
refresh()
|
||||||
|
message.success("删除成功")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="link" danger>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<Modal
|
||||||
|
open={selected !== undefined}
|
||||||
|
onCancel={() => setSelected(undefined)}
|
||||||
|
destroyOnClose
|
||||||
|
title={(selected ? "编辑" : "新增") + "链接"}
|
||||||
|
footer={false}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
labelCol={{ span: 4 }}
|
||||||
|
initialValues={
|
||||||
|
selected
|
||||||
|
? selected
|
||||||
|
: { title: "", url: "", logoLink: "", description: "", priority: 0 }
|
||||||
|
}
|
||||||
|
onFinish={async (res) => {
|
||||||
|
if (selected) {
|
||||||
|
await mRequest("PUT", "/api/link", {
|
||||||
|
id: selected._id, ...res
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await mRequest("POST", "/api/link", {
|
||||||
|
...res, type: props.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
setSelected(undefined)
|
||||||
|
message.success("操作成功")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="图标名称"
|
||||||
|
rules={[{ required: true, message: "名称必填" }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="logoLink"
|
||||||
|
label="图标名称"
|
||||||
|
rules={[{ required: true, message: "名称必填" }]}
|
||||||
|
>
|
||||||
|
<ImageUpload
|
||||||
|
accept="image/*"
|
||||||
|
width={60}
|
||||||
|
height={60}
|
||||||
|
></ImageUpload>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="link" label="链接" rules={[{ required: true, message: "链接必填" }]}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="description" label="详情" >
|
||||||
|
<Input.TextArea />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="priority" label="优先级">
|
||||||
|
<InputNumber></InputNumber>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item className="flex justify-end">
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>ad</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<>文章管理</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
import { Space } from 'antd';
|
|
||||||
import { ColumnsType } from 'antd/es/table';
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
title: '姓名',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '年龄',
|
|
||||||
dataIndex: 'age',
|
|
||||||
key: 'age',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '地址',
|
|
||||||
dataIndex: 'address',
|
|
||||||
key: 'address',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Action',
|
|
||||||
key: 'action',
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
render: (_, record) => (
|
|
||||||
<Space size="middle" >
|
|
||||||
< a > Delete </a>
|
|
||||||
</Space>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
] as ColumnsType<unknown>;
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default function Loading() {
|
||||||
|
return <>
|
||||||
|
|
||||||
|
loading
|
||||||
|
</>
|
||||||
|
}
|
|
@ -1,132 +1,252 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { Button, Card, Drawer, Form, Input, Space, Table } from "antd";
|
import { Button, Card, Drawer, Form, Image, Input, message, Modal, Popconfirm, Space, Table } from "antd";
|
||||||
import { useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import '@ant-design/v5-patch-for-react-19';
|
import '@ant-design/v5-patch-for-react-19';
|
||||||
import tableConfig from "./config"
|
|
||||||
import { useAntdTable } from "ahooks";
|
import { useAntdTable } from "ahooks";
|
||||||
import { User } from "@/app/_lib/data/user";
|
|
||||||
import { mRequest } from "@/app/_lib/request";
|
import { mRequest } from "@/app/_lib/request";
|
||||||
interface DataType {
|
import { LinkTypeItem } from "@/app/_lib/types";
|
||||||
key: string;
|
import { LinkType } from "@/app/api/linkType/route";
|
||||||
name: string;
|
import LinkTable from "./LinkTable";
|
||||||
age: number;
|
import ImageUpload from "@/app/_ui/ImageUpload";
|
||||||
address: string;
|
import { useForm } from "antd/es/form/Form";
|
||||||
tags: string[];
|
|
||||||
}
|
|
||||||
const data: DataType[] = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
name: 'John Brown',
|
|
||||||
age: 32,
|
|
||||||
address: 'New York No. 1 Lake Park',
|
|
||||||
tags: ['nice', 'developer'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
name: 'Jim Green',
|
|
||||||
age: 42,
|
|
||||||
address: 'London No. 1 Lake Park',
|
|
||||||
tags: ['loser'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '3',
|
|
||||||
name: 'Joe Black',
|
|
||||||
age: 32,
|
|
||||||
address: 'Sydney No. 1 Lake Park',
|
|
||||||
tags: ['cool', 'teacher'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [open, setOpen] = useState(false)
|
const { tableProps, refresh } = useAntdTable(
|
||||||
const { tableProps, loading, error } = useAntdTable(({ current, pageSize }) => mRequest<{
|
async ({ current, pageSize }) => {
|
||||||
total: number;
|
return mRequest<{
|
||||||
list: User[];
|
total: number;
|
||||||
}>('GET', `/api/links?page=${current}&pageSize=${pageSize}}`),
|
data: LinkTypeItem[]
|
||||||
{
|
}>(
|
||||||
defaultPageSize: 10,
|
"GET",
|
||||||
}
|
`/api/linkType?page=${current}&pageSize=${pageSize}`
|
||||||
);
|
).then((res: any) => {
|
||||||
|
return {
|
||||||
|
total: res?.total || 0,
|
||||||
|
list: res?.list || [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
)
|
||||||
|
const [selectedType, setSelectedType] = useState<undefined | null | LinkType>(
|
||||||
|
undefined
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
title="链接管理"
|
title="新链接管理"
|
||||||
extra={
|
extra={
|
||||||
<Button type="primary" onClick={() => {
|
|
||||||
setOpen(true)
|
|
||||||
}}>添加链接</Button>
|
|
||||||
}>
|
|
||||||
<Table
|
|
||||||
columns={tableConfig}
|
|
||||||
dataSource={data}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
<Drawer
|
|
||||||
title={'添加图标'}
|
|
||||||
open={open}
|
|
||||||
width={500}
|
|
||||||
onClose={() => {
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
footer={
|
|
||||||
<Space>
|
<Space>
|
||||||
|
<Button onClick={() => {
|
||||||
<Button>重置</Button>
|
refresh()
|
||||||
<Button type="primary">提交</Button>
|
}}>刷新</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedType(null)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
添加分类
|
||||||
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
<Table<LinkType>
|
||||||
|
{...tableProps}
|
||||||
|
rowKey="_id"
|
||||||
|
expandable={{
|
||||||
|
expandedRowRender: (row) => <LinkTable id={row._id} />,
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "名称",
|
||||||
|
dataIndex: "label",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "图标",
|
||||||
|
dataIndex: "icon",
|
||||||
|
render: (_, item) => (
|
||||||
|
<>
|
||||||
|
<Image src={item.icon} width={70} ></Image>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
render: (_, item) => (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
ghost
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedType(item)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
修改分类
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Popconfirm
|
||||||
|
title={`确认要删除${item.label}么?`}
|
||||||
|
onConfirm={() => {
|
||||||
|
mRequest("DELETE", "/api/linkType/" + item._id, {
|
||||||
|
returnType: "text",
|
||||||
|
}).then(() => {
|
||||||
|
refresh()
|
||||||
|
message.success("删除成功")
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="primary" danger size="small">
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
destroyOnClose
|
||||||
|
title={selectedType !== undefined ? selectedType === null ? "添加分类" : "修改分类" : ""}
|
||||||
|
open={selectedType !== undefined}
|
||||||
|
width={500}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedType(undefined)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
labelCol={{ span: 4 }}
|
labelCol={{ span: 4 }}
|
||||||
onFinish={(res) => {
|
initialValues={selectedType ?
|
||||||
mRequest('POST', "/api/link").then(res => {
|
selectedType
|
||||||
setOpen(false)
|
: {}}
|
||||||
})
|
onFinish={async (res) => {
|
||||||
|
if (selectedType) {
|
||||||
|
await mRequest("PUT", "/api/linkType", {
|
||||||
|
_id: selectedType._id, ...res
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await mRequest("POST", "/api/linkType", {
|
||||||
|
...res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
setSelectedType(undefined)
|
||||||
|
message.success("操作成功")
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="name"
|
name="label"
|
||||||
label="名称"
|
label="名称"
|
||||||
rules={[{ required: true, message: "请输入名称" }]}
|
rules={[{ required: true, message: "请输入名称" }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="desc"
|
name="icon"
|
||||||
label="关键词"
|
label="图标"
|
||||||
|
rules={[{ required: true, message: "请输入" }]}
|
||||||
|
>
|
||||||
|
<ImageUpload
|
||||||
|
accept="image/*"
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
></ImageUpload>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="location"
|
||||||
|
label="显示位置"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item className="flex justify-end">
|
||||||
|
<Space>
|
||||||
|
<Button>
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
{/* <Drawer
|
||||||
|
forceRender
|
||||||
|
title={'添加图标'}
|
||||||
|
open={selectedType !== undefined}
|
||||||
|
width={500}
|
||||||
|
onClose={() => {
|
||||||
|
setSelectedType(undefined)
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={fo}
|
||||||
|
labelCol={{ span: 4 }}
|
||||||
|
onFinish={async (res) => {
|
||||||
|
if (selectedType) {
|
||||||
|
await mRequest("PUT", "/api/linkType", {
|
||||||
|
_id: selectedType._id, ...res
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await mRequest("POST", "/api/linkType", {
|
||||||
|
...res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
setSelectedType(undefined)
|
||||||
|
message.success("操作成功")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="label"
|
||||||
|
label="名称"
|
||||||
rules={[{ required: true, message: "请输入名称" }]}
|
rules={[{ required: true, message: "请输入名称" }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="url"
|
name="icon"
|
||||||
label="链接"
|
label="图标"
|
||||||
|
rules={[{ required: true, message: "请输入" }]}
|
||||||
|
>
|
||||||
|
<ImageUpload
|
||||||
|
accept="image/*"
|
||||||
|
width={60}
|
||||||
|
height={60}
|
||||||
|
background={fo?.getFieldValue("icon ")}
|
||||||
|
></ImageUpload>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="location"
|
||||||
|
label="显示位置"
|
||||||
rules={[{ required: true, message: "请输入链接" }]}
|
rules={[{ required: true, message: "请输入链接" }]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|
||||||
{/* <Form.Item
|
|
||||||
name="icon"
|
|
||||||
label="图标"
|
|
||||||
rules={[{ required: true, message: "请上传图标" }]}
|
|
||||||
>
|
|
||||||
<ResourceUpload></ResourceUpload>
|
|
||||||
</Form.Item> */}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Form.Item className="flex justify-end">
|
<Form.Item className="flex justify-end">
|
||||||
<Button type="primary" htmlType="submit">
|
<Space>
|
||||||
确认
|
<Button>
|
||||||
</Button>
|
重置
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
</Drawer>
|
</Drawer> */}
|
||||||
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 { faMagnet } from "@fortawesome/free-solid-svg-icons"
|
import { faAd, faArrowAltCircleLeft, faMagnet, faPenClip } 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({
|
||||||
|
@ -8,16 +8,34 @@ export default function Layout({
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<SiderNav linkList={[
|
<SiderNav
|
||||||
{
|
|
||||||
label: '链接管理',
|
linkList={[
|
||||||
icon: <FontAwesomeIcon icon={faMagnet} className=" fa-fw"></FontAwesomeIcon>,
|
{
|
||||||
id: 'add',
|
label: '链接管理',
|
||||||
href: '/admin'
|
iconElement: <FontAwesomeIcon icon={faMagnet} className=" fa-fw"></FontAwesomeIcon>,
|
||||||
}
|
_id: 'addLink',
|
||||||
]}></SiderNav>
|
href: '/admin/dashboard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '广告管理',
|
||||||
|
iconElement: <FontAwesomeIcon icon={faAd}></FontAwesomeIcon>,
|
||||||
|
_id: 'adMenagement',
|
||||||
|
href: '/admin/dashboard/ad'
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '文章管理',
|
||||||
|
iconElement: <FontAwesomeIcon icon={faPenClip}></FontAwesomeIcon>,
|
||||||
|
_id: 'articleMenagement',
|
||||||
|
href: '/admin/dashboard/article'
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
]}></SiderNav>
|
||||||
<div>
|
<div>
|
||||||
<div className="h-[50px] bg-white/80 shadow flex items-center justify-between px-5 ">
|
<div className="h-[50px] bg-white/80 shadow flex items-center justify-between px-5 ">
|
||||||
<span className="font-bold text-xl">后台管理面板</span>
|
<span className="font-bold text-xl">后台管理面板</span>
|
||||||
|
|
|
@ -3,12 +3,13 @@ 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 { message } from "antd";
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
export type Link = {
|
export type Link = {
|
||||||
title: string;
|
name: string;
|
||||||
url?: string;
|
link?: string;
|
||||||
description: string;
|
description: string;
|
||||||
id: number;
|
_id: string;
|
||||||
type: string;
|
type: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
logoLink: string;
|
logoLink: string;
|
||||||
|
@ -18,31 +19,25 @@ export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const collection = await getCollection('link');
|
const collection = await getCollection('link');
|
||||||
// Check if the user is authenticated
|
// Check if the user is authenticated
|
||||||
const session = await verifySession()
|
|
||||||
if (!session) {
|
|
||||||
// User is not authenticated
|
|
||||||
return new Response(null, { status: 401 })
|
|
||||||
}
|
|
||||||
// 获取分页参数
|
|
||||||
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
|
||||||
const pageSize = parseInt(req.nextUrl.searchParams.get('page') || '10') || 10;
|
|
||||||
|
|
||||||
|
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
||||||
|
const pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
||||||
|
const typeId = req.nextUrl.searchParams.get('typeId')
|
||||||
// 计算起始索引和结束索引
|
// 计算起始索引和结束索引
|
||||||
const startIndex = (page - 1) * pageSize;
|
const startIndex = (page - 1) * pageSize;
|
||||||
|
|
||||||
// 查询数据
|
// 查询数据
|
||||||
const cursor = collection.find<Link>({}).skip(startIndex).limit(pageSize);
|
const cursor = collection.find<Link>({ type: typeId }).skip(startIndex).limit(pageSize);
|
||||||
const data = await cursor.toArray();
|
const data = await cursor.toArray();
|
||||||
|
|
||||||
// 计算总数量
|
// 计算总数量
|
||||||
const total = await collection.countDocuments();
|
const total = (await collection.find<Link>({ type: typeId }).toArray()).length
|
||||||
|
|
||||||
return Response.json({
|
return Response.json({
|
||||||
total,
|
total,
|
||||||
list: data,
|
list: data,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
return Response.error()
|
return Response.error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +49,31 @@ export async function POST(req: NextRequest) {
|
||||||
await collection.insertOne(link)
|
await collection.insertOne(link)
|
||||||
return Response.json({ message: '成功' })
|
return Response.json({ message: '成功' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function DELETE(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 获取路径参数
|
||||||
|
const segments = req.nextUrl.pathname.split('/')
|
||||||
|
const dynamicParam = segments[segments.length - 1]
|
||||||
|
const collection = await getCollection('link')
|
||||||
|
collection.deleteOne({
|
||||||
|
_id: new ObjectId(dynamicParam)
|
||||||
|
})
|
||||||
|
return Response.json({ message: '删除成功' })
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function PUT(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 获取待更新的对象
|
||||||
|
const link = await req.json() as Link
|
||||||
|
const collection = await getCollection('link')
|
||||||
|
await collection.updateOne({ _id: new ObjectId(link.id) }, { $set: link })
|
||||||
|
return Response.json({ message: '成功' })
|
||||||
|
} catch (e) {
|
||||||
return Response.error()
|
return Response.error()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { verifySession } from "@/app/_lib/dal";
|
||||||
|
import { getCollection, getDb } from "@/app/_lib/mongodb";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { LinkType } from "../route";
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const collection = await getCollection('link-type');
|
||||||
|
// Check if the user is authenticated
|
||||||
|
const session = await verifySession()
|
||||||
|
if (!session) {
|
||||||
|
// User is not authenticated
|
||||||
|
return new Response(null, { status: 401 })
|
||||||
|
}
|
||||||
|
// 获取分页参数
|
||||||
|
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
||||||
|
const pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
||||||
|
|
||||||
|
// 计算起始索引和结束索引
|
||||||
|
const startIndex = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
const cursor = collection.find<LinkType>({}).skip(startIndex).limit(pageSize);
|
||||||
|
const data = await cursor.toArray();
|
||||||
|
|
||||||
|
// 计算总数量
|
||||||
|
const total = await collection.countDocuments();
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
total,
|
||||||
|
list: data,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||||
|
try {
|
||||||
|
// 获取路径参数
|
||||||
|
const slug = (await params).id
|
||||||
|
const collection = await getCollection('link-type')
|
||||||
|
collection.deleteOne({
|
||||||
|
_id: new ObjectId(slug)
|
||||||
|
})
|
||||||
|
return Response.json({ message: '删除成功' })
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { verifySession } from "@/app/_lib/dal";
|
||||||
|
import { getCollection, getDb } from "@/app/_lib/mongodb";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
export type LinkType = {
|
||||||
|
label: string;
|
||||||
|
icon?: string;
|
||||||
|
iconElement?: ReactNode;
|
||||||
|
_id: string;
|
||||||
|
href?: string;
|
||||||
|
location?: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const collection = await getCollection('link-type');
|
||||||
|
// Check if the user is authenticated
|
||||||
|
|
||||||
|
// 获取分页参数
|
||||||
|
const page = parseInt(req.nextUrl.searchParams.get('page') || '1') || 1;
|
||||||
|
const pageSize = parseInt(req.nextUrl.searchParams.get('pageSize') || '10') || 10;
|
||||||
|
|
||||||
|
// 计算起始索引和结束索引
|
||||||
|
const startIndex = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
const cursor = collection.find<LinkType>({}).skip(startIndex).limit(pageSize);
|
||||||
|
const data = await cursor.toArray();
|
||||||
|
|
||||||
|
// 计算总数量
|
||||||
|
const total = await collection.countDocuments();
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
total,
|
||||||
|
list: data,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 获取待插入的对象
|
||||||
|
const link = await req.json()
|
||||||
|
const collection = await getCollection('link-type')
|
||||||
|
await collection.insertOne(link)
|
||||||
|
return Response.json({ message: '成功' })
|
||||||
|
} catch (e) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 获取待更新的对象
|
||||||
|
const link = await req.json() as LinkType
|
||||||
|
const collection = await getCollection('link-type')
|
||||||
|
await collection.replaceOne({ _id: new ObjectId(link._id) }, { ...link, _id: new ObjectId(link._id) })
|
||||||
|
return Response.json({ message: '成功' })
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export async function GET() {
|
||||||
|
const accessKeyId = process.env.ALIYUN_RAM_ACCESS_KEY_ID || ''
|
||||||
|
const accessKeySecret = process.env.ALIYUN_RAM_ACCESS_KEY_SECRET || ''
|
||||||
|
return Response.json({
|
||||||
|
accessKeyId: accessKeyId,
|
||||||
|
accessKeySecret: accessKeySecret,
|
||||||
|
region: 'oss-cn-hangzhou',
|
||||||
|
bucket: 'newuitab'
|
||||||
|
})
|
||||||
|
}
|
91
app/page.tsx
91
app/page.tsx
|
@ -5,70 +5,30 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faArrowRotateBack, faDeafness, faImage, faMagnet, faMessage, faPenClip, faSearch, faThumbsUp, faVideo } from '@fortawesome/free-solid-svg-icons'
|
import { faArrowRotateBack, faDeafness, faImage, faMagnet, faMessage, faPenClip, faSearch, faThumbsUp, faVideo } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { LinkTypeItem } from "./_lib/types";
|
import { LinkTypeItem } from "./_lib/types";
|
||||||
import PosterBox from "./_ui/PosterBox";
|
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";
|
||||||
|
|
||||||
const defaultLinkList = [
|
|
||||||
{
|
|
||||||
label: 'AI应用集',
|
|
||||||
icon: <FontAwesomeIcon icon={faThumbsUp} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/ai-apps',
|
|
||||||
id: 1,
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI写作工具',
|
|
||||||
icon: <FontAwesomeIcon icon={faPenClip} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-writing',
|
|
||||||
id: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI图像工具',
|
|
||||||
icon: <FontAwesomeIcon icon={faImage} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-image',
|
|
||||||
id: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI办公工具',
|
|
||||||
icon: <FontAwesomeIcon icon={faArrowRotateBack} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-office',
|
|
||||||
id: 4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI对话聊天',
|
|
||||||
icon: <FontAwesomeIcon icon={faMessage} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-chat',
|
|
||||||
id: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI编程工具',
|
|
||||||
icon: <FontAwesomeIcon icon={faMagnet} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-programming',
|
|
||||||
id: 6,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI搜索引擎',
|
|
||||||
icon: <FontAwesomeIcon icon={faSearch} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-search',
|
|
||||||
id: 7,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI音频工具',
|
|
||||||
icon: <FontAwesomeIcon icon={faVideo} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-audio',
|
|
||||||
id: 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'AI开发平台',
|
|
||||||
icon: <FontAwesomeIcon icon={faDeafness} className="fa-fw text-[20px]" />,
|
|
||||||
href: '/?type=ai-platform',
|
|
||||||
id: 9,
|
|
||||||
}
|
|
||||||
] as LinkTypeItem[];
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
|
const collection = await getCollection('link-type')
|
||||||
|
|
||||||
|
const result = (await collection.find<LinkType>({}).toArray())
|
||||||
|
const linkTypeList = result.map(doc => {
|
||||||
|
doc._id = doc._id.toString(); // 将 _id 转换为字符串
|
||||||
|
return doc;
|
||||||
|
});
|
||||||
|
const linkCollect = await getCollection('link')
|
||||||
|
const linkList = (await linkCollect.find<_Link>({}).toArray()).map(doc => {
|
||||||
|
doc._id = doc._id.toString(); // 将 _id 转换为字符串
|
||||||
|
return doc;
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-full w-full font-[family-name:var(--font-geist-sans)] relative">
|
<div className="flex min-h-full w-full font-[family-name:var(--font-geist-sans)] relative">
|
||||||
<SiderNav linkList={defaultLinkList} />
|
<SiderNav linkList={linkTypeList} />
|
||||||
<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-[#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 className="absolute z-10 from-[#F9F9F9] left-0 to-transparent bg-gradient-to-t w-full h-[100px] bottom-[0px]">
|
||||||
|
@ -80,20 +40,7 @@ export default async function Home() {
|
||||||
<HeaderNav></HeaderNav>
|
<HeaderNav></HeaderNav>
|
||||||
<Search></Search>
|
<Search></Search>
|
||||||
<PosterBox posterList={[]} />
|
<PosterBox posterList={[]} />
|
||||||
|
<LinkListBox linkList={linkList} linkTypeList={linkTypeList}></LinkListBox>
|
||||||
<div className="flex w-full flex-col">
|
|
||||||
{
|
|
||||||
defaultLinkList.map(item => (
|
|
||||||
<div className="flex flex-col" key={item.id}>
|
|
||||||
<div className="flex items-center text-[#555555] ">
|
|
||||||
|
|
||||||
{item.icon}
|
|
||||||
<span>{item.label}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,10 +9,12 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.5.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",
|
||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
|
"ali-oss": "^6.22.0",
|
||||||
"antd": "^5.23.2",
|
"antd": "^5.23.2",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
@ -20,13 +22,16 @@
|
||||||
"jose": "^5.9.6",
|
"jose": "^5.9.6",
|
||||||
"mongodb": "^6.12.0",
|
"mongodb": "^6.12.0",
|
||||||
"next": "15.1.4",
|
"next": "15.1.4",
|
||||||
|
"proxy-agent": "^6.5.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"uuid": "^11.0.5",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@types/ali-oss": "^6.16.11",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
|
|
675
pnpm-lock.yaml
675
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue