123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 |
- import useCopy from "@/Hook/useCopy"
- import React, { useCallback, useEffect, useState } from "react"
- import { Carousel, Image, message, Pagination, Popconfirm, Spin } from 'antd'
- import style from './index.less'
- import FileModal from './components/fileModal'
- import { useModel } from "umi"
- import ImgModal from "./components/imgModal"
- import TreeBox from "./components/tree"
- import SortModal from "./components/fileModal/sortModal"
- import { EyeOutlined } from "@ant-design/icons"
- import ImgsModal from "./components/imgsModal"
- import MoveTo from "./components/moveTo"
- import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews"
- import { getVideoImgUrl } from "@/utils/utils"
- import useFileDrop from "@/Hook/useFileDrop"
- import { RcFile } from "antd/lib/upload"
- import UploadsTable from "./components/uploadsTable"
- interface News {
- id: number,
- sysMediaId: number,
- sortIndex: number,
- title: string,
- thumbUrl: string,
- author: string,
- digest: string,
- showCoverPic: boolean,
- content: string,//内容只在调取详情时获取
- contentSourceUrl: string,//文章地址
- needOpenComment: boolean,
- onlyFansCanComment: boolean
- }
- interface Item {
- id: number,
- folder: boolean,//是否是文件夹
- mediaType: 'VIDEO' | 'IMG' | 'PAGE',//类型
- number: string,//编号
- title: string,//名称
- url: string,//链接
- belongType: string,//所属类型
- createTime: string,//创建时间
- description: string,//k图文描述
- news?: News[]//图文内容
- knewsThumbUrl: string, // 图片链接
- knewsThumbId: number,//本地图片ID
- knewsThumbInfo: any,//k客服详情
- videoTitle: string,//视频标题
- width: number, // 宽
- height: number, // 高
- pageSpecsList?: any[],
- }
- interface Props {
- isAll?: boolean,//是否允许全选默认开启
- height?: any,//当使用为弹窗组件时设置高度以免太高
- noFile?: boolean,//是否禁止选择文件夹
- setPage?: (type: 0 | 1 | 2 | 3, data?: any) => void, // 0 创建 1 查看 2 复制
- isBack?: boolean, // 是否需要回填
- }
- function FlieBox(props: Props) {
- const { isAll = true, height, noFile = false, setPage, isBack = false } = props
- const { state, set, dels, list, get, onFile, allFile, delPupOn, delPupOff, changeClickFile, editFile, fileClick, treeClick, pathClick, getList, edit_media_folder, get_folder_tree } = useModel(isAll ? 'useLaunchAdq.useBdMedia' : 'useLaunchAdq.useBdMediaPup')
- const { fileVisible, belongUser, selectFile, selectItem, delPupId, xy, rightClickPup, path, publicPath, parentId, imgVisrible, imgsVisrible, mediaType, sortVisible, num } = state
- const { copy } = useCopy()
- const fileImg = require('../../../public/file.png')
- const [moveId, setMoveId] = useState<any>('')//移动的素材ID
- const [treeEl, item, folderId, setActionId, setHoverId] = TreeBox({ data: get_folder_tree.data, belongUser })
- const [listData, setListData] = useState<any>({})
- const [showImg, setShowImg] = useState(-1)
- /********************************/
- let moveSelected = document.getElementById("moveSelected"); // 选区
- let fileContent = document.getElementById("file_content"); // 内容区
- const [startPoint, setStartPoint] = useState({ x: 0, y: 0, flag: false }); // 记录鼠标开始按下坐标 flag 是否开启拖拽
- const [isSelect, setIsSelect] = useState<boolean>(false) // 记录是否框选过素材
- const [sourceList, setSourceList] = useState<any>({})
- const [moveShow, setMoveShow] = useState<boolean>(false)
- /********************************/
- // 处理数据
- useEffect(() => {
- let newList: any = {}
- listData?.records?.forEach((item: { id: number }) => {
- newList[item.id] = JSON.parse(JSON.stringify(item))
- })
- setSourceList(newList)
- }, [listData?.records])
- // 获取数据
- useEffect(() => {
- setListData(list?.data)
- }, [list, mediaType])
- /**复制编号 */
- const copyId = useCallback((e: React.MouseEvent<HTMLSpanElement, MouseEvent>, value: string) => {
- e.stopPropagation()//阻止冒泡传递到文件夹被点击事件
- copy(value)
- }, [])
- /**全局右键菜单 */
- const Menu = useCallback((props: { isItem?: boolean }) => {
- if (props.isItem && isAll) {
- return <ul style={{ top: xy?.y, left: xy?.x }} className={style.menu} >
- {(isAll || num === 100) && <li onClick={() => allFile('xz')}>全选</li>}
- {(isAll || num === 100) && listData?.records && listData?.records?.some((item: { id: number }) => selectFile?.includes(item.id)) && <li onClick={() => allFile('qx')} style={{ color: 'red' }}>取消选择</li>}
- {mediaType === 'PAGE' && !rightClickPup.folder ? <>
- {/* <li onClick={() => { setPage && setPage(1, rightClickPup.id) }}>查看</li> */}
- <li onClick={() => { setPage && setPage(2, rightClickPup.id) }}>复制</li>
- <li onClick={() => { setPage && setPage(3, rightClickPup.id) }}>批量复制</li>
- </> : <>
- <li onClick={() => { editFile() }}>编辑</li>
- </>}
- {mediaType !== 'PAGE' && !rightClickPup.folder && <li onClick={() => { copy(rightClickPup?.url) }}>复制链接</li>}
- <li onClick={() => { set({ actionItem: rightClickPup, sortVisible: true }) }}>编辑排序</li>
- <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li>
- <li onClick={() => { set({ imgVisrible: true }) }}>新建素材</li>
- {isAll && selectFile && selectFile?.length > 0 && <li onClick={() => { setMoveShow(true) }}>移动至</li>}
- <li onClick={(e) => { delPupOn(rightClickPup.id); onFile(e, rightClickPup, isAll, true) }} style={{ color: 'red' }}>删除</li>
- {isAll && selectFile && selectFile?.length > 0 && <li onClick={dels} style={{ color: 'red' }}>删除选中文件</li>}
- </ul>
- }
- return <ul style={{ top: xy?.y, left: xy?.x }} className={style.menu}>
- {(isAll || num === 100) && <li onClick={() => allFile('xz')}>全选</li>}
- {(isAll || num === 100) && listData?.records && listData?.records?.some((item: { id: number }) => selectFile?.includes(item.id)) && <li onClick={() => allFile('qx')} style={{ color: 'red' }}>取消选择</li>}
- {//防止K图文无限嵌套创建判断
- (isAll !== false) && <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li> //: <li>此处无法新建操作</li>
- }
- {mediaType === 'PAGE' ? isAll ? <li onClick={() => { setPage && setPage(0) }}>新建素材</li> : <li>此处无法新建操作</li> : <li onClick={() => { set({ imgVisrible: true }) }}>新建素材</li>}
- {mediaType === 'IMG' && <li onClick={() => { set({ imgsVisrible: true }) }}>批量新建素材</li>}
- {isAll && selectFile && selectFile?.length > 0 && <li onClick={dels} style={{ color: 'red' }}>删除选中文件</li>}
- </ul>
- }, [xy, rightClickPup, allFile, mediaType])
- /**鼠标右键 */
- const rightMenu = useCallback((e: any, isItem?: any) => {
- e.stopPropagation()
- e.preventDefault()
- let x = e.clientX;
- let y = e.clientY;
- set({ xy: { x, y } })
- if (isItem) {
- set({ rightClickPup: isItem })
- } else {
- set({ rightClickPup: { id: 'all' } })
- }
- return false;
- }, [])
- /**左键点击页面隐藏菜单 */
- useEffect(() => {
- function fnc(e: any) {
- set({ rightClickPup: { id: '' } })
- }
- document.addEventListener('click', fnc)
- return () => {
- document.removeEventListener('click', fnc)
- }
- }, [])
- // /**切换个人|公共|父目录ID变化清空选中的文件*/
- useEffect(() => {
- if (!isBack) {
- set({ selectFile: [], selectItem: [] })
- }
- if (belongUser == '1' && path) {//处理切换个人|公共时保留路径层级的请求
- set({ parentId: path[path?.length - 1]?.id || null })
- }
- if (belongUser == '0' && publicPath) {
- set({ parentId: publicPath[publicPath?.length - 1]?.id || null })
- }
- }, [belongUser, parentId, path?.length, publicPath?.length])
- /**卸载组件处理 */
- useEffect(() => {
- return () => {
- set({ parentId: null })
- }
- }, [])
- /**获取目录树*/
- useEffect(() => {
- get_folder_tree.run({ belongUser, mediaType })
- }, [isAll, mediaType, belongUser])
- // 点击目录树进入对应目录
- useEffect(() => {
- if (item) {
- let { icon, children, ...arg } = item
- treeClick(arg)
- }
- }, [item])
- // 拖动事件配置
- const moveConfig = useCallback((item: any) => {
- return {
- draggable: true,
- onDragStart: (ev: any) => {
- let img = document.createElement('img')
- img.src = fileImg;
- ev.dataTransfer.setDragImage(img, 0, 0)
- console.log('item---->', item)
- setMoveId(item.id)
- },
- onDragEnd: (e: any) => {
- if (folderId !== '' && moveId !== '') {
- edit_media_folder.run({ sysMediaId: moveId, folderId, mediaType }).then(res => {
- message.success('操作成功')
- list.refresh()
- })
- }
- setActionId('')
- setHoverId('')
- setMoveId('')
- },
- }
- }, [folderId, moveId, mediaType])
- /** 判断2个元素是否重合 */
- const elementsOverlap = (el1: HTMLElement, el2: HTMLElement) => {
- const domRect1 = el1.getBoundingClientRect();
- const domRect2 = el2.getBoundingClientRect();
- return !(
- domRect1.top > domRect2.bottom ||
- domRect1.right < domRect2.left ||
- domRect1.bottom < domRect2.top ||
- domRect1.left > domRect2.right
- );
- }
- /**记录鼠标down event */
- const onMouseDown = ({ target: { dataset }, pageX, pageY }: any) => {
- const { type } = dataset
- if (isBack && num !== 100) return
- if (moveSelected && type === 'right-boxzones') {
- moveSelected.style.top = pageY + 'px';
- moveSelected.style.left = pageX + 'px';
- setStartPoint({ x: pageX, y: pageY, flag: true })
- }
- }
- /**记录鼠标抬起事件 */
- const onMouseUp = () => {
- if (startPoint.flag && moveSelected) {
- setStartPoint({ x: 0, y: 0, flag: false })
- moveSelected.style.width = '0px';
- moveSelected.style.height = '0px';
- moveSelected.style.top = '0px';
- moveSelected.style.left = '0px';
- moveSelected.style.bottom = '0px';
- moveSelected.style.right = '0px';
- }
- }
- /** 鼠标移动事件 */
- const onMouseMove = ({ pageX, pageY }: any) => {
- const { x, y, flag } = startPoint
- if (flag) {
- if (moveSelected) {
- if (pageX < x) { //表示左移
- moveSelected.style.left = pageX + 'px';
- moveSelected.style.width = (x - pageX) + 'px'; //向左移动的距离作为选区的宽
- } else {
- moveSelected.style.width = (pageX - x) + 'px';
- }
- if (pageY < y) { //向上移动
- moveSelected.style.top = pageY + 'px';
- moveSelected.style.height = (y - pageY) + 'px';
- } else {
- moveSelected.style.height = (pageY - y) + 'px';
- }
- //通过得到的left和top加上元素自身的宽高来计算选区的right和bottom
- moveSelected.style.bottom = Number(moveSelected.style.top.split('px')[0]) + Number(moveSelected.style.height.split('px')[0]) + 'px';
- moveSelected.style.right = Number(moveSelected.style.left.split('px')[0]) + Number(moveSelected.style.width.split('px')[0]) + 'px';
- if (fileContent && fileContent?.children?.length > 0) {
- //找出选中的区域并激活
- let checkboxs: any = fileContent?.children
- for (let i = 0; i < checkboxs?.length; i++) {
- let id = checkboxs[i]?.getAttribute('data-id')
- if (elementsOverlap(moveSelected, checkboxs[i]) && id) {
- id = Number(id)
- let state = selectFile?.some((i) => i === id)
- if (!state) {//新增
- setIsSelect(true)
- set({ selectFile: [...selectFile as number[], id] })
- if (isBack) {
- set({ selectItem: [...selectItem, sourceList[id]] })
- }
- }
- }
- }
- }
- }
- }
- }
- /** 鼠标离开可拖动区域事件 */
- const onMouseLeave = () => {
- if (startPoint.flag && moveSelected) {
- setStartPoint({ x: 0, y: 0, flag: false })
- moveSelected.style.width = '0px';
- moveSelected.style.height = '0px';
- moveSelected.style.top = '0px';
- moveSelected.style.left = '0px';
- }
- }
- // 获取拖拽的文件
- const [fileList, setFileList] = useState<FileList>()
- const [uploadVisible, setUploadVisible] = useState<boolean>(false)
- const { isDragOver, dropAreaProps } = useFileDrop((fileList) => {
- if (!moveId) {
- setFileList(fileList)
- setUploadVisible(true)
- }
- });
- return <div style={{ display: 'flex', flexFlow: 'row', width: '100%', padding: get_folder_tree?.data?.length ? 0 : '0 16px 0' }}>
- {get_folder_tree?.data?.length > 0 && <div style={{ flexShrink: 0 }}>
- {treeEl}
- </div>}
- <div
- style={{ flex: 1, padding: '10px 0px 0px' }}
- data-type="right-boxzones"
- onMouseDown={onMouseDown}
- onMouseUp={onMouseUp}
- onMouseLeave={onMouseLeave}
- onMouseMove={onMouseMove}
- onDoubleClick={() => {
- if (isSelect) {
- set({ selectItem: [] }); set({ selectFile: [] })
- }
- }}
- >
- <div
- className={style.files}
- data-type="right-boxzones"
- onContextMenu={rightMenu}
- style={height ? { height } : {}}
- {...(mediaType !== 'PAGE' ? dropAreaProps : {})}
- >
- {mediaType !== 'PAGE' && isDragOver && !moveId && <div className={`${style.placeholder} ${isDragOver ? style.dragOver : ''}`}><span>拖动文件到这里</span></div>}
- {/* 层级路径 */}
- <div className={style.path} >
- {//存在渲染个人本地路径不存在执行公共本地
- belongUser == '1' && path?.map((item: { title: string }, index: number) => {
- return <span key={index} onClick={() => { pathClick({ ...item, index }) }} >
- <span className={path?.length !== index + 1 ? style.path_color : ''}>{item?.title}</span>
- {path?.length !== index + 1 && <span className={style.rt} >{'>'}</span>}
- </span>
- })
- }
- {belongUser == '0' && publicPath?.map((item: { title: string }, index: number) => {
- return <span key={index} onClick={() => { pathClick({ ...item, index }) }} >
- <span className={publicPath?.length !== index + 1 ? style.path_color : ''}>{item?.title}</span>
- {publicPath?.length !== index + 1 && <span className={style.rt} >{'>'}</span>}
- </span>
- })}
- </div>
- {/* 内容 */}
- <Spin spinning={list.loading} style={{ width: '100%' }}>
- <div className={style.file_content} data-type="right-boxzones" id="file_content">
- {listData?.records?.map((item: Item, index: number) => {
- if (item.folder) {
- {/* 文件夹模板 */ }
- return <Popconfirm
- title="确定要删除吗?"
- onConfirm={() => { dels(item) }}
- okText="是"
- cancelText="否"
- onCancel={delPupOff}
- visible={delPupId === item.id}
- key={item.id}
- >
- <Spin tip='正在请求素材详情,请耐心等待...' spinning={get?.loading}>
- <div
- className={`${style.flex_box} ${selectFile?.some((id: number) => id === item.id) ? style.action : ''}`}
- onContextMenu={(e) => { rightMenu(e, item) }}
- onClick={(e) => {
- e.stopPropagation()
- e.preventDefault()
- changeClickFile(e, item, isAll, noFile)
- }}
- onDragStart={(e: any) => {
- e.preventDefault()
- }}
- >
- <img src={fileImg}
- className={style.flex_box_img}
- onClick={(e: any) => { e.stopPropagation(); fileClick(item) }}
- onDragOver={(ev) => {
- ev.preventDefault()
- }}
- onDrop={() => {
- if (item.id !== folderId) {
- setActionId(item.id)
- }
- }}
- />
- <span className={style.flex_box_name} >{item?.title}</span>
- <span className={style.flex_box_id} onClick={(e) => { copyId(e, item?.number) }} >{item?.number}</span>
- </div>
- </Spin>
- </Popconfirm>
- } else {
- {/* 图片模板 ,视频模板,音频模板*/ }
- let topPageElements: any
- let topName: string = ""
- if (mediaType === 'PAGE' && item?.pageSpecsList) {
- topPageElements = item?.pageSpecsList[0]?.pageElementsSpecList[0]
- }
- let El = null
- if (mediaType === 'IMG') {
- El = <Image
- rootClassName={style.imgData}
- src={item.url}
- onClick={(e) => {
- e.stopPropagation()
- let className = (e.target as any).className
- if (className === 'ant-image-mask-info') {
- setShowImg(index)
- } else {
- changeClickFile(e, item, isAll, noFile)
- }
- }}
- preview={{
- visible: showImg === index,
- maskClassName: style.maskClass,
- onVisibleChange: (value) => {
- if (!value) {
- setShowImg(-1)
- }
- }
- }}
- />
- } else if (mediaType === 'VIDEO') {
- El = <VideoNews src={item.url} />
- } else if (mediaType === 'PAGE') {
- switch (topPageElements?.elementType) {
- case 'TOP_IMAGE':
- topName = "顶部图片"
- El = <Image
- src={topPageElements?.topImageSpec?.imageUrl}
- preview={{ visible: false, maskClassName: style.maskClass }}
- onClick={(e) => {
- e.stopPropagation()
- let className = (e.target as any).className
- if (className === 'ant-image-mask-info') {
- setPage && setPage(1, item.id)
- } else {
- changeClickFile(e, item, isAll, noFile)
- }
- }}
- />
- break
- case 'TOP_SLIDER':
- topName = "顶部轮播图"
- El = <>
- <Carousel autoplay style={{ width: 150, textAlign: 'center' }}>
- {topPageElements?.topSliderSpec?.imageUrlList?.map((url: string, index: number) => <div key={index}>
- <Image
- preview={{ visible: false, maskClassName: style.maskClass }}
- src={url}
- onClick={(e) => {
- e.stopPropagation()
- let className = (e.target as any).className
- if (className === 'ant-image-mask-info') {
- setPage && setPage(1, item.id)
- } else {
- changeClickFile(e, item, isAll, noFile)
- }
- }} />
- </div>)}
- </Carousel>
- </>
- break
- case 'TOP_VIDEO':
- topName = "顶部视频"
- El = <Image
- src={getVideoImgUrl(topPageElements?.topVideoSpec?.videoUrl)}
- preview={{ visible: false, maskClassName: style.maskClass }}
- onClick={(e) => {
- e.stopPropagation()
- let className = (e.target as any).className
- if (className === 'ant-image-mask-info') {
- setPage && setPage(1, item.id)
- } else {
- changeClickFile(e, item, isAll, noFile)
- }
- }}
- />
- break
- }
- }
- return <Popconfirm
- title="确定要删除吗?"
- onConfirm={() => { dels(item.id) }}
- okText="是"
- cancelText="否"
- onCancel={delPupOff}
- visible={delPupId === item.id}
- key={item.id}
- >
- <Spin tip='正在请求素材详情,请耐心等待...' spinning={get?.loading} data-id={item.id}>
- <div
- className={`${style.image_box} ${!isBack ? (selectFile?.some((id: number) => id === item.id) ? style.action : '') : (selectItem?.some((item1: { url: string }) => item1.url === item.url) ? style.action : '')}`}
- onContextMenu={(e) => { rightMenu(e, item) }}
- onClick={(e) => { changeClickFile(e, item, isAll) }}
- {...moveConfig(item)}
- >
- <span className={`${style.select}`} onClick={(e) => { changeClickFile(e, item, isAll) }}>
- <span role="img" aria-label="check" style={{ color: '#fff' }}><svg viewBox="64 64 896 896" focusable="false" data-icon="check" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"></path></svg></span>
- </span>
- {El}
- <span className={style.flex_box_name} onClick={(e) => { copyId(e, item?.videoTitle || item?.title) }}>{item?.videoTitle || item?.title}</span>
- {mediaType === 'PAGE' ? <span>{topName}</span> : <span>{item?.width}*{item.height}</span>}
- </div>
- </Spin>
- </Popconfirm>
- }
- })}
- </div>
- </Spin>
- {/* 鼠标右键菜单 */}
- {rightClickPup.id ? rightClickPup.id === 'all' ? <Menu /> : <Menu isItem /> : null}
- {/* 新建文件弹窗 */}
- {fileVisible && <FileModal isAll={isAll} />}
- {/* 编辑排序 */}
- {sortVisible && <SortModal isAll={isAll} />}
- {/* 新建非图文 */}
- {imgVisrible && <ImgModal isAll={isAll} />}
- {imgsVisrible && <ImgsModal isAll={isAll} />}
- {/* 移动至 */}
- {moveShow && <MoveTo visible={moveShow} onClose={() => setMoveShow(false)} isAll={isAll} catalogueData={get_folder_tree.data} />}
- </div>
- <div className={style.pagination}>
- {/* 分页 */}
- {listData?.records?.length > 0 && <Pagination
- showSizeChanger
- onChange={(page: number, pageSize?: number) => {
- getList({ pageSize, pageNum: page })
- }}
- onShowSizeChange={(current: number, size: number) => {
- getList({ pageSize: size, pageNum: current })
- }}
- pageSizeOptions={['10', '20', '30', '50', '100']}
- current={listData?.current}
- defaultPageSize={30}
- total={listData?.total}
- />}
- </div>
- <div className={style.moveSelected} id='moveSelected' />
- </div>
- {uploadVisible && <UploadsTable
- parentId={parentId}
- belongUser={belongUser}
- mediaType={mediaType}
- visible={uploadVisible}
- fileList={fileList}
- onClose={() => {
- setUploadVisible(false)
- setFileList(undefined)
- }}
- onChange={() => {
- setUploadVisible(false)
- setFileList(undefined)
- list.refresh()
- }}
- />}
- </div>
- }
- export default React.memo(FlieBox)
|