index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import useCopy from "@/Hook/useCopy"
  2. import React, { useCallback, useEffect, useState } from "react"
  3. import { Image, message, Pagination, Popconfirm, Spin } from 'antd'
  4. import style from './index.less'
  5. import FileModal from './components/fileModal'
  6. import { useModel } from "umi"
  7. import ImgModal from "./components/imgModal"
  8. import TreeBox from "./components/tree"
  9. import SortModal from "./components/fileModal/sortModal"
  10. interface News {
  11. id: number,
  12. sysMediaId: number,
  13. sortIndex: number,
  14. title: string,
  15. thumbUrl: string,
  16. author: string,
  17. digest: string,
  18. showCoverPic: boolean,
  19. content: string,//内容只在调取详情时获取
  20. contentSourceUrl: string,//文章地址
  21. needOpenComment: boolean,
  22. onlyFansCanComment: boolean
  23. }
  24. interface Item {
  25. id: number,
  26. folder: boolean,//是否是文件夹
  27. mediaType: 'VIDEO' | 'IMG',//类型
  28. number: string,//编号
  29. title: string,//名称
  30. url: string,//链接
  31. belongType: string,//所属类型
  32. createTime: string,//创建时间
  33. description: string,//k图文描述
  34. news?: News[]//图文内容
  35. knewsThumbUrl: string, // 图片链接
  36. knewsThumbId: number,//本地图片ID
  37. knewsThumbInfo: any,//k客服详情
  38. videoTitle: string,//视频标题
  39. width: number, // 宽
  40. height: number, // 高
  41. }
  42. interface Props {
  43. isAll?: boolean,//是否允许全选默认开启
  44. height?: any,//当使用为弹窗组件时设置高度以免太高
  45. noFile?: boolean,//是否禁止选择文件夹
  46. isBd?: boolean,//是否强制使用本地数据
  47. }
  48. function FlieBox(props: Props) {
  49. const { isAll = true, height, noFile = false, isBd } = props
  50. const { state, set, dels, listImg, listVideo, get, onFile, allFile, delPupOn, delPupOff, changeClickFile, editFile, fileClick, treeClick, pathClick, getList, edit_media_folder, get_folder_tree_img, get_folder_tree_video } = useModel(isAll || isBd ? 'useLaunchAdq.useBdMedia' : 'useLaunchAdq.useBdMediaPup')
  51. const { fileVisible, belongUser, selectFile, delPupId, xy, rightClickPup, path, publicPath, parentId, imgVisrible, mediaType, sortVisible } = state
  52. const { copy } = useCopy()
  53. const fileImg = require('../../../public/file.png')
  54. const [moveId, setMoveId] = useState<any>('')//移动的素材ID
  55. const [treeEl, item, folderId, setActionId, setHoverId] = TreeBox({ data: mediaType === 'IMG' ? get_folder_tree_img.data : get_folder_tree_video.data, belongUser })
  56. const [listData, setListData] = useState<any>({})
  57. // 获取数据
  58. useEffect(() => {
  59. if (mediaType === 'IMG') {
  60. setListData(listImg?.data)
  61. } else if (mediaType === 'VIDEO') {
  62. setListData(listVideo?.data)
  63. } else {
  64. setListData({})
  65. }
  66. }, [listImg, listVideo, mediaType])
  67. /**复制编号 */
  68. const copyId = useCallback((e: React.MouseEvent<HTMLSpanElement, MouseEvent>, value: string) => {
  69. e.stopPropagation()//阻止冒泡传递到文件夹被点击事件
  70. copy(value)
  71. }, [])
  72. /**全局右键菜单 */
  73. const Menu = useCallback((props: { isItem?: boolean }) => {
  74. if (props.isItem && isAll) {
  75. return <ul style={{ top: xy?.y, left: xy?.x }} className={style.menu} >
  76. {
  77. isAll && <li onClick={allFile}>全选/反选</li>
  78. }
  79. <li onClick={(e) => { delPupOn(rightClickPup.id); onFile(e, rightClickPup, isAll, true) }}> 删除</li>
  80. <li onClick={() => { editFile() }}>编辑</li>
  81. <li onClick={() => { set({ actionItem: rightClickPup, sortVisible: true }) }}>编辑排序</li>
  82. <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li>
  83. <li onClick={() => { set({ imgVisrible: true }) }}>新建素材</li>
  84. {
  85. isAll && <li onClick={dels}>删除选中文件</li>
  86. }
  87. </ul>
  88. }
  89. return <ul style={{ top: xy?.y, left: xy?.x }} className={style.menu}>
  90. {
  91. isAll && <li onClick={allFile}>全选/反选</li>
  92. }
  93. {//防止K图文无限嵌套创建判断
  94. (isAll !== false) ? <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li> : <li>此处无法新建操作</li>
  95. }
  96. {
  97. (isAll !== false) && <li onClick={() => { set({ imgVisrible: true }) }}>新建素材</li>
  98. }
  99. {
  100. isAll && <li onClick={dels}>删除选中文件</li>
  101. }
  102. </ul>
  103. }, [xy, rightClickPup, allFile, mediaType])
  104. /**鼠标右键 */
  105. const rightMenu = useCallback((e: any, isItem?: any) => {
  106. e.stopPropagation()
  107. e.preventDefault()
  108. let x = e.clientX;
  109. let y = e.clientY;
  110. set({ xy: { x, y } })
  111. if (isItem) {
  112. set({ rightClickPup: isItem })
  113. } else {
  114. set({ rightClickPup: { id: 'all' } })
  115. }
  116. return false;
  117. }, [])
  118. /**左键点击页面隐藏菜单 */
  119. useEffect(() => {
  120. function fnc(e: any) {
  121. set({ rightClickPup: { id: '' } })
  122. }
  123. document.addEventListener('click', fnc)
  124. return () => {
  125. document.removeEventListener('click', fnc)
  126. }
  127. }, [])
  128. // /**切换个人|公共|父目录ID变化清空选中的文件*/
  129. useEffect(() => {
  130. set({ selectFile: [] })
  131. if (belongUser == '1' && path) {//处理切换个人|公共时保留路径层级的请求
  132. set({ parentId: path[path?.length - 1]?.id || null })
  133. }
  134. if (belongUser == '0' && publicPath) {
  135. set({ parentId: publicPath[publicPath?.length - 1]?.id || null })
  136. }
  137. }, [belongUser, parentId, path?.length, publicPath?.length])
  138. /**卸载组件处理 */
  139. useEffect(() => {
  140. return () => {
  141. set({ parentId: null })
  142. }
  143. }, [])
  144. /**获取目录树*/
  145. useEffect(() => {
  146. // if (isBd) {
  147. if (mediaType === 'IMG') {
  148. get_folder_tree_img.run({ belongUser })
  149. } else if (mediaType === 'VIDEO') {
  150. get_folder_tree_video.run({ belongUser })
  151. }
  152. // }
  153. }, [isAll, isBd, mediaType, belongUser])
  154. // 点击目录树进入对应目录
  155. useEffect(() => {
  156. if (item) {
  157. let { icon, children, ...arg } = item
  158. treeClick(arg)
  159. }
  160. }, [item])
  161. // 拖动事件配置
  162. const moveConfig = useCallback((item) => {
  163. return {
  164. draggable: true,
  165. onDragStart: (ev: any) => {
  166. let img = document.createElement('img')
  167. img.src = fileImg;
  168. ev.dataTransfer.setDragImage(img, 0, 0)
  169. setMoveId(item.id)
  170. },
  171. onDragEnd: (e: any) => {
  172. if (folderId !== '' && moveId !== '') {
  173. edit_media_folder.run({ sysMediaId: moveId, folderId, mediaType }).then(res => {
  174. message.success('操作成功')
  175. if (mediaType === 'IMG') {
  176. listImg.refresh()
  177. } else if (mediaType === 'VIDEO') {
  178. listVideo.refresh()
  179. }
  180. })
  181. }
  182. setActionId('')
  183. setHoverId('')
  184. setMoveId('')
  185. },
  186. }
  187. }, [folderId, moveId, mediaType])
  188. return <div style={{ display: 'flex', flexFlow: 'row' }}>
  189. {
  190. mediaType === 'IMG' ? get_folder_tree_img?.data?.length > 0 ? <div style={{ flexShrink: 0 }}>
  191. {treeEl}
  192. </div> : null : get_folder_tree_video?.data?.length > 0 ? <div style={{ flexShrink: 0 }}>
  193. {treeEl}
  194. </div> : null
  195. }
  196. <div style={{ flexShrink: 1 }}>
  197. <div className={style.files} onContextMenu={rightMenu} style={height ? { height } : {}}>
  198. {/* 关联公众号筛选 */}
  199. <div className={style.wxSelect}>
  200. {/* <WxSelect mode={1} isAll={isAll} /> */}
  201. </div>
  202. {/* 层级路径 */}
  203. <div className={style.path} >
  204. {//存在渲染个人本地路径不存在执行公共本地
  205. belongUser == '1' && path?.map((item: { title: string }, index: number) => {
  206. return <span key={index} onClick={() => { pathClick({ ...item, index }) }} >
  207. <span className={path?.length !== index + 1 ? style.path_color : ''}>{item?.title}</span>
  208. {path?.length !== index + 1 && <span className={style.rt} >{'>'}</span>}
  209. </span>
  210. })
  211. }
  212. {
  213. belongUser == '0' && publicPath?.map((item: { title: string }, index: number) => {
  214. return <span key={index} onClick={() => { pathClick({ ...item, index }) }} >
  215. <span className={publicPath?.length !== index + 1 ? style.path_color : ''}>{item?.title}</span>
  216. {publicPath?.length !== index + 1 && <span className={style.rt} >{'>'}</span>}
  217. </span>
  218. })
  219. }
  220. </div>
  221. {/* 内容 */}
  222. <div className={style.file_content} >
  223. {
  224. listData?.records?.map((item: Item) => {
  225. if (item.folder) {
  226. {/* 文件夹模板 */ }
  227. return <Popconfirm
  228. title="确定要删除吗?"
  229. onConfirm={() => { dels(item) }}
  230. okText="是"
  231. cancelText="否"
  232. onCancel={delPupOff}
  233. visible={delPupId === item.id}
  234. key={item.id}
  235. >
  236. <Spin tip='正在请求素材详情,请耐心等待...' spinning={get?.loading}>
  237. <div
  238. className={`${style.flex_box} ${selectFile?.some((id: number) => id === item.id) ? style.action : ''}`}
  239. onContextMenu={(e) => { rightMenu(e, item) }}
  240. onClick={(e) => { changeClickFile(e, item, isAll, noFile) }}
  241. onDragStart={(e: any) => {
  242. e.preventDefault()
  243. }}
  244. >
  245. <span className={`${style.select}`} onClick={(e) => { changeClickFile(e, item, isAll, noFile) }}>
  246. <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>
  247. </span>
  248. <img src={fileImg}
  249. className={style.flex_box_img}
  250. onClick={(e: any) => { e.stopPropagation(); fileClick(item) }}
  251. onDragOver={(ev) => {
  252. ev.preventDefault()
  253. }}
  254. onDrop={() => {
  255. if (item.id !== folderId) {
  256. setActionId(item.id)
  257. }
  258. }}
  259. />
  260. <span className={style.flex_box_name} >{item?.title}</span>
  261. <span className={style.flex_box_id} onClick={(e) => { copyId(e, item?.number) }} >{item?.number}</span>
  262. </div>
  263. </Spin>
  264. </Popconfirm>
  265. } else {
  266. {/* 图片模板 ,视频模板,音频模板*/ }
  267. let El = item.mediaType === 'IMG' ?
  268. <Image src={item.url} onClick={(e) => { e.stopPropagation() }}
  269. /> :
  270. item.mediaType === 'VIDEO' ?
  271. <video src={item.url} style={{ width: 130, height: 100 }} controls />
  272. :
  273. <audio src={item.url} controls style={{ width: 150 }} />
  274. return <Popconfirm
  275. title="确定要删除吗?"
  276. onConfirm={() => { dels(item.id) }}
  277. okText="是"
  278. cancelText="否"
  279. onCancel={delPupOff}
  280. visible={delPupId === item.id}
  281. key={item.id}
  282. >
  283. <Spin tip='正在请求素材详情,请耐心等待...' spinning={get?.loading}>
  284. <div
  285. className={`${style.image_box} ${selectFile?.some((id: number) => id === item.id) ? style.action : ''}`}
  286. onContextMenu={(e) => { rightMenu(e, item) }}
  287. onClick={(e) => { changeClickFile(e, item, isAll) }}
  288. {...moveConfig(item)}
  289. >
  290. <span className={`${style.select}`} onClick={(e) => { changeClickFile(e, item, isAll) }}>
  291. <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>
  292. </span>
  293. {El}
  294. <span className={style.flex_box_name} onClick={(e) => { copyId(e, item?.url) }}>{item?.videoTitle || item?.title}</span>
  295. <span onClick={(e) => { copyId(e, item?.number) }} >{item?.width}*{item.height}</span>
  296. </div>
  297. </Spin>
  298. </Popconfirm>
  299. }
  300. })
  301. }
  302. </div>
  303. {/* 鼠标右键菜单 */}
  304. {rightClickPup.id ? rightClickPup.id === 'all' ? <Menu /> : <Menu isItem /> : null}
  305. {/* 新建文件弹窗 */}
  306. {fileVisible && <FileModal isAll={isAll} />}
  307. {/* 编辑排序 */}
  308. {sortVisible && <SortModal isAll={isAll} />}
  309. {/* 新建非图文 */}
  310. {imgVisrible && <ImgModal isAll={isAll} />}
  311. </div>
  312. {
  313. listData?.records?.length > 0 && <div className={style.pagination}>
  314. {/* 分页 */}
  315. <Pagination
  316. showSizeChanger
  317. onChange={(page: number, pageSize?: number) => {
  318. getList({ pageSize, pageNum: page })
  319. }}
  320. onShowSizeChange={(current: number, size: number) => {
  321. getList({ pageSize: size, pageNum: current })
  322. }}
  323. current={listData?.current}
  324. defaultPageSize={20}
  325. total={listData?.total}
  326. />
  327. </div>
  328. }
  329. </div>
  330. </div>
  331. }
  332. export default React.memo(FlieBox)