uploadFile.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import { Button, Card, Form, Input, message, Modal, Radio, Select, Space, TreeSelect, Upload } from "antd"
  2. import React, { useEffect, useMemo, useState } from "react"
  3. import '../../tencentAdPutIn/index.less'
  4. import { useAjax } from "@/Hook/useAjax"
  5. import { getUserAllApi } from "@/services/operating/account"
  6. import { RcFile } from "antd/lib/upload"
  7. import { UploadOutlined } from "@ant-design/icons"
  8. import style from './index.less'
  9. import CropperImg from "@/components/FileBoxAD/components/Cropper"
  10. import { compressAccurately } from "image-conversion"
  11. import { blobToBase64, dataURLtoFile, videoMessage } from "@/utils/compress"
  12. import { getImgSize } from "@/utils/utils"
  13. import request from "umi-request"
  14. import { getFileUrl } from "@/services/launchAdq/material"
  15. import getMD5 from "@/components/MD5"
  16. import { addMaterialApi, getFolderListApi } from "@/services/adqV3/cloudNew"
  17. import { DataNode } from "antd/lib/tree"
  18. import { updateTreeData } from "./const"
  19. interface Props {
  20. userId: number,
  21. folderId?: number
  22. visible?: boolean
  23. onChange?: () => void
  24. onClose?: () => void
  25. }
  26. /**
  27. * 上传素材
  28. * @returns
  29. */
  30. const UploadFile: React.FC<Props> = ({ visible, onClose, folderId, userId, onChange }) => {
  31. /**********************************/
  32. const [queryForm, setQueryForm] = useState<CLOUDNEW.AddMaterialProps>({ materialType: 'image' })
  33. const [fileList, setFileList] = useState<any>([])
  34. const [fileUrl, setFileUrl] = useState<string>('')
  35. const [previewVisible, setPreviewVisible] = useState<boolean>(false)
  36. const [visibleCropper, setVisibleCropper] = useState<boolean>(false)
  37. const [loading, setLoading] = useState<boolean>(false)
  38. const [treeData, setTreeData] = useState<DataNode[]>([]);
  39. const getUserAll = useAjax(() => getUserAllApi())
  40. const addMaterial = useAjax((params) => addMaterialApi(params))
  41. const getFolderList = useAjax((params) => getFolderListApi(params))
  42. //请求上传地址
  43. const getFileUrlAjx = useAjax((params: { type: string, fileType: 'video' | 'image' }) => getFileUrl(params), { manual: true })
  44. /**********************************/
  45. useEffect(() => {
  46. getFolder()
  47. }, [])
  48. const getFolder = () => {
  49. getFolderList.run({}).then(res => {
  50. setTreeData(() => res?.map((item: { folderName: any; id: any; createBy: number }) => ({
  51. title: item.folderName,
  52. value: item.id,
  53. key: item.id,
  54. disabled: userId.toString() !== item.createBy.toString()
  55. })) || [])
  56. })
  57. }
  58. useEffect(() => {
  59. getUserAll.run()
  60. }, [])
  61. const handleOk = async () => {
  62. if (!folderId && !queryForm?.folderId) {
  63. message.error('当前不能上传素材,请选择文件夹')
  64. return
  65. }
  66. if (queryForm?.file) {
  67. let file = queryForm.file
  68. let fileSize = 0
  69. if (queryForm.materialType === 'image') {
  70. fileSize = 409600
  71. } else {
  72. fileSize = 524288000
  73. }
  74. if (queryForm.materialType === 'image') {
  75. if (file?.size > fileSize) { // 大于400kb进入压缩
  76. let bole = await compressAccurately(file, fileSize / 1024 - 50)
  77. if (bole?.size > fileSize) {
  78. bole = await compressAccurately(file, fileSize / 1024 - 100)
  79. }
  80. if (bole?.size > fileSize) {
  81. bole = await compressAccurately(file, fileSize / 1024 - 150)
  82. }
  83. if (bole?.size > fileSize) {
  84. bole = await compressAccurately(file, fileSize / 1024 - 200)
  85. }
  86. let newFile = await blobToBase64(bole)
  87. message.warning({
  88. content: `选择的图片大于${fileSize / 1024}KB,图片已压缩`,
  89. duration: 3
  90. })
  91. file = await dataURLtoFile(newFile, file?.name)
  92. }
  93. } else if (queryForm.materialType === 'video') {
  94. if (file?.size > fileSize) { // 大于100mb进入压缩
  95. message.error({
  96. content: `选择的视频大于${fileSize / 1024 / 1024}MB,请重新选择提交`,
  97. duration: 3
  98. })
  99. return
  100. }
  101. }
  102. setLoading(() => true)
  103. let width = 0
  104. let height = 0
  105. let videoDuration = 0
  106. if (queryForm.materialType === 'image') {
  107. let imgData = await getImgSize(file)
  108. width = imgData.width
  109. height = imgData.height
  110. } else if (queryForm.materialType === "video") {
  111. let videoInfo: { width: number, height: number, videoLength: number }[] = await videoMessage([file])
  112. width = videoInfo[0].width
  113. height = videoInfo[0].height
  114. videoDuration = videoInfo[0].videoLength
  115. }
  116. /**修改文件名以用户设置的文件title命名*/
  117. let newFile = new File([file], queryForm?.materialName ? queryForm?.materialName + '.' + file?.name?.split('.')[1] : file?.name, { type: file?.type })
  118. let formData = new FormData();
  119. /**向阿里云请求上传地址*/
  120. getFileUrlAjx.run({ type: newFile.type, fileType: queryForm.materialType }).then(res1 => {
  121. Object.keys(res1).forEach((key: string) => {
  122. if (key !== 'url') {
  123. formData.append(key, res1[key])
  124. }
  125. })
  126. formData.append('file', newFile)
  127. /**向阿里云返回的上传地址上传文件*/
  128. request(res1?.ossUrl, { method: 'post', body: formData }).then(async (res2: { code: number, data: { url: string } }) => {
  129. if (res2.code === 200) {
  130. /**取到返回的文件地址向后端发送具体数据*/
  131. if (res2?.data?.url) {
  132. let fileMd5 = await getMD5(newFile)
  133. let obj: CLOUDNEW.AddMaterialProps = { ...queryForm, width, height, md5: fileMd5, ossUrl: res2?.data?.url, fileSize: newFile?.size, fileMime: newFile.type, aspectRatio: width / height }
  134. delete obj?.file
  135. if (!obj?.folderId) obj.folderId = folderId
  136. if (!obj?.designerId) obj.designerId = userId;
  137. if (queryForm.materialType === 'video') obj.videoDuration = videoDuration;
  138. if (!obj?.materialName) obj.materialName = queryForm.file.name;
  139. if (obj?.materialName && obj.materialName.match(RegExp(/[<>&\\'"/\x08\x09\x0A\x0D\x7F]/g))) {
  140. obj.materialName = obj.materialName.replace(RegExp(/[<>&\\'"/\x08\x09\x0A\x0D\x7F]/g), '')
  141. }
  142. obj.materialName = (obj as any).materialName.replace(/\.(jpg|jpeg|gif|png|mp4)$/i, '')
  143. addMaterial.run(obj).then((res) => {
  144. setLoading(() => false)
  145. if (res) {
  146. message.success('添加成功')
  147. onChange?.()
  148. }
  149. }).catch(() => setLoading(() => false))
  150. }
  151. } else {
  152. message.error('上传失败!')
  153. }
  154. }).catch(() => setLoading(() => false))
  155. }).catch(() => setLoading(() => false))
  156. } else {
  157. message.error('请选择素材')
  158. }
  159. }
  160. const getVideo = useMemo(() => {
  161. if (queryForm?.materialType === 'video') {
  162. if (queryForm?.file) {
  163. return <video src={URL.createObjectURL(queryForm?.file)} style={{ width: 200 }} controls />
  164. } else {
  165. return null
  166. }
  167. } else {
  168. return null
  169. }
  170. }, [queryForm?.file])
  171. // 下级目录
  172. const handleUpdateFolder = (parentId: number) => {
  173. return getFolderListApi({ parentId }).then(res => {
  174. setTreeData(origin =>
  175. updateTreeData(origin, parentId, res?.data?.map((item: { folderName: any; id: any; createBy: number }) => ({
  176. title: item.folderName,
  177. value: item.id,
  178. key: item.id,
  179. disabled: userId.toString() !== item.createBy.toString()
  180. })) || []),
  181. );
  182. })
  183. }
  184. return <Modal
  185. title={<strong>{'上传素材'}</strong>}
  186. open={visible}
  187. onCancel={onClose}
  188. onOk={handleOk}
  189. className="modalResetCss"
  190. bodyStyle={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '10px' }}
  191. maskClosable={false}
  192. width={600}
  193. confirmLoading={loading}
  194. >
  195. <Card className="cardResetCss">
  196. <Form
  197. labelAlign='left'
  198. labelCol={{ span: 4 }}
  199. layout="horizontal"
  200. colon={false}
  201. style={{}}
  202. >
  203. <Form.Item label={<strong>素材名称</strong>}>
  204. <Input placeholder="素材名称(不填是上传素材名称)" value={queryForm?.materialName} onChange={(e) => setQueryForm({ ...queryForm, materialName: e.target.value })} />
  205. </Form.Item>
  206. <Form.Item label={<strong>设计师</strong>}>
  207. <Select
  208. placeholder="请选择设计师(不选默认自己)"
  209. filterOption={(input, option) =>
  210. ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
  211. }
  212. value={queryForm?.designerId}
  213. onChange={(e => setQueryForm({ ...queryForm, designerId: e }))}
  214. options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
  215. />
  216. </Form.Item>
  217. <Form.Item label={<strong>文件夹</strong>}>
  218. <TreeSelect
  219. loading={getFolderList.loading}
  220. allowClear
  221. style={{ width: '100%' }}
  222. value={queryForm?.folderId}
  223. dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
  224. placeholder="请选择文件夹(不选择默认当前文件夹)"
  225. onChange={(e) => {
  226. setQueryForm({ ...queryForm, folderId: e })
  227. }}
  228. loadData={({ value }) => {
  229. return new Promise<void>(async (resolve) => {
  230. await handleUpdateFolder(Number(value))
  231. resolve()
  232. })
  233. }}
  234. treeData={treeData}
  235. />
  236. </Form.Item>
  237. <Form.Item label={<strong>素材类型</strong>}>
  238. <Radio.Group value={queryForm?.materialType} onChange={(e) => setQueryForm({ ...queryForm, materialType: e.target.value })} buttonStyle="solid">
  239. <Radio.Button value="image">图片</Radio.Button>
  240. <Radio.Button value="video">视频</Radio.Button>
  241. </Radio.Group>
  242. </Form.Item>
  243. <Form.Item label={<strong>描述</strong>}>
  244. <Input.TextArea placeholder="描述" value={queryForm?.description} onChange={(e) => setQueryForm({ ...queryForm, description: e.target.value })} />
  245. </Form.Item>
  246. <Form.Item label={<strong>{queryForm?.materialType ? '上传图片' : '上传视频'}</strong>}>
  247. {queryForm?.materialType === 'image' ? <Space>
  248. <Upload
  249. listType="picture-card"
  250. accept='image/gif,image/jpeg,image/png,image/jpg'
  251. beforeUpload={(file: RcFile) => {
  252. return false
  253. }}
  254. fileList={fileList}
  255. onChange={(newFileList: any) => {
  256. console.log('newFileList-->', newFileList)
  257. setQueryForm({ ...queryForm, file: newFileList.file })
  258. setFileList([...newFileList.fileList])
  259. }}
  260. onPreview={(file: any) => {
  261. setPreviewVisible(true)
  262. setFileUrl(file.thumbUrl)
  263. }}
  264. onRemove={() => {
  265. setQueryForm({ ...queryForm, file: null })
  266. }}
  267. >
  268. {fileList?.length < 1 && '普通上传'}
  269. </Upload>
  270. {fileList?.length === 0 && <Upload
  271. listType="picture-card"
  272. accept='image/gif,image/jpeg,image/png,image/jpg'
  273. beforeUpload={(file: RcFile) => {
  274. return false
  275. }}
  276. fileList={fileList}
  277. onChange={(newFileList: any) => {
  278. setQueryForm({ ...queryForm, file: newFileList.file })
  279. setVisibleCropper(true)
  280. }}
  281. onRemove={() => {
  282. setQueryForm({ ...queryForm, file: null })
  283. }}
  284. onPreview={(file: any) => {
  285. setPreviewVisible(true)
  286. setFileUrl(file.thumbUrl)
  287. }}
  288. >
  289. {fileList?.length < 1 && '裁剪上传'}
  290. </Upload>}
  291. </Space> : <>
  292. <div className={style.file}>
  293. <Button type='primary'><UploadOutlined />上传视频</Button>
  294. <input type='file' onChange={(e) => {
  295. if (e?.target?.files) {
  296. setQueryForm({ ...queryForm, file: e?.target?.files[0] })
  297. }
  298. }} accept="video/*" />
  299. </div>
  300. {queryForm?.file && <>
  301. <p>{queryForm?.file?.name}</p>
  302. {getVideo}
  303. </>}
  304. </>}
  305. </Form.Item>
  306. </Form>
  307. </Card>
  308. {/* 预览 */}
  309. <Modal
  310. open={previewVisible}
  311. footer={null}
  312. onCancel={() => {
  313. setPreviewVisible(false)
  314. }}
  315. >
  316. <img alt="example" style={{ width: '100%' }} src={fileUrl} />
  317. </Modal>
  318. {/* 裁剪 */}
  319. {visibleCropper && <CropperImg visible={visible} onClose={() => setVisibleCropper(false)} file={queryForm.file} onChange={(fileList: any[], file: any) => { setFileList(fileList); setQueryForm({ ...queryForm, file: file }); setVisibleCropper(false) }} />}
  320. </Modal>
  321. }
  322. export default React.memo(UploadFile)