diff --git a/README.md b/README.md index c650d40..006e7de 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ PicImpact

- +

PicImpact 是一个摄影师专用的摄影作品展示网站,基于 Next.js 开发。 @@ -24,6 +24,7 @@ PicImpact 是一个摄影师专用的摄影作品展示网站,基于 Next.js - 支持批量自动化上传,上传图片时会生成 0.3 倍率的压缩图片,以提供加载优化。 - 图片版权信息展示和维护功能,支持外链跳转。 - 后台有图片数据统计、图片上传、图片维护、相册管理、系统设置和存储配置功能。 +- 双因素认证功能,基于 TOTP 算法 [RFC 6238](https://www.rfc-editor.org/rfc/rfc6238),支持 Google Authenticator、Microsoft Authenticator 和 1Password 等。 - 基于 SSR 的混合渲染,采用状态机制,提供良好的使用体验。 - 基于 prisma 的自动初始化数据库和数据迁移,简化部署流程。 - 支持 Vercel 部署、Node.js 部署、Docker 等容器化部署,当然 k8s 也支持。 diff --git a/app/admin/about/page.tsx b/app/admin/about/page.tsx index 9c1d55f..e0058ef 100644 --- a/app/admin/about/page.tsx +++ b/app/admin/about/page.tsx @@ -7,6 +7,23 @@ import { ExternalLink, Github } from 'lucide-react' import Link from 'next/link' export default function About() { + const contributors = [ + { + name: 'Bess Croft', + url: 'https://github.com/besscroft', + avatar: 'https://avatars.githubusercontent.com/u/33775809?v=4' + }, + { + name: 'Nadeshiko Manju', + url: 'https://github.com/Zheaoli', + avatar: 'https://avatars.githubusercontent.com/u/7054676?v=4' + }, + { + name: '仙姑本咕', + url: 'https://github.com/hexgu', + avatar: 'https://avatars.githubusercontent.com/u/85490069?v=4' + }, + ] return (
- v0.11.1 + v1.0.0 PicImpact 是一个摄影师专用的摄影作品展示网站,基于 Next.js 开发。
@@ -33,30 +50,28 @@ export default function About() {
Contributors - - - Bess Croft - - - - - Nadeshiko Manju - - + { + contributors.map((item: any) => { + return ( + + + {item.name} + + + ) + }) + }
支持项目 diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index 595d927..c33324a 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -2,6 +2,7 @@ import DashHeader from '~/components/layout/DashHeader' import { BaseSide } from '~/components/layout/BaseSide' import { AntdRegistry } from '@ant-design/nextjs-registry' +import Command from '~/components/admin/Command' export default function AdminLayout({ children, @@ -17,6 +18,7 @@ export default function AdminLayout({
+ {children}
diff --git a/app/admin/page.tsx b/app/admin/page.tsx index cf2fbdf..1b4f12e 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,14 +1,13 @@ -import { Button, Card, CardBody } from '@nextui-org/react' -import Link from 'next/link' -import { Star, MessageSquareHeart } from 'lucide-react' import { fetchImagesAnalysis } from '~/server/lib/query' -import TagTable from '~/components/admin/dashboard/TagTable' +import CardList from '~/components/admin/dashboard/CardList' import { DataProps } from '~/types' export default async function Admin() { const getData = async (): Promise<{ total: number showTotal: number + crTotal: number + tagsTotal: number result: any[] }> => { 'use server' @@ -23,26 +22,8 @@ export default async function Admin() { } return ( -
- - - - - 如果您觉得项目不错 - - - - - - - 如果您有 Bug 反馈和建议 - - - - - - - +
+
) } \ No newline at end of file diff --git a/app/providers/toaster-providers.tsx b/app/providers/toaster-providers.tsx index 9c1c9a3..4ee571b 100644 --- a/app/providers/toaster-providers.tsx +++ b/app/providers/toaster-providers.tsx @@ -10,11 +10,8 @@ export function ToasterProviders() { ) } \ No newline at end of file diff --git a/components/MasonryItem.tsx b/components/MasonryItem.tsx index 19310e5..6b9d967 100644 --- a/components/MasonryItem.tsx +++ b/components/MasonryItem.tsx @@ -1,9 +1,9 @@ 'use client' import { - Dialog, + CustomDialog, DialogContent, -} from '~/components/ui/Dialog' +} from '~/components/ui/CustomDialog' import { useButtonStore } from '~/app/providers/button-store-Providers' import { CopyrightType, DataProps, ImageType } from '~/types' import { Image, Tabs, Tab, Card, CardHeader, CardBody, CardFooter, Button, Chip, Link, Avatar, Tooltip } from '@nextui-org/react' @@ -72,7 +72,7 @@ export default function MasonryItem() { } return ( - { @@ -365,6 +365,6 @@ export default function MasonryItem() {
- + ) } \ No newline at end of file diff --git a/components/admin/Command.tsx b/components/admin/Command.tsx new file mode 100644 index 0000000..6c5e3ef --- /dev/null +++ b/components/admin/Command.tsx @@ -0,0 +1,106 @@ +'use client' + +import * as React from 'react' +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from '~/components/ui/command' +import { useButtonStore } from '~/app/providers/button-store-Providers' +import { Archive, Milestone, Image, Server, ImageUp, MonitorDot, Copyright, Info, SquareAsterisk, ShieldCheck } from 'lucide-react' +import { useRouter } from 'next-nprogress-bar' + +export default function Command() { + const router = useRouter() + const { searchOpen, setSearchOpen } = useButtonStore( + (state) => state, + ) + + return ( + + + + 没有任何结果. + + { + router.push('/admin') + setSearchOpen(false) + }}> + + 控制台 + + { + router.push('/admin/upload') + setSearchOpen(false) + }}> + + 上传 + + { + router.push('/admin/list') + setSearchOpen(false) + }}> + + 图片维护 + + { + router.push('/admin/tag') + setSearchOpen(false) + }}> + + 相册管理 + + { + router.push('/admin/copyright') + setSearchOpen(false) + }}> + + 版权管理 + + { + router.push('/admin/about') + setSearchOpen(false) + }}> + + 关于 + + + + + { + router.push('/admin/settings/preferences') + setSearchOpen(false) + }}> + + 首选项 + + { + router.push('/admin/settings/password') + setSearchOpen(false) + }}> + + 密码修改 + + { + router.push('/admin/settings/storages') + setSearchOpen(false) + }}> + + 存储 + + { + router.push('/admin/settings/authenticator') + setSearchOpen(false) + }}> + + 双因素验证 + + + + + ) +} \ No newline at end of file diff --git a/components/admin/SearchBorder.tsx b/components/admin/SearchBorder.tsx new file mode 100644 index 0000000..31ee5fd --- /dev/null +++ b/components/admin/SearchBorder.tsx @@ -0,0 +1,26 @@ +'use client' + +import { Button } from '~/components/ui/button' +import { cn } from '~/utils' +import { MagnifyingGlassIcon } from '@radix-ui/react-icons' +import { useButtonStore } from '~/app/providers/button-store-Providers' + +export default function SearchBorder() { + const { setSearchOpen } = useButtonStore( + (state) => state, + ) + + return ( + + ) +} \ No newline at end of file diff --git a/components/admin/SearchButton.tsx b/components/admin/SearchButton.tsx new file mode 100644 index 0000000..119265e --- /dev/null +++ b/components/admin/SearchButton.tsx @@ -0,0 +1,20 @@ +'use client' + +import { Search } from 'lucide-react' +import { useButtonStore } from '~/app/providers/button-store-Providers' +import { usePathname } from 'next/navigation' + +export default function SearchButton() { + const pathname = usePathname() + const { setSearchOpen } = useButtonStore( + (state) => state, + ) + + return ( + <> + { + pathname.startsWith('/admin') && setSearchOpen(true)} size={20} /> + } + + ) +} \ No newline at end of file diff --git a/components/admin/copyright/CopyrightList.tsx b/components/admin/copyright/CopyrightList.tsx index 4886b6b..efd0b2c 100644 --- a/components/admin/copyright/CopyrightList.tsx +++ b/components/admin/copyright/CopyrightList.tsx @@ -151,7 +151,7 @@ export default function CopyrightList(props : Readonly) { isSelected ? ( ) : ( - + ) } onValueChange={(isSelected: boolean) => updateCopyrightShow(copyright.id, isSelected ? 0 : 1)} @@ -168,7 +168,7 @@ export default function CopyrightList(props : Readonly) { isSelected ? ( ) : ( - + ) } onValueChange={(isSelected: boolean) => updateCopyrightDefault(copyright.id, isSelected ? 0 : 1)} @@ -220,7 +220,7 @@ export default function CopyrightList(props : Readonly) { + + + 如果您有 Bug 反馈和建议 + + + + + + + + +
+ + + + + 相册 + 数量/张 + 显示/张 + + + + {props.data?.result.map((item: any) => ( + + {item.name} + {Number(item?.total)} + {Number(item?.show_total)} + + ))} + +
+
+ + ) +} \ No newline at end of file diff --git a/components/admin/dashboard/TagTable.tsx b/components/admin/dashboard/TagTable.tsx deleted file mode 100644 index 1b7e745..0000000 --- a/components/admin/dashboard/TagTable.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client' - -import { Card, CardBody, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell } from '@nextui-org/react' -import { DataProps } from '~/types' - -export default function TagTable(props: Readonly) { - return ( - <> - - - 照片数据 - {props.data?.total || 0}张 - 显示照片 - {props.data?.showTotal || 0}张 - - - - - 相册 - 数量/张 - 显示/张 - - - {props.data?.result && props.data?.result.map((item: any) => ( - - {item?.name} - {Number(item?.total)} - {Number(item?.show_total)} - - )) - } - -
- - ) -} \ No newline at end of file diff --git a/components/admin/list/ImageBatchDeleteSheet.tsx b/components/admin/list/ImageBatchDeleteSheet.tsx index 29c92d9..8f7e9d5 100644 --- a/components/admin/list/ImageBatchDeleteSheet.tsx +++ b/components/admin/list/ImageBatchDeleteSheet.tsx @@ -74,6 +74,7 @@ export default function ImageBatchDeleteSheet(props : Readonly setData(value)} @@ -87,11 +88,19 @@ export default function ImageBatchDeleteSheet(props : Readonly )} + onClear={() => setData([])} /> + + + + + + + +
+ {updateShowLoading && updateShowId === image.id ? : + + isSelected ? ( + + ) : ( + + ) + } + onValueChange={(isSelected: boolean) => updateImageShow(image.id, isSelected ? 0 : 1)} + /> + } + + + } + aria-label="排序" + >{image.sort} + + +
+
排序
+
规则为从高到低
+
+
+
+
+
+ + + + + } onClick={() => { - setImageViewData(image) - setImageView(true) + setImage(image) + setImageDefaultTag({ label: image.tag_names, value: image.tag_values }) + setIsTypeOpen(true) }} - aria-label="查看图片" > - - - -
- - - - - -
- {updateShowLoading && updateShowId === image.id ? : - - isSelected ? ( - - ) : ( - - ) - } - onValueChange={(isSelected: boolean) => updateImageShow(image.id, isSelected ? 0 : 1)} - /> - } - - - } - aria-label="排序" - >{image.sort} - - -
-
排序
-
规则为从高到低
-
-
-
-
-
- - - - - - } - onClick={() => { - setImage(image) - setImageDefaultTag({ label: image.tag_names, value: image.tag_values }) - setIsTypeOpen(true) - }} - > - 绑定相册 - - } - onClick={() => { - setImageEditData(image) - setImageEdit(true) - }} - >编辑图片 - } - onClick={() => { - setImage(image) - setIsOpen(true) - }} - > - 删除图片 - - - -
-
+ 绑定相册 + + } + onClick={() => { + setImageEditData(image) + setImageEdit(true) + }} + >编辑图片 + } + onClick={() => { + setImage(image) + setIsOpen(true) + }} + > + 删除图片 + + + + +
))} @@ -395,7 +395,7 @@ export default function ListProps(props : Readonly) { - - - - - - - + +
+ + + + + + +
+ + ))} ) {