|
|
@@ -29,60 +29,204 @@ import ImageLazy from '../ImageLazy'
|
|
|
import store from '@/store'
|
|
|
import MyPopconfirm from '../MyPopconfirm'
|
|
|
|
|
|
-// 可排序的文件项组件
|
|
|
+// ----------------这个组件用于外面一级的附件上传
|
|
|
+
|
|
|
interface SortableFileItemProps {
|
|
|
file: FileListType
|
|
|
onDelete: (id: number) => void
|
|
|
isLook: boolean
|
|
|
index: number
|
|
|
+ disabled?: boolean
|
|
|
+ oneIsCover?: boolean
|
|
|
}
|
|
|
+// 修复性能问题:移除不必要的状态和事件处理
|
|
|
+const SortableFileItem = React.memo(
|
|
|
+ ({ file, onDelete, isLook, index, disabled, oneIsCover }: SortableFileItemProps) => {
|
|
|
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
|
+ id: file.id,
|
|
|
+ disabled: disabled || isLook // 修复拖动禁用问题
|
|
|
+ })
|
|
|
+
|
|
|
+ const style = {
|
|
|
+ transform: CSS.Transform.toString(transform),
|
|
|
+ transition,
|
|
|
+ opacity: isDragging ? 0.5 : 1
|
|
|
+ }
|
|
|
|
|
|
+ // 修复按钮点击问题:阻止事件冒泡
|
|
|
+ const handleButtonClick = useCallback((e: React.MouseEvent, callback: () => void) => {
|
|
|
+ e.stopPropagation() // 修复按钮点击无效问题:阻止事件冒泡到拖拽元素
|
|
|
+ callback()
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ if (file.type === 'img') {
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ ref={setNodeRef}
|
|
|
+ style={style}
|
|
|
+ className={classNames('ZTbox1ImgRow', isDragging ? 'dragging' : '')}
|
|
|
+ >
|
|
|
+ {/* 修复按钮点击无效问题:将拖拽手柄单独放置,不覆盖操作按钮 */}
|
|
|
+ <div className='ZTbox1ImgRowDragHandle' {...attributes} {...listeners}>
|
|
|
+ {file.thumb || file.filePath ? (
|
|
|
+ <ImageLazy noLook={true} width={100} height={100} src={file.thumb || file.filePath} />
|
|
|
+ ) : null}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {oneIsCover && index === 0 ? <div className='ZTbox1ImgRowCover'>封面</div> : null}
|
|
|
+
|
|
|
+ <div className='ZTbox1ImgRowIcon'>
|
|
|
+ <EyeOutlined
|
|
|
+ onClick={e =>
|
|
|
+ handleButtonClick(e, () =>
|
|
|
+ store.dispatch({
|
|
|
+ type: 'layout/lookBigImg',
|
|
|
+ payload: {
|
|
|
+ url: baseURL + file.filePath,
|
|
|
+ show: true
|
|
|
+ }
|
|
|
+ })
|
|
|
+ )
|
|
|
+ }
|
|
|
+ rev={undefined}
|
|
|
+ />
|
|
|
+ <a
|
|
|
+ href={baseURL + file.filePath}
|
|
|
+ download
|
|
|
+ target='_blank'
|
|
|
+ rel='noreferrer'
|
|
|
+ onClick={e => e.stopPropagation()} // 修复按钮点击无效问题
|
|
|
+ >
|
|
|
+ <DownloadOutlined rev={undefined} />
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {!isLook && (
|
|
|
+ <MyPopconfirm
|
|
|
+ txtK='删除'
|
|
|
+ onConfirm={() => onDelete(file.id)}
|
|
|
+ Dom={
|
|
|
+ <CloseOutlined
|
|
|
+ className='ZTbox1ImgRowX'
|
|
|
+ rev={undefined}
|
|
|
+ onClick={e => e.stopPropagation()} // 修复按钮点击无效问题
|
|
|
+ />
|
|
|
+ }
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ ref={setNodeRef}
|
|
|
+ style={style}
|
|
|
+ className={classNames('Z3filesRow', isDragging ? 'dragging' : '')}
|
|
|
+ >
|
|
|
+ {!isLook && (
|
|
|
+ <div className='dragHandle' {...attributes} {...listeners}>
|
|
|
+ <MenuOutlined rev={undefined} />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <div className='Z3files1' title={file.fileName}>
|
|
|
+ {file.fileName}
|
|
|
+ </div>
|
|
|
+ <div className='Z3files2'>
|
|
|
+ {authFilesLookFu(file.fileName, '') ? (
|
|
|
+ <>
|
|
|
+ <EyeOutlined
|
|
|
+ rev={undefined}
|
|
|
+ title='查看'
|
|
|
+ onClick={e =>
|
|
|
+ handleButtonClick(e, () => authFilesLookFu(file.fileName, file.filePath))
|
|
|
+ }
|
|
|
+ />
|
|
|
+  
|
|
|
+ </>
|
|
|
+ ) : null}
|
|
|
+ <a
|
|
|
+ title='下载'
|
|
|
+ href={baseURL + file.filePath}
|
|
|
+ download={file.fileName}
|
|
|
+ target='_blank'
|
|
|
+ rel='noreferrer'
|
|
|
+ onClick={e => e.stopPropagation()} // 修复按钮点击无效问题
|
|
|
+ >
|
|
|
+ <DownloadOutlined rev={undefined} />
|
|
|
+ </a>
|
|
|
+  
|
|
|
+ {!isLook && (
|
|
|
+ <Popconfirm
|
|
|
+ title='删除后无法恢复,是否删除?'
|
|
|
+ okText='删除'
|
|
|
+ cancelText='取消'
|
|
|
+ onConfirm={() => onDelete(file.id)}
|
|
|
+ okButtonProps={{ loading: false }}
|
|
|
+ >
|
|
|
+ <CloseOutlined
|
|
|
+ rev={undefined}
|
|
|
+ title='删除'
|
|
|
+ onClick={e => e.stopPropagation()} // 修复按钮点击无效问题
|
|
|
+ />
|
|
|
+ </Popconfirm>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// ------------------------------------------
|
|
|
type Props = {
|
|
|
- isLook: boolean // 是否是查看
|
|
|
- fileCheck: boolean
|
|
|
- dirCode: string // 文件的code码
|
|
|
+ dirCode: string
|
|
|
myUrl: string
|
|
|
+ isLook?: boolean
|
|
|
+ fileCheck?: boolean
|
|
|
fromData?: any
|
|
|
tips?: string
|
|
|
- // 文件大小
|
|
|
size?: number
|
|
|
- // 最大文件数量
|
|
|
maxCount?: number
|
|
|
- // 订单id
|
|
|
- moduleId: number
|
|
|
- oneIsCover?: boolean //是否将第一张作为封面
|
|
|
+ oneIsCover?: boolean
|
|
|
}
|
|
|
|
|
|
+// 修复类型检查问题:统一使用dnd-kit管理所有拖拽
|
|
|
function Z3upFiles({
|
|
|
- isLook,
|
|
|
- fileCheck,
|
|
|
+ isLook = false,
|
|
|
+ fileCheck = false,
|
|
|
dirCode,
|
|
|
myUrl,
|
|
|
fromData,
|
|
|
tips = '单个附件不得超过500M',
|
|
|
size = 500,
|
|
|
- moduleId,
|
|
|
maxCount = 999,
|
|
|
oneIsCover
|
|
|
}: Props) {
|
|
|
const { info, setInfoFu } = useInfo()
|
|
|
|
|
|
- const fileList = useMemo(() => {
|
|
|
- return info.files || []
|
|
|
+ // 修复图片闪动问题:分别管理图片和非图片的排序
|
|
|
+ const { imageFiles, otherFiles } = useMemo(() => {
|
|
|
+ const arr = info.files || []
|
|
|
+ const imgArr = arr.filter(v => v.type === 'img')
|
|
|
+ const otherArr = arr.filter(v => v.type !== 'img')
|
|
|
+ return { imageFiles: imgArr, otherFiles: otherArr }
|
|
|
}, [info.files])
|
|
|
|
|
|
+ const fileList = useMemo(() => {
|
|
|
+ return [...imageFiles, ...otherFiles]
|
|
|
+ }, [imageFiles, otherFiles])
|
|
|
+
|
|
|
+ // 修复性能问题:使用useCallback优化函数记忆
|
|
|
const setFileList = useCallback(
|
|
|
(newFiles: FileListType[] | ((prev: FileListType[]) => FileListType[])) => {
|
|
|
setInfoFu((prevInfo: Typetable) => {
|
|
|
const currentFiles = prevInfo.files || []
|
|
|
const updatedFiles = typeof newFiles === 'function' ? newFiles(currentFiles) : newFiles
|
|
|
|
|
|
- // 明确指定返回类型
|
|
|
const updatedInfo: Typetable = {
|
|
|
...prevInfo,
|
|
|
files: updatedFiles
|
|
|
}
|
|
|
-
|
|
|
return updatedInfo
|
|
|
})
|
|
|
},
|
|
|
@@ -92,6 +236,9 @@ function Z3upFiles({
|
|
|
const [activeId, setActiveId] = useState<number | null>(null)
|
|
|
const myInput = useRef<HTMLInputElement>(null)
|
|
|
|
|
|
+ // 修复性能问题:使用useMemo缓存文件ID数组
|
|
|
+ const fileIds = useMemo(() => fileList.map(f => f.id), [fileList])
|
|
|
+
|
|
|
// 上传多个文件
|
|
|
const handeUpPhoto = useCallback(
|
|
|
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
@@ -106,19 +253,17 @@ function Z3upFiles({
|
|
|
|
|
|
// 逐个上传文件
|
|
|
for (const file of files) {
|
|
|
- // 校验大小
|
|
|
if (size && file.size > size * 1024 * 1024) {
|
|
|
MessageFu.warning(`文件"${file.name}"超过${size}M限制!`)
|
|
|
continue
|
|
|
}
|
|
|
const typeRes = fileTypeRes(file.name)
|
|
|
- // 创建FormData对象
|
|
|
const fd = new FormData()
|
|
|
fd.append('type', typeRes)
|
|
|
fd.append('dirCode', dirCode)
|
|
|
fd.append('isCompress', 'true')
|
|
|
fd.append('isDb', 'true')
|
|
|
- fd.append('moduleId', moduleId + '')
|
|
|
+ fd.append('moduleId', info.id + '')
|
|
|
fd.append('file', file)
|
|
|
|
|
|
if (fromData) {
|
|
|
@@ -126,7 +271,7 @@ function Z3upFiles({
|
|
|
if (fromData[k]) fd.append(k, fromData[k])
|
|
|
}
|
|
|
}
|
|
|
- // 清空input值
|
|
|
+
|
|
|
e.target.value = ''
|
|
|
|
|
|
try {
|
|
|
@@ -135,7 +280,7 @@ function Z3upFiles({
|
|
|
MessageFu.success(
|
|
|
`上传成功${files.length > 1 ? `(${files.indexOf(file) + 1}/${files.length})` : ''}`
|
|
|
)
|
|
|
- setFileList(prev => [...prev, res.data])
|
|
|
+ setFileList(prev => [res.data, ...prev])
|
|
|
fileDomInitialFu()
|
|
|
} else {
|
|
|
fileDomInitialFu()
|
|
|
@@ -145,30 +290,35 @@ function Z3upFiles({
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- [dirCode, fileList.length, fromData, maxCount, moduleId, myUrl, setFileList, size]
|
|
|
+ [dirCode, fileList.length, fromData, info.id, maxCount, myUrl, setFileList, size]
|
|
|
)
|
|
|
|
|
|
- // 拖拽开始
|
|
|
+ // 修复拖拽逻辑问题:统一使用dnd-kit管理拖拽
|
|
|
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 activeFile = fileList.find(item => item.id === active.id)
|
|
|
+ const overFile = fileList.find(item => item.id === over.id)
|
|
|
+
|
|
|
+ // 修复类型检查问题:只有同类型文件才能交换位置
|
|
|
+ if (activeFile && overFile && activeFile.type === overFile.type) {
|
|
|
+ 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)
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
- [setFileList]
|
|
|
+ [fileList, setFileList]
|
|
|
)
|
|
|
|
|
|
// 列表删除某一个文件
|
|
|
@@ -182,136 +332,41 @@ function Z3upFiles({
|
|
|
// 获取当前拖拽的文件
|
|
|
const activeFile = activeId ? fileList.find(file => file.id === activeId) : null
|
|
|
|
|
|
- // 附件图片的拖动
|
|
|
- const [dragImg, setDragImg] = useState<any>(null)
|
|
|
+ // 修复拖拽覆盖层显示问题
|
|
|
+ const renderDragOverlay = useCallback(() => {
|
|
|
+ if (!activeFile) return null
|
|
|
|
|
|
- const handleDragOver = useCallback(
|
|
|
- (e: React.DragEvent<HTMLDivElement>, item: FileListType) => {
|
|
|
- if (isLook) return
|
|
|
- e.dataTransfer.dropEffect = 'move'
|
|
|
- },
|
|
|
- [isLook]
|
|
|
- )
|
|
|
-
|
|
|
- const handleDragEnter = useCallback(
|
|
|
- (e: React.DragEvent<HTMLDivElement>, item: FileListType) => {
|
|
|
- if (isLook) return
|
|
|
-
|
|
|
- e.dataTransfer.effectAllowed = 'move'
|
|
|
- if (item === dragImg) return
|
|
|
- const newItems = [...fileList] //拷贝一份数据进行交换操作。
|
|
|
- const src = newItems.indexOf(dragImg) //获取数组下标
|
|
|
- const dst = newItems.indexOf(item)
|
|
|
- newItems.splice(dst, 0, ...newItems.splice(src, 1)) //交换位置
|
|
|
- setFileList({ ...newItems })
|
|
|
- },
|
|
|
- [dragImg, fileList, isLook, setFileList]
|
|
|
- )
|
|
|
-
|
|
|
- // 详情子函数--------------------
|
|
|
- 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
|
|
|
- }
|
|
|
- if (file.type === 'img') {
|
|
|
+ if (activeFile.type === 'img') {
|
|
|
return (
|
|
|
- <div
|
|
|
- className='ZTbox1ImgRow'
|
|
|
- draggable='true'
|
|
|
- onDragStart={() => setDragImg(file)}
|
|
|
- onDragOver={e => handleDragOver(e, file)}
|
|
|
- onDragEnter={e => handleDragEnter(e, file)}
|
|
|
- onDragEnd={() => setDragImg(null)}
|
|
|
- >
|
|
|
- {file.thumb || file.filePath ? (
|
|
|
- <ImageLazy noLook={true} width={100} height={100} src={file.thumb || file.filePath} />
|
|
|
- ) : null}
|
|
|
-
|
|
|
- {oneIsCover && index === 0 ? <div className='ZTbox1ImgRowCover'>封面</div> : null}
|
|
|
-
|
|
|
- <div className='ZTbox1ImgRowIcon'>
|
|
|
- <EyeOutlined
|
|
|
- onClick={() =>
|
|
|
- store.dispatch({
|
|
|
- type: 'layout/lookBigImg',
|
|
|
- payload: {
|
|
|
- url: baseURL + file.filePath,
|
|
|
- show: true
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- rev={undefined}
|
|
|
+ <div className={classNames('ZTbox1ImgRow', 'dragOverlay')}>
|
|
|
+ {activeFile.thumb || activeFile.filePath ? (
|
|
|
+ <ImageLazy
|
|
|
+ noLook={true}
|
|
|
+ width={100}
|
|
|
+ height={100}
|
|
|
+ src={activeFile.thumb || activeFile.filePath}
|
|
|
/>
|
|
|
- <a href={baseURL + file.filePath} download target='_blank' rel='noreferrer'>
|
|
|
- <DownloadOutlined rev={undefined} />
|
|
|
- </a>
|
|
|
+ ) : null}
|
|
|
+ <div className='ZTbox1ImgRowIcon' style={{ opacity: 0.5 }}>
|
|
|
+ <EyeOutlined rev={undefined} />
|
|
|
+ <DownloadOutlined rev={undefined} />
|
|
|
</div>
|
|
|
-
|
|
|
- <MyPopconfirm
|
|
|
- txtK='删除'
|
|
|
- onConfirm={() => delImgListFu(file.id)}
|
|
|
- Dom={<CloseOutlined className='ZTbox1ImgRowX' rev={undefined} />}
|
|
|
- />
|
|
|
</div>
|
|
|
)
|
|
|
} else {
|
|
|
return (
|
|
|
- <div
|
|
|
- ref={setNodeRef}
|
|
|
- style={style}
|
|
|
- className={classNames('Z3filesRow', isDragging ? 'dragging' : '')}
|
|
|
- >
|
|
|
- {!isLook && (
|
|
|
- <div className='dragHandle' {...attributes} {...listeners}>
|
|
|
- <MenuOutlined rev={undefined} />
|
|
|
- </div>
|
|
|
- )}
|
|
|
- <div className='Z3files1' title={file.fileName}>
|
|
|
- {file.fileName}
|
|
|
+ <div className={classNames('Z3filesRow', 'dragOverlay')}>
|
|
|
+ <div className='dragHandle'>
|
|
|
+ <MenuOutlined rev={undefined} />
|
|
|
</div>
|
|
|
- <div className='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 className='Z3files1' title={activeFile.fileName}>
|
|
|
+ {activeFile.fileName}
|
|
|
</div>
|
|
|
+ <div className='Z3files2'>{/* 拖拽时隐藏操作按钮 */}</div>
|
|
|
</div>
|
|
|
)
|
|
|
}
|
|
|
- }
|
|
|
+ }, [activeFile])
|
|
|
|
|
|
return (
|
|
|
<div className={styles.Z3upFiles}>
|
|
|
@@ -320,7 +375,8 @@ function Z3upFiles({
|
|
|
type='file'
|
|
|
ref={myInput}
|
|
|
onChange={handeUpPhoto}
|
|
|
- multiple // 支持多选
|
|
|
+ multiple
|
|
|
+ style={{ display: 'none' }}
|
|
|
/>
|
|
|
<div className='Z3Btn'>
|
|
|
{!isLook && (
|
|
|
@@ -338,7 +394,7 @@ function Z3upFiles({
|
|
|
onDragStart={handleDragStart}
|
|
|
onDragEnd={handleDragEnd}
|
|
|
>
|
|
|
- <SortableContext items={fileList.map(f => f.id)} strategy={verticalListSortingStrategy}>
|
|
|
+ <SortableContext items={fileIds} strategy={verticalListSortingStrategy}>
|
|
|
{fileList.map((file, index) => (
|
|
|
<SortableFileItem
|
|
|
key={file.id}
|
|
|
@@ -346,27 +402,17 @@ function Z3upFiles({
|
|
|
onDelete={delImgListFu}
|
|
|
isLook={isLook}
|
|
|
index={index}
|
|
|
+ oneIsCover={oneIsCover}
|
|
|
/>
|
|
|
))}
|
|
|
</SortableContext>
|
|
|
- <DragOverlay>
|
|
|
- {activeFile ? (
|
|
|
- <div className={classNames('Z3filesRow', 'dragOverlay')}>
|
|
|
- <div className='dragHandle'>
|
|
|
- <MenuOutlined rev={undefined} />
|
|
|
- </div>
|
|
|
- <div className='Z3files1' title={activeFile.fileName}>
|
|
|
- {activeFile.fileName}
|
|
|
- </div>
|
|
|
- <div className='Z3files2'>{/* 拖拽时隐藏操作按钮 */}</div>
|
|
|
- </div>
|
|
|
- ) : null}
|
|
|
- </DragOverlay>
|
|
|
+ <DragOverlay>{renderDragOverlay()}</DragOverlay>
|
|
|
</DndContext>
|
|
|
</div>
|
|
|
|
|
|
<div className='fileTit' hidden={isLook}>
|
|
|
- {tips};支持按住Ctrl键选择多个文件;按住鼠标拖动图片/拖动附件左侧图标 可调整顺序
|
|
|
+ {tips}
|
|
|
+ ;支持按住Ctrl键选择多个文件上传;按住鼠标拖动图片 / 拖动附件左侧图标 可调整顺序
|
|
|
<div
|
|
|
className={classNames(
|
|
|
'noUpThumb',
|
|
|
@@ -384,4 +430,4 @@ function Z3upFiles({
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-export default Z3upFiles
|
|
|
+export default React.memo(Z3upFiles)
|