useBdMedia.ts 23 KB

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