一个快速选择文件夹树的组件

开发

文件树

explorer/src/components/readdir-tree/index.tsx
explorer/src/components/select-path-input/index.tsx

 文件路径:explorer/src/components/select-path-input/index.tsx

封装一个泡泡弹窗,弹窗内容为文件树组件

'use client'
import React, { useState } from 'react'
import { Card, Popover } from 'antd'
import styled from 'styled-components'
import { OnSelectType, Tree } from '@/components/readdir-tree'

const PopoverItem = styled(Card)`
  overflow-y: scroll;
  overscroll-behavior: contain;
  max-height: 30vh;
`

const SelectPathInput: React.FC<React.PropsWithChildren & { onSelect: OnSelectType; highlight_path?: string }> = ({
  children,
  onSelect,
  highlight_path,
}) => {
  const [open, setOpen] = useState(false)

  const handleOpenChange = (new_open: boolean) => {
    setOpen(new_open)
  }

  return (
    <Popover
      content={
        <PopoverItem>
          <Tree onSelect={onSelect} highlight_path={highlight_path} />
        </PopoverItem>
      }
      placement="bottomLeft"
      title="目录"
      trigger="click"
      open={open}
      onOpenChange={handleOpenChange}
      arrow={false}
    >
      <>{children}</>
    </Popover>
  )
}

export default SelectPathInput

文件路径:explorer/src/components/readdir-tree/index.tsx

使用 ul、li、文件夹 icon 的文件树组件

  1. ul 被装载时通过传入的 parent\_path 指定的目录下的文件夹列表。useReaddirRequest 方法。并使用 map 渲染 li 组件。
  2. li 组件内置一个 open 状态,当点击 li 时,将 open 设置为真,组合 parent\_path.concat(item.name) 传递给 ul 组件。此时新的 ul 组件被装载,触发 “1” 流程。
  3. 接受一个 highlight\_path 的 props。使用 new RegExp(\`^${encodeURIComponent(path.join('/'))}\`).test(encodeURIComponent(highlight\_path)) 正则匹配高亮目录,加强目录提示。具体效果可看最后的“效果”图片。

通过上面的流程简单实现了文件夹树组件

'use client'
import React, { useContext, useState } from 'react'
import { Button, Space } from 'antd'
import { ReaddirItemType, ReaddirListType } from '@/explorer-manager/src/type'
import styled from 'styled-components'
import { isEmpty } from 'lodash'
import { FolderOpenOutlined, FolderOutlined, LoadingOutlined, MinusOutlined } from '@ant-design/icons'
import { useRequest } from 'ahooks'
import axios from 'axios'

export const LiStyle = styled.li``

export const UlStyle = styled.ul`
  padding-left: 20px;
  list-style: none;
`

export type OnSelectType = (path: string[]) => void

const SelectPathInputContext = React.createContext<{ onSelect?: OnSelectType; highlight_path?: string }>(null!)

const useReaddirRequest = (
  { manual, init_path }: { manual: boolean; init_path: string } = { manual: false, init_path: '' },
) => {
  return useRequest(
    (path: string = init_path) =>
      axios
        .get<{ readdir: ReaddirListType }>('/path/api/readdir', {
          params: { path: path, only_dir: 1 },
        })
        .then(({ data: { readdir } }) => {
          return readdir
        }),
    { manual, cacheKey: init_path ? init_path : undefined, staleTime: 5000 },
  )
}

const EmptyItem: React.FC<React.PropsWithChildren> = ({ children }) => {
  return (
    <UlStyle>
      <li>{children}</li>
    </UlStyle>
  )
}

const Li: React.FC<{ item: ReaddirItemType; parent_path: string[] }> = ({ item, parent_path }) => {
  const [open, changeOpen] = useState(false)
  const path = parent_path.concat(item.name)
  const ctx = useContext(SelectPathInputContext)

  const { highlight_path = '' } = ctx

  const is_highlight =
    !!highlight_path && new RegExp(`^${encodeURIComponent(path.join('/'))}`).test(encodeURIComponent(highlight_path))

  const btn_tye = is_highlight ? 'primary' : 'text'

  return (
    <LiStyle data-h={highlight_path} data-p={path.join('/')}>
      <Space.Compact
        onClick={() => {
          changeOpen(!open)
        }}
      >
        {open ? (
          <Button type={btn_tye} icon={<FolderOpenOutlined />} />
        ) : (
          <Button type={btn_tye} icon={<FolderOutlined />} />
        )}
        <Button type={btn_tye} onClick={() => ctx.onSelect?.(path)}>
          {item.name}
        </Button>
      </Space.Compact>
      {open && <Ul parent_path={path} />}
    </LiStyle>
  )
}

const Ul: React.FC<{ parent_path: string[] }> = ({ parent_path = [] }) => {
  const { data: dir_list, loading } = useReaddirRequest({ manual: false, init_path: parent_path.join('/') })

  return !dir_list && loading ? (
    <EmptyItem>
      <LoadingOutlined />
    </EmptyItem>
  ) : isEmpty(dir_list) ? (
    <EmptyItem>
      <MinusOutlined />
    </EmptyItem>
  ) : (
    <UlStyle>
      {dir_list?.map((item) => {
        return <Li key={item.name} item={item} parent_path={parent_path} />
      })}
    </UlStyle>
  )
}

export const Tree: React.FC<{ onSelect?: OnSelectType; highlight_path?: string }> = ({ onSelect, highlight_path }) => {
  return (
    <SelectPathInputContext.Provider value={{ onSelect, highlight_path: highlight_path?.replace(/^\//, '') }}>
      <Ul parent_path={[]} />
    </SelectPathInputContext.Provider>
  )
}

可用于类似 win 系统资源管理器左边的菜单树功能,提供快速跳转目录。或者 input 快速选择输入所需要的目录地址。

效果

git-repo

yangWs29/share-explorer


寒露
18 声望0 粉丝