- 使用 Next.js 的 Image 组件显示图片。自带图片压缩生成快速预览的 webp 格式图片
- 使用 antd 的 PreviewGroup 组件实现原图浏览,自带缩小、放大、上一张、下一张等功能
功能实现
文件树
lua
explorer/src/app/path/[[...path]]/card-display.tsx
explorer/src/app/path/[[...path]]/page.tsx
explorer/src/components/preview/ext-rxp.tsx
explorer/src/components/preview/index.tsx
explorer/src/components/preview/proview-group-context.tsx
explorer/src/components/use-replace-pathname.ts
文件路径:explorer/src/components/preview/ext-rxp.tsx
一些判断文件后缀名方法
typescript
export const ImgRxp = /.(jpg|jpeg|gif|png|webp|ico)$/i
const RawRxp = /.(cr2|arw)/i
const GifRxp = /.(gif)$/i
const ZipRxp = /.(zip|rar|7z|tar.xz|tar)(.+\d+)?$/i
const VideoRxp = /.(mp4|mkv|mov|wmv|avi|avchd|flv|f4v|swf)(.+\d+)?$/i
export const isRaw = (path: string) => RawRxp.test(path)
export const isImage = (path: string) => ImgRxp.test(path)
export const isGif = (path: string) => GifRxp.test(path)
export const isZip = (path: string) => ZipRxp.test(path)
export const isVideo = (path: string) => VideoRxp.test(path)
文件路径:explorer/src/components/preview/index.tsx
预览封装组件,根据是否为文件夹、视频、图片、压缩包显示不同的 icon。
点击图片时,使用 antd 的 PreviewGroup 组件查看原图。
javascript
import React from 'react'
import { FileOutlined, FileZipOutlined, FolderOutlined, VideoCameraOutlined } from '@ant-design/icons'
import Image from 'next/image'
import { isGif, isImage, isVideo, isZip } from '@/components/preview/ext-rxp'
import { usePreviewGroupDispatch } from '@/components/preview/proview-group-context'
import { ReaddirItemType } from '@/explorer-manager/src/type'
import { useReplacePathname } from '@/components/use-replace-pathname'
const Preview: React.FC<{ item: ReaddirItemType }> = ({ item }) => {
const previewGroupDispatch = usePreviewGroupDispatch()
const { name, is_directory } = item
const { staticPath, joinSearchPath } = useReplacePathname()
if (is_directory) {
return <FolderOutlined />
}
if (isVideo(name)) {
return <VideoCameraOutlined />
}
if (isImage(name)) {
const image_path = staticPath(name)
return (
<Image
onClick={() => previewGroupDispatch(name)}
src={image_path}
alt={name}
fill
sizes="375px"
style={{
objectFit: 'scale-down', //"contain" | "cover" | "fill" | "none" | "scale-down"
}}
unoptimized={isGif(image_path)}
placeholder="empty"
/>
)
}
if (isZip(name)) {
return <FileZipOutlined />
}
return <FileOutlined />
}
export default Preview
文件路径:explorer/src/components/preview/proview-group-context.tsx
antd PreviewGroup 组件封装。
需要在顶层目录插入 PreviewGroupProvider 上下文组件,导出 usePreviewGroup、usePreviewGroupDispatch 读写方法。
typescript
'use client'
import React from 'react'
import { Image as AntdImage } from 'antd'
import { findIndex } from 'lodash'
import { isImage } from '@/components/preview/ext-rxp'
import { useReplacePathname } from '@/components/use-replace-pathname'
import createCtx from '@/lib/create-ctx'
import { useReaddirContext } from '@/app/path/readdir-context'
export const PreviewGroupContent = createCtx<string>()
export const usePreviewGroup = PreviewGroupContent.useStore
export const usePreviewGroupDispatch = PreviewGroupContent.useDispatch
const AntdImagePreviewGroup: React.FC<React.PropsWithChildren> = ({ children }) => {
const { staticPath } = useReplacePathname()
const readdir_list = useReaddirContext()
const image_list = readdir_list.filter((item) => isImage(item.name))
const name = usePreviewGroup()
const previewGroupDispatch = usePreviewGroupDispatch()
return (
<AntdImage.PreviewGroup
preview={{
visible: !!name,
current: findIndex(image_list, { name }),
onVisibleChange: () => {
previewGroupDispatch('')
},
onChange: (current) => {
previewGroupDispatch(image_list[current].name)
},
}}
items={image_list.map(({ name }) => staticPath(name))}
>
{children}
</AntdImage.PreviewGroup>
)
}
const PreviewGroupProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<PreviewGroupContent.ContextProvider value={''}>
<AntdImagePreviewGroup>{children}</AntdImagePreviewGroup>
</PreviewGroupContent.ContextProvider>
)
}
export default PreviewGroupProvider
文件路径:explorer/src/components/use-replace-pathname.ts
添加一个获取不同路径的hooks
- replace_pathname 将不需要的一级路径 /path/ 过滤掉
- joinSearchPath 拼接过滤掉 /path/ 的 pathname
- joinPath 拼接未过滤的 pathname
- staticPath 拼接得到获取文件地址
typescript
import { usePathname } from 'next/navigation'
export const pathExp = /(^/)?path/
export const encodePathItem = (path: string) => {
return path
.split('/')
.map((text) => encodeURIComponent(text))
.join('/')
}
export const useReplacePathname = () => {
const pathname = decodeURIComponent(usePathname() || '')
const replace_pathname = pathname.replace(pathExp, '')
const joinSearchPath = (path: string) => encodePathItem(`${replace_pathname}/${path}`)
const joinPath = (path: string) => encodePathItem(`${pathname}/${path}`)
const staticPath = (path: string) => `/static${joinSearchPath(path)}`
return {
pathname: pathname,
replace_pathname: replace_pathname,
joinSearchPath: joinSearchPath,
joinPath: joinPath,
staticPath: staticPath,
}
}
文件路径:explorer/src/app/path/[[...path]]/card-display.tsx
将 Preview 组件插入 List Item 内
ini
...
import Preview from '@/components/preview'
const CardDisplay: React.FC = () => {
const pathname = usePathname()
const readdir = useReaddirContext()
const column = useCardColumnContext()
return (
<List
...
<div style={{ position: 'absolute', width: '100%', height: '100%' }}>
<Preview item={item} />
</div>
...
文件路径:explorer/src/app/path/[[...path]]/page.tsx
将 PreviewGroupProvider 组件插入最顶部
javascript
...
import PreviewGroupProvider from '@/components/preview/proview-group-context'
const Page: React.FC = () => {
const display_type = useDisplayTypeContext()
return <PreviewGroupProvider>{display_type === 'table' ? <TableDisplay /> : <CardDisplay />}</PreviewGroupProvider>
}
export default Page
效果