|
|
@@ -6,16 +6,30 @@ import { MessageFu } from '@/utils/message'
|
|
|
import { fileDomInitialFu } from '@/utils/domShow'
|
|
|
import { forwardRef, useImperativeHandle } from 'react'
|
|
|
import { Button, Popconfirm } from 'antd'
|
|
|
-import { UploadOutlined, CloseOutlined, DownloadOutlined, EyeOutlined } from '@ant-design/icons'
|
|
|
+import {
|
|
|
+ UploadOutlined,
|
|
|
+ CloseOutlined,
|
|
|
+ DownloadOutlined,
|
|
|
+ EyeOutlined,
|
|
|
+ MenuOutlined
|
|
|
+} from '@ant-design/icons'
|
|
|
import classNames from 'classnames'
|
|
|
import { baseURL } from '@/utils/http'
|
|
|
import { authFilesLookFu } from './data'
|
|
|
+import { DndContext, DragEndEvent, DragOverlay, DragStartEvent, closestCenter } from '@dnd-kit/core'
|
|
|
+import {
|
|
|
+ SortableContext,
|
|
|
+ useSortable,
|
|
|
+ verticalListSortingStrategy,
|
|
|
+ arrayMove
|
|
|
+} from '@dnd-kit/sortable'
|
|
|
+import { CSS } from '@dnd-kit/utilities'
|
|
|
|
|
|
type Props = {
|
|
|
- isLook: boolean //是否是查看
|
|
|
- ref: any //当前自己的ref,给父组件调用
|
|
|
+ isLook: boolean // 是否是查看
|
|
|
+ ref: any // 当前自己的ref,给父组件调用
|
|
|
fileCheck: boolean
|
|
|
- dirCode: string //文件的code码
|
|
|
+ dirCode: string // 文件的code码
|
|
|
myUrl: string
|
|
|
fromData?: any
|
|
|
accept?: string
|
|
|
@@ -24,6 +38,78 @@ type Props = {
|
|
|
tips?: string
|
|
|
// 文件大小
|
|
|
size?: number
|
|
|
+ // 最大文件数量
|
|
|
+ maxCount?: number
|
|
|
+}
|
|
|
+
|
|
|
+// 可排序的文件项组件
|
|
|
+interface SortableFileItemProps {
|
|
|
+ file: FileImgListType
|
|
|
+ onDelete: (id: number) => void
|
|
|
+ isLook: boolean
|
|
|
+ index: number
|
|
|
+}
|
|
|
+
|
|
|
+const SortableFileItem = ({ file, onDelete, isLook, index }: SortableFileItemProps) => {
|
|
|
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
|
+ id: file.id
|
|
|
+ })
|
|
|
+
|
|
|
+ const style = {
|
|
|
+ transform: CSS.Transform.toString(transform),
|
|
|
+ transition,
|
|
|
+ opacity: isDragging ? 0.5 : 1
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ ref={setNodeRef}
|
|
|
+ style={style}
|
|
|
+ className={classNames(styles.Z3filesRow, { [styles.dragging]: isDragging })}
|
|
|
+ >
|
|
|
+ {!isLook && (
|
|
|
+ <div className={styles.dragHandle} {...attributes} {...listeners}>
|
|
|
+ <MenuOutlined rev={undefined} />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <div className={styles.Z3files1} title={file.fileName}>
|
|
|
+ {file.fileName}
|
|
|
+ </div>
|
|
|
+ <div className={styles.Z3files2}>
|
|
|
+ {authFilesLookFu(file.fileName, '') ? (
|
|
|
+ <>
|
|
|
+ <EyeOutlined
|
|
|
+ rev={undefined}
|
|
|
+ title='查看'
|
|
|
+ onClick={() => authFilesLookFu(file.fileName, file.filePath)}
|
|
|
+ />
|
|
|
+  
|
|
|
+ </>
|
|
|
+ ) : null}
|
|
|
+ <a
|
|
|
+ title='下载'
|
|
|
+ href={baseURL + file.filePath}
|
|
|
+ download={file.fileName}
|
|
|
+ target='_blank'
|
|
|
+ rel='noreferrer'
|
|
|
+ >
|
|
|
+ <DownloadOutlined rev={undefined} />
|
|
|
+ </a>
|
|
|
+  
|
|
|
+ {!isLook && (
|
|
|
+ <Popconfirm
|
|
|
+ title='删除后无法恢复,是否删除?'
|
|
|
+ okText='删除'
|
|
|
+ cancelText='取消'
|
|
|
+ onConfirm={() => onDelete(file.id)}
|
|
|
+ okButtonProps={{ loading: false }}
|
|
|
+ >
|
|
|
+ <CloseOutlined rev={undefined} title='删除' />
|
|
|
+ </Popconfirm>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
function Z3upFiles(
|
|
|
@@ -35,47 +121,46 @@ function Z3upFiles(
|
|
|
myUrl,
|
|
|
fromData,
|
|
|
accept = '.zip',
|
|
|
- tips = '此处的附件为对外的项目成果文件,仅支持zip格式,最多10个;单个附件不得超过500M',
|
|
|
+ tips = '单个附件不得超过500M',
|
|
|
size
|
|
|
}: Props,
|
|
|
ref: any
|
|
|
) {
|
|
|
const [fileList, setFileList] = useState<FileImgListType[]>([])
|
|
|
+ const [activeId, setActiveId] = useState<number | null>(null)
|
|
|
+ const myInput = useRef<HTMLInputElement>(null)
|
|
|
|
|
|
// 给父组件调用 回显
|
|
|
const showList = useCallback((list: FileImgListType[]) => {
|
|
|
setFileList(list)
|
|
|
}, [])
|
|
|
|
|
|
- const myInput = useRef<HTMLInputElement>(null)
|
|
|
-
|
|
|
- // 上传文件
|
|
|
+ // 上传多个文件
|
|
|
const handeUpPhoto = useCallback(
|
|
|
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
- if (e.target.files) {
|
|
|
- // 拿到files信息
|
|
|
- const filesInfo = e.target.files[0]
|
|
|
-
|
|
|
- // 校验格式
|
|
|
- if (!filesInfo.name.includes('.zip') && accept !== '*') {
|
|
|
- e.target.value = ''
|
|
|
- return MessageFu.warning(`只支持zip格式!`)
|
|
|
- }
|
|
|
+ if (!e.target.files || e.target.files.length === 0) return
|
|
|
+
|
|
|
+ const files = Array.from(e.target.files)
|
|
|
|
|
|
+ const uploadedFiles: FileImgListType[] = []
|
|
|
+ let hasError = false
|
|
|
+
|
|
|
+ // 逐个上传文件
|
|
|
+ for (const file of files) {
|
|
|
// 校验大小
|
|
|
- if (size && filesInfo.size > size * 1024 * 1024) {
|
|
|
- e.target.value = ''
|
|
|
- return MessageFu.warning(`最大支持${size}M!`)
|
|
|
+ if (size && file.size > size * 1024 * 1024) {
|
|
|
+ MessageFu.warning(`文件"${file.name}"超过${size}M限制!`)
|
|
|
+ hasError = true
|
|
|
+ continue
|
|
|
}
|
|
|
|
|
|
// 创建FormData对象
|
|
|
const fd = new FormData()
|
|
|
- // 把files添加进FormData对象(‘photo’为后端需要的字段)
|
|
|
fd.append('type', type)
|
|
|
fd.append('dirCode', dirCode)
|
|
|
fd.append('isCompress', 'true')
|
|
|
fd.append('isDb', 'true')
|
|
|
- fd.append('file', filesInfo)
|
|
|
+ fd.append('file', file)
|
|
|
|
|
|
if (fromData) {
|
|
|
for (const k in fromData) {
|
|
|
@@ -83,40 +168,71 @@ function Z3upFiles(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- e.target.value = ''
|
|
|
-
|
|
|
try {
|
|
|
const res = await API_upFile(fd, myUrl)
|
|
|
if (res.code === 0) {
|
|
|
- MessageFu.success('上传成功!')
|
|
|
- setFileList([...fileList, res.data])
|
|
|
+ uploadedFiles.push(res.data)
|
|
|
+ } else {
|
|
|
+ MessageFu.error(`文件"${file.name}"上传失败`)
|
|
|
+ hasError = true
|
|
|
}
|
|
|
- fileDomInitialFu()
|
|
|
} catch (error) {
|
|
|
- fileDomInitialFu()
|
|
|
+ MessageFu.error(`文件"${file.name}"上传失败`)
|
|
|
+ hasError = true
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 清空input值
|
|
|
+ e.target.value = ''
|
|
|
+
|
|
|
+ if (uploadedFiles.length > 0) {
|
|
|
+ setFileList(prev => [...prev, ...uploadedFiles])
|
|
|
+ MessageFu.success(`成功上传${uploadedFiles.length}个文件`)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!hasError && uploadedFiles.length === files.length) {
|
|
|
+ fileDomInitialFu()
|
|
|
+ }
|
|
|
},
|
|
|
- [accept, dirCode, fileList, fromData, myUrl, size, type]
|
|
|
+ [dirCode, fromData, myUrl, size, type]
|
|
|
)
|
|
|
|
|
|
+ // 拖拽开始
|
|
|
+ const handleDragStart = useCallback((event: DragStartEvent) => {
|
|
|
+ setActiveId(Number(event.active.id))
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ // 拖拽结束
|
|
|
+ const handleDragEnd = useCallback((event: DragEndEvent) => {
|
|
|
+ const { active, over } = event
|
|
|
+ setActiveId(null)
|
|
|
+
|
|
|
+ if (over && active.id !== over.id) {
|
|
|
+ setFileList(items => {
|
|
|
+ const oldIndex = items.findIndex(item => item.id === active.id)
|
|
|
+ const newIndex = items.findIndex(item => item.id === over.id)
|
|
|
+
|
|
|
+ return arrayMove(items, oldIndex, newIndex)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }, [])
|
|
|
+
|
|
|
// 列表删除某一个文件
|
|
|
- const delImgListFu = useCallback(
|
|
|
- async (id: number) => {
|
|
|
- const newItems = fileList.filter(v => v.id !== id)
|
|
|
- setFileList(newItems)
|
|
|
- },
|
|
|
- [fileList, setFileList]
|
|
|
- )
|
|
|
+ const delImgListFu = useCallback((id: number) => {
|
|
|
+ setFileList(prev => prev.filter(v => v.id !== id))
|
|
|
+ }, [])
|
|
|
|
|
|
- // 让父组件调用,拿到 附件信息
|
|
|
- const filesIdRes = useCallback(() => {
|
|
|
- return fileList.map(v => v.id)
|
|
|
+ // 让父组件调用,拿到附件信息
|
|
|
+ const filesRes = useCallback(() => {
|
|
|
+ return fileList || []
|
|
|
}, [fileList])
|
|
|
|
|
|
+ // 获取当前拖拽的文件
|
|
|
+ const activeFile = activeId ? fileList.find(file => file.id === activeId) : null
|
|
|
+
|
|
|
// 可以让父组件调用子组件的方法
|
|
|
useImperativeHandle(ref, () => ({
|
|
|
- filesIdRes,
|
|
|
+ filesRes,
|
|
|
showList
|
|
|
}))
|
|
|
|
|
|
@@ -125,75 +241,66 @@ function Z3upFiles(
|
|
|
<input
|
|
|
id='upInput'
|
|
|
type='file'
|
|
|
- accept={accept}
|
|
|
ref={myInput}
|
|
|
- onChange={e => handeUpPhoto(e)}
|
|
|
+ onChange={handeUpPhoto}
|
|
|
+ multiple // 支持多选
|
|
|
/>
|
|
|
- <div className='Z3Btn'>
|
|
|
- {isLook ? null : (
|
|
|
+ <div className={styles.Z3Btn}>
|
|
|
+ {!isLook && (
|
|
|
<Button
|
|
|
onClick={() => myInput.current?.click()}
|
|
|
icon={<UploadOutlined rev={undefined} />}
|
|
|
>
|
|
|
- 上传
|
|
|
+ 上传文件
|
|
|
</Button>
|
|
|
)}
|
|
|
|
|
|
- <div className='Z3files'>
|
|
|
- {fileList.map(v => (
|
|
|
- <div className='Z3filesRow' key={v.id}>
|
|
|
- <div className='Z3files1' title={v.fileName}>
|
|
|
- {v.fileName}
|
|
|
- </div>
|
|
|
- <div className='Z3files2'>
|
|
|
- {authFilesLookFu(v.fileName, '') ? (
|
|
|
- <>
|
|
|
- <EyeOutlined
|
|
|
- rev={undefined}
|
|
|
- title='查看'
|
|
|
- onClick={() => authFilesLookFu(v.fileName, v.filePath)}
|
|
|
- />
|
|
|
-  
|
|
|
- </>
|
|
|
- ) : null}
|
|
|
- <a
|
|
|
- title='下载'
|
|
|
- href={baseURL + v.filePath}
|
|
|
- download={v.fileName}
|
|
|
- target='_blank'
|
|
|
- rel='noreferrer'
|
|
|
- >
|
|
|
- <DownloadOutlined rev={undefined} />
|
|
|
- </a>
|
|
|
-  
|
|
|
- <Popconfirm
|
|
|
- title='删除后无法恢复,是否删除?'
|
|
|
- okText='删除'
|
|
|
- cancelText='取消'
|
|
|
- onConfirm={() => delImgListFu(v.id)}
|
|
|
- okButtonProps={{ loading: false }}
|
|
|
- >
|
|
|
- <CloseOutlined rev={undefined} title='删除' hidden={isLook} />
|
|
|
- </Popconfirm>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- ))}
|
|
|
+ <div className={styles.Z3files}>
|
|
|
+ <DndContext
|
|
|
+ collisionDetection={closestCenter}
|
|
|
+ onDragStart={handleDragStart}
|
|
|
+ onDragEnd={handleDragEnd}
|
|
|
+ >
|
|
|
+ <SortableContext items={fileList.map(f => f.id)} strategy={verticalListSortingStrategy}>
|
|
|
+ {fileList.map((file, index) => (
|
|
|
+ <SortableFileItem
|
|
|
+ key={file.id}
|
|
|
+ file={file}
|
|
|
+ onDelete={delImgListFu}
|
|
|
+ isLook={isLook}
|
|
|
+ index={index}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ </SortableContext>
|
|
|
+ <DragOverlay>
|
|
|
+ {activeFile ? (
|
|
|
+ <div className={classNames(styles.Z3filesRow, styles.dragOverlay)}>
|
|
|
+ <div className={styles.dragHandle}>
|
|
|
+ <MenuOutlined rev={undefined} />
|
|
|
+ </div>
|
|
|
+ <div className={styles.Z3files1} title={activeFile.fileName}>
|
|
|
+ {activeFile.fileName}
|
|
|
+ </div>
|
|
|
+ <div className={styles.Z3files2}>{/* 拖拽时隐藏操作按钮 */}</div>
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
+ </DragOverlay>
|
|
|
+ </DndContext>
|
|
|
</div>
|
|
|
|
|
|
- <div className='fileTit' hidden={isLook}>
|
|
|
- {tips}
|
|
|
- <br />
|
|
|
+ <div className={styles.fileTit} hidden={isLook}>
|
|
|
+ {tips};支持按住Ctrl键选择多个文件;拖动附件左侧图标可调整顺序
|
|
|
<div
|
|
|
className={classNames(
|
|
|
- 'noUpThumb',
|
|
|
- fileList.length <= 0 && fileCheck ? 'noUpThumbAc' : ''
|
|
|
+ styles.noUpThumb,
|
|
|
+ fileList.length <= 0 && fileCheck ? styles.noUpThumbAc : ''
|
|
|
)}
|
|
|
>
|
|
|
- 请上传视频!
|
|
|
+ 请上传文件!
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- {isLook && fileList.length <= 0 ? <div className='lookNone'>(空)</div> : null}
|
|
|
+ {isLook && fileList.length <= 0 ? <div className={styles.lookNone}>(空)</div> : null}
|
|
|
</div>
|
|
|
)
|
|
|
}
|