useBdMedia.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. import getMD5 from '@/components/MD5';
  2. import { useAjax } from '@/Hook/useAjax';
  3. import { bdSysMediaListImg, bdSysMediaListVideo, bdSysMediaAdd, delMedia, bdSysMediaEdit, getFileUrl, getMedia, configSortApi, getFolderTreeImg, getFolderTreeVideo, editMediaFolder } from '@/services/launchAdq/material';
  4. import { blobToBase64, dataURLtoFile, videoMessage } from '@/utils/compress';
  5. import { getImgSize } from '@/utils/utils';
  6. import { message } from 'antd';
  7. import { compressAccurately } from 'image-conversion';
  8. import { Dispatch, useCallback, useReducer, useState } from 'react'
  9. import { request } from 'umi';
  10. type State = {
  11. fileVisible?: boolean,//文件夹弹窗
  12. imgVisrible?: boolean,//img,voice,video弹窗
  13. newsVisrible?: boolean,//news弹窗
  14. knewsVisrible?: boolean,//客服图文弹窗
  15. fileName?: string,//文件夹名称
  16. sort?: number, // 排序
  17. videoTitle?: string,//video文件名称
  18. videoDescription?: string,//视频描述
  19. belongUser?: any,//0公共本地|1个人本地
  20. mediaType?: 'VIDEO' | 'IMG',//类型
  21. parentId?: any,//上级目录ID,顶级使用null
  22. url?: string,//素材地址
  23. pathId?: string,//路径ID
  24. selectFile?: number[],//选中的文件列表
  25. rightClickPup?: any,// /**右键菜单开关 all为全部 id 为目标文件 空为不显示*/
  26. xy?: { x: number, y: number },// /**鼠标位置 菜单弹窗位置使用*/
  27. delPupId?: any,//按了单个删除存放的id为了让弹窗区分文件
  28. actionItem?: any,//当前选中的Itme
  29. path?: any[],//个人路径
  30. publicPath?: any[],//本地路径
  31. file?: File,//素材文件
  32. selectItem?: any,//单选素材时存放选中的素材
  33. knewsdefaultData?: any,//k图文编辑时的默认内容
  34. sortVisible?: boolean,//排序弹窗
  35. size?: number, // 需要上传素材的大小
  36. upLoadLoading?: boolean, // 上传loading
  37. }
  38. export type Action = {
  39. type: 'set' | 'init',
  40. params?: any
  41. }
  42. const typeEnum = {
  43. 'IMG': '图片',
  44. 'VIDEO': '视频'
  45. }
  46. function reducer(state: State, action: Action) {
  47. let { type, params } = action
  48. let newState = JSON.parse(JSON.stringify(state))
  49. newState.file = state.file
  50. switch (type) {
  51. case 'set':
  52. Object.keys(params as State).forEach((key: string) => {
  53. newState[key] = (params as State)[key]
  54. })
  55. return newState
  56. case 'init':
  57. return { ...initData, ...params }
  58. default:
  59. return state;
  60. }
  61. }
  62. const initData: State = {
  63. fileVisible: false,
  64. knewsVisrible: false,
  65. fileName: '',
  66. videoTitle: '',
  67. videoDescription: '',
  68. belongUser: '1',
  69. mediaType: 'IMG',
  70. parentId: null,
  71. selectFile: [],
  72. delPupId: '',
  73. rightClickPup: { id: '' },
  74. publicPath: [{ title: '公共本地', number: '0' }],
  75. path: [{ title: '个人本地', number: '0' }],
  76. imgVisrible: false,
  77. newsVisrible: false,
  78. sortVisible: false,
  79. sort: 0,
  80. }
  81. /**本地素材管理器 */
  82. function useBdMediaPup() {
  83. const [state, dispatch]: [State, Dispatch<Action>] = useReducer(reducer, initData)
  84. const { fileName, sort, belongUser, mediaType, parentId, selectFile, delPupId, rightClickPup, actionItem, path, publicPath, size, videoTitle, videoDescription } = state
  85. const listImg = useAjax((params) => bdSysMediaListImg(params))
  86. const listVideo = useAjax((params) => bdSysMediaListVideo(params))
  87. const add = useAjax((params) => bdSysMediaAdd(params), { msgNmae: '新增' })
  88. const del = useAjax((params) => delMedia(params), { msgNmae: '删除' })
  89. const edit = useAjax((params) => bdSysMediaEdit(params), { msgNmae: '编辑' })
  90. const configSort = useAjax((params) => configSortApi(params), { msgNmae: '排序' })
  91. const get_folder_tree_img = useAjax((params: any) => getFolderTreeImg(params))
  92. const get_folder_tree_video = useAjax((params: any) => getFolderTreeVideo(params))
  93. const edit_media_folder = useAjax((params: any) => editMediaFolder(params))
  94. const get = useAjax((params) => getMedia(params))//获取本地素材详情
  95. const [isOk, setIsOk] = useState<boolean>(true)
  96. //请求上传地址
  97. const fileUrl = useAjax((params: { type: string, fileType: 'video' | 'image' }) => getFileUrl(params), {
  98. manual: true,
  99. })
  100. /**初始化数据 */
  101. const init = useCallback((params: any) => {
  102. console.log('init===>', params)
  103. dispatch({ type: 'init', params })
  104. }, [])
  105. /**设置state */
  106. const set = useCallback((params: State) => {
  107. // console.log('params===>',params)
  108. if (params) {
  109. dispatch({ type: 'set', params })
  110. }
  111. }, [])
  112. /**新增文件夹 */
  113. const addFolder = () => {
  114. if (fileName) {
  115. let obj = { title: fileName, mediaType, folder: true, parentId, belongUser: belongUser === '0' ? false : true, sort }
  116. add.run(obj).then((res) => {
  117. if (mediaType === 'IMG') {
  118. get_folder_tree_img.refresh()
  119. } else if (mediaType === 'VIDEO') {
  120. get_folder_tree_video.refresh()
  121. }
  122. if (mediaType === 'IMG') {
  123. listImg.refresh()
  124. } else if (mediaType === 'VIDEO') {
  125. listVideo.refresh()
  126. }
  127. offEditFile()//关闭弹窗并清空相关数据
  128. })
  129. }
  130. }
  131. /** 新增图片 视频 */
  132. const addFile = async (data: any) => {
  133. if (data) {//存在代表素材
  134. if (!data) {
  135. return
  136. }
  137. if (data?.file) {
  138. let file = data.file
  139. let fileSize = size || 0
  140. if (!size) {
  141. if (mediaType === 'IMG') {
  142. fileSize = 307200
  143. } else {
  144. fileSize = 104857600
  145. }
  146. }
  147. if (mediaType === 'IMG') {
  148. if (file?.size > fileSize) { // 大于300kb进入压缩
  149. let bole = await compressAccurately(file, 250)
  150. if (bole?.size > 300000) {
  151. bole = await compressAccurately(file, 200)
  152. }
  153. if (bole?.size > 300000) {
  154. bole = await compressAccurately(file, 150)
  155. }
  156. if (bole?.size > 300000) {
  157. bole = await compressAccurately(file, 100)
  158. }
  159. let newFile = await blobToBase64(bole)
  160. message.warning({
  161. content: `选择的图片大于${fileSize / 1024}KB,图片已压缩`,
  162. duration: 3
  163. })
  164. file = await dataURLtoFile(newFile, file?.name)
  165. }
  166. } else if (mediaType === 'VIDEO') {
  167. if (file?.size > fileSize) { // 大于100mb进入压缩
  168. message.error({
  169. content: `选择的视频大于${fileSize / 1024 / 1024}MB,请重新选择提交`,
  170. duration: 3
  171. })
  172. return
  173. }
  174. }
  175. set({ upLoadLoading: true })
  176. let width = 0
  177. let height = 0
  178. if (mediaType === 'IMG') {
  179. let imgData = await getImgSize(file)
  180. width = imgData.width
  181. height = imgData.height
  182. } else if (mediaType === "VIDEO") {
  183. let videoInfo: any = await videoMessage([file])
  184. width = videoInfo[0].width
  185. height = videoInfo[0].height
  186. }
  187. /**修改文件名以用户设置的文件title命名*/
  188. let newFile = new File([file], data?.title ? data?.title + '.' + file?.name?.split('.')[1] : file?.name, { type: file?.type })
  189. let formData = new FormData();
  190. /**向阿里云请求上传地址*/
  191. fileUrl.run({ type: newFile.type, fileType: mediaType === 'VIDEO' ? 'video' : 'image' }).then(res1 => {
  192. Object.keys(res1).forEach((key: string) => {
  193. if (key !== 'url') {
  194. formData.append(key, res1[key])
  195. }
  196. })
  197. formData.append('file', newFile)
  198. /**向阿里云返回的上传地址上传文件*/
  199. request(res1?.ossUrl, { method: 'post', body: formData }).then(async (res2: { code: number, data: { url: string } }) => {
  200. if (res2.code === 200) {
  201. message.success('上传成功')
  202. if (data) {
  203. /**取到返回的文件地址向后端发送具体数据*/
  204. if (res2?.data?.url) {
  205. let fileMd5 = await getMD5(newFile)
  206. let obj = { title: data.title, mediaType, folder: false, parentId, width, height, fileMd5, belongUser: belongUser === '0' ? false : true, url: res2?.data?.url, sort: data?.sort, fileSize: newFile?.size, fileMime: newFile.type }
  207. if (mediaType === 'VIDEO') {
  208. obj['videoTitle'] = data?.videoTitle || data?.title
  209. obj['videoDescription'] = data?.videoDescription
  210. }
  211. add.run(obj).then((res) => {
  212. console.log(res)
  213. if (mediaType === 'IMG') {
  214. listImg.refresh()
  215. } else if (mediaType === 'VIDEO') {
  216. listVideo.refresh()
  217. }
  218. offEditFile()//关闭弹窗并清空相关数据
  219. set({ upLoadLoading: false })
  220. }).catch(() => set({ upLoadLoading: false }))
  221. }
  222. }
  223. } else {
  224. message.error('上传失败!')
  225. }
  226. }).catch(() => set({ upLoadLoading: false }))
  227. }).catch(() => set({ upLoadLoading: false }))
  228. }
  229. }
  230. }
  231. /**编辑非图文素材名称*/
  232. const nameOk = useCallback((selectWx?: any) => {
  233. if (fileName && actionItem) {
  234. let obj = { title: fileName, belongUser: belongUser === '0' ? false : true, sysMediaId: actionItem?.id, mediaType: actionItem?.mediaType, folder: actionItem?.folder, url: actionItem?.url, sort }
  235. if (mediaType === 'VIDEO') {
  236. obj['videoTitle'] = videoTitle
  237. obj['videoDescription'] = videoDescription
  238. }
  239. edit.run(obj).then((res) => {
  240. if (mediaType === 'IMG') {
  241. listImg.refresh()
  242. } else if (mediaType === 'VIDEO') {
  243. listVideo.refresh()
  244. }
  245. offEditFile()//关闭弹窗并清空相关数据
  246. })
  247. }
  248. }, [fileName, actionItem, edit, belongUser, mediaType, videoTitle, videoDescription])
  249. /**删除文件 */
  250. const dels = useCallback((id?: any) => {
  251. let arr = typeof id === 'number' ? [id] : selectFile
  252. let len = arr?.length || 0
  253. if (len) {
  254. arr?.map((id, index) => {
  255. del.run({ sysMediaId: id, mediaType }).then(() => {
  256. set({ selectFile: selectFile?.filter(i => i !== id) })//清理已删除文件
  257. if (index === len - 1) {
  258. if (mediaType === 'IMG') {
  259. listImg.refresh()
  260. get_folder_tree_img.refresh()
  261. } else if (mediaType === 'VIDEO') {
  262. listVideo.refresh()
  263. get_folder_tree_video.refresh()
  264. }
  265. }
  266. })
  267. })
  268. }
  269. }, [listImg, listVideo, selectFile, mediaType])
  270. /**获取本地素材数据列表 */
  271. const getList = useCallback((props?: any) => {
  272. let obj = { pageSize: 20, pageNum: 1, belongUser: belongUser === '0' ? false : true, parentId, ...props }
  273. if (mediaType === 'IMG') {
  274. listImg.run(obj).then((res) => {
  275. setIsOk(true)
  276. })
  277. } else if (mediaType === 'VIDEO') {
  278. listVideo.run(obj).then((res) => {
  279. setIsOk(true)
  280. })
  281. }
  282. }, [mediaType, belongUser, parentId])
  283. /**选中文件 single 开启可单选为了在右键删除选择时只选一个*/
  284. const onFile = useCallback((e: any, item: { id: any, folder?: boolean }, isAll?: boolean, single?: boolean) => {
  285. let { id } = item
  286. e?.stopPropagation()
  287. if (isAll && !single) {
  288. set({ selectFile: [...new Set([...selectFile as number[], id])] })
  289. } else {
  290. if (item?.folder && !single) {//假如是文件不让选择
  291. message.error('不能选择文件夹')
  292. return
  293. }
  294. set({ selectFile: [item.id], selectItem: item })
  295. }
  296. }, [selectFile])
  297. /**点击文件夹 */
  298. const fileClick = useCallback((item) => {
  299. if (isOk) {
  300. setIsOk(false)
  301. console.log(item, belongUser == '1')
  302. if (belongUser == '1' && path) {
  303. set({ path: [...path, item] })
  304. }
  305. if (belongUser == '0' && publicPath) {
  306. set({ publicPath: [...publicPath, item] })
  307. }
  308. set({ parentId: item.id })
  309. getList({ parentId: item.id })//请求对应文件夹列表
  310. }
  311. }, [path, publicPath, mediaType, belongUser, isOk])
  312. /**点击目录树*/
  313. const treeClick = useCallback((item) => {
  314. if (isOk) {
  315. setIsOk(false)
  316. // console.log(item, belongUser == '1')
  317. if (belongUser == '1' && path) {
  318. set(item?.id === 0 ? { path: [path[0]] } : { path: [path[0], item] })
  319. }
  320. if (belongUser == '0' && publicPath) {
  321. set(item?.id === 0 ? { publicPath: [publicPath[0]] } : { publicPath: [publicPath[0], item] })
  322. }
  323. set({ parentId: item.id })
  324. getList({ parentId: item.id })//请求对应文件夹列表
  325. }
  326. }, [path, publicPath, mediaType, belongUser, isOk])
  327. /**点击路径 */
  328. const pathClick = useCallback((item) => {
  329. let newPath: any[] = []
  330. if (belongUser == '1') {
  331. path?.forEach((paths, index) => {//用传入的index和path循环的index对比小于等于index代表该保留的路径
  332. if (index <= item.index) {
  333. newPath.push(paths)
  334. }
  335. })
  336. set({ path: newPath })
  337. } else {
  338. publicPath?.forEach((paths, index) => {//用传入的index和path循环的index对比小于等于index代表该保留的路径
  339. if (index <= item.index) {
  340. newPath.push(paths)
  341. }
  342. })
  343. set({ publicPath: newPath })
  344. }
  345. set({ parentId: item.parentId })
  346. console.log(item)
  347. getList({ pageSize: 20, pageNum: 1, mediaType, belongUser: belongUser === '0' ? false : true, parentId: item.id })
  348. console.log('文件夹被点击')
  349. }, [path, mediaType, belongUser, publicPath])
  350. /**取消文件 */
  351. const offFile = useCallback((e: any, item: { id: any }) => {
  352. let { id } = item
  353. set({ selectFile: selectFile?.filter(i => i !== id) })
  354. }, [selectFile])
  355. /**判断是取消还是选中文件的操作在点击打钩时使用 */
  356. const changeClickFile = useCallback((e: any, item: { id: any, folder?: boolean }, isAll?: boolean) => {
  357. let { id } = item
  358. e?.stopPropagation()//阻止冒泡传递到文件夹被点击事件
  359. let state = selectFile?.some((i) => i === id)
  360. if (isAll) {
  361. if (state) {//存在就是删除
  362. set({ selectFile: selectFile?.filter(i => i !== id) })
  363. } else {//否则新增
  364. set({ selectFile: [...selectFile as number[], id] })
  365. }
  366. } else {//单选情况存在于选择素材弹窗组件
  367. if (item?.folder && mediaType !== 'IMG') {//假如是文件不让选择
  368. message.error('不能选择文件夹')
  369. return
  370. }
  371. if (state) {//存在就是删除
  372. set({ selectFile: selectFile?.filter(i => i !== id) })
  373. set({ selectItem: null })
  374. } else {//否则新增
  375. set({ selectFile: [id] })
  376. set({ selectItem: item })
  377. }
  378. }
  379. }, [selectFile, mediaType])
  380. /**开启删除弹窗 */
  381. const delPupOn = useCallback((delPupId) => {
  382. set({ delPupId })
  383. }, [])
  384. /**关闭删除弹窗并去除选中 */
  385. const delPupOff = useCallback(() => {
  386. set({ delPupId: '' })
  387. offFile(null, { id: delPupId })
  388. }, [delPupId,])
  389. /**编辑 */
  390. const editFile = useCallback((e?: any,) => {
  391. e?.stopPropagation()
  392. onFile(null, rightClickPup, true, true)
  393. // if (rightClickPup?.mediaType !== 'news') {//不是图文开启编辑名字弹窗
  394. let obj = { fileVisible: true, actionItem: rightClickPup, fileName: rightClickPup.title, sort: rightClickPup.sort }
  395. if (rightClickPup?.mediaType === 'video') {
  396. obj['videoTitle'] = rightClickPup?.videoTitle || rightClickPup?.title
  397. obj['videoDescription'] = rightClickPup?.videoDescription
  398. }
  399. set(obj)
  400. // }
  401. }, [rightClickPup])
  402. /**取消编辑后清空选中存放的数据并关闭弹窗*/
  403. const offEditFile = useCallback(() => {
  404. set({ fileVisible: false, imgVisrible: false, sortVisible: false, actionItem: '', fileName: '', selectFile: selectFile?.filter(id => id !== actionItem?.id), sort: 0 })
  405. }, [selectFile, actionItem])
  406. /**全选反选文件*/
  407. const allFile = useCallback(() => {
  408. let allArr: any[] = []
  409. if (mediaType === 'IMG') {
  410. listImg?.data?.records?.forEach((item: { id: any }) => {
  411. allArr.push(item.id)
  412. })
  413. } else if (mediaType === "VIDEO") {
  414. listVideo?.data?.records?.forEach((item: { id: any }) => {
  415. allArr.push(item.id)
  416. })
  417. }
  418. set({ selectFile: allArr.filter((i) => selectFile?.every(id => id !== i)) })
  419. }, [selectFile, listImg, listVideo, mediaType])
  420. return {
  421. state,
  422. init,
  423. set,
  424. addFile,
  425. addFolder,
  426. getList,
  427. dels,
  428. onFile,
  429. changeClickFile,
  430. allFile,
  431. delPupOn,
  432. delPupOff,
  433. editFile,
  434. offEditFile,
  435. nameOk,
  436. fileClick,
  437. treeClick,
  438. pathClick,
  439. configSort,
  440. fileUrl,
  441. listImg,
  442. listVideo,
  443. add,
  444. get,
  445. edit,
  446. typeEnum,
  447. get_folder_tree_img,
  448. get_folder_tree_video,
  449. edit_media_folder
  450. }
  451. }
  452. export default useBdMediaPup