index.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import SvgIcon from '@/components/SvgIcon'
  2. import { iFile, iOSSData } from '@/types/file'
  3. import { apiGetFileList, apiSaveFileInfo, getSignature } from '@/utils/common/api'
  4. import consts from '@/utils/consts'
  5. import { dayjsFormat } from '@/utils/util'
  6. import { InboxOutlined } from '@ant-design/icons'
  7. import { message, Modal, Table, Upload } from 'antd'
  8. import { ColumnsType } from 'antd/lib/table'
  9. import { UploadChangeParam } from 'antd/lib/upload'
  10. import { UploadFile } from 'antd/lib/upload/interface'
  11. import React, { useEffect, useState } from 'react'
  12. import { ZhCloseButton } from '../Button'
  13. const { Dragger } = Upload
  14. interface iFileModalProps {
  15. dataId: string
  16. visible: boolean
  17. dataType: number
  18. onCancel: () => void
  19. showUpload?: boolean
  20. uploadCallBack?: () => void
  21. }
  22. interface iFileModalState {
  23. id: string
  24. filename: string
  25. filepath: string
  26. accountName: string
  27. createTime: string
  28. }
  29. interface FileTable {
  30. data: iFileModalState[]
  31. total: number
  32. }
  33. export default function FileModal(props: iFileModalProps) {
  34. const { dataId = "", visible = false, onCancel, dataType = 1, showUpload = false, uploadCallBack } = props
  35. const [ files, setFiles ] = useState<FileTable>({
  36. data: [],
  37. total: 0
  38. })
  39. const [ fileList, setFileList ] = useState<UploadFile[]>([])
  40. const [ OSSData, setOssData ] = useState<iOSSData>({
  41. dir: '',
  42. expire: '',
  43. host: '',
  44. accessId: '',
  45. policy: '',
  46. signature: ''
  47. })
  48. const [ uidArr, setUidArr ] = useState<string[]>([])
  49. useEffect(() => {
  50. if (props.visible) {
  51. initOssData()
  52. initData()
  53. }
  54. }, [ visible ])
  55. const initData = async (pageNo: number = 1, pageSize: number = consts.PAGE_SIZE) => {
  56. const { code = -1, data = [], total = 0 } = await apiGetFileList(dataType, dataId, pageNo, pageSize)
  57. if (code === consts.RET_CODE.SUCCESS) {
  58. setFiles({ ...files, data, total })
  59. setFileList([])
  60. setUidArr([])
  61. }
  62. }
  63. const saveFiles = async (newFileList: iFile[]) => {
  64. const { code = -1 } = await apiSaveFileInfo(newFileList, dataType, dataId)
  65. if (code === consts.RET_CODE.SUCCESS) {
  66. initData()
  67. uploadCallBack && uploadCallBack()
  68. }
  69. }
  70. useEffect(() => {
  71. if (uidArr.length && uidArr.length === fileList.length) {
  72. saveFiles(fileList.filter(file => file.status === 'done').map(file => ({
  73. createTime: new Date(),
  74. filepath: file.url,
  75. filename: file.name
  76. })))
  77. }
  78. }, [ uidArr ])
  79. // 上传文件改变时的状态
  80. const onChange = async (info: UploadChangeParam) => {
  81. const { status } = info.file
  82. // if (status !== 'uploading') {
  83. // }
  84. if (status === 'done') {
  85. setUidArr([ ...uidArr, info.file.uid ])
  86. } else if (status === 'error') {
  87. // message.error(`${info.file.name} 上传失败`)
  88. }
  89. setFileList(info.fileList)
  90. }
  91. // 初始化、获取签名
  92. const initOssData = async () => {
  93. try {
  94. const { code = -1, data = {} } = await getSignature()
  95. if (code === consts.RET_CODE.SUCCESS) {
  96. setOssData({ ...OSSData, ...data })
  97. } else {
  98. message.error("获取签名失败,请联系管理员")
  99. }
  100. } catch (error) {
  101. message.error(error)
  102. }
  103. }
  104. // 额外参数、oss签名
  105. const getExtraData = (file: UploadFile) => {
  106. return {
  107. key: file.url,
  108. OSSAccessKeyId: OSSData.accessId,
  109. policy: OSSData.policy,
  110. signature: OSSData.signature
  111. }
  112. }
  113. // 上传前的回调
  114. const beforeUpload = async (file: any) => {
  115. const { UPLOAD_LIMIT } = consts
  116. const isLt30M = file.size / 1024 / 1024 < UPLOAD_LIMIT
  117. if (!isLt30M) {
  118. file.status = 'error'
  119. message.error("上传附件大小限制在30MB")
  120. return false
  121. }
  122. const expire = parseInt(OSSData.expire) * 1000
  123. if (expire < Date.now()) {
  124. try {
  125. await initOssData()
  126. } catch (error) {
  127. file.status = 'error'
  128. return false
  129. }
  130. }
  131. const reg = new RegExp(consts.UPLOAD_WHITE)
  132. if (!reg.test(file.name)) {
  133. file.status = 'error'
  134. message.error('不支持该类型文件')
  135. return false
  136. }
  137. file.url = OSSData.dir + file.name
  138. return Promise.resolve(file)
  139. }
  140. // 移除文件
  141. const onRemove = (file: any) => {
  142. console.log(file)
  143. }
  144. const columns: ColumnsType<iFileModalState> = [
  145. {
  146. title: "文件名",
  147. dataIndex: "filepath",
  148. width: 287,
  149. onCell: () => {
  150. return {
  151. style: {
  152. maxWidth: 287,
  153. overflow: 'hidden',
  154. whiteSpace: 'nowrap',
  155. textOverflow: 'ellipsis',
  156. cursor: 'pointer'
  157. }
  158. }
  159. },
  160. // eslint-disable-next-line react/display-name
  161. render: (path: string, record: iFileModalState) => <a href={consts.OSS_PATH.REVIEW + path} target="_blank" rel="noopener noreferrer">{record.filename}</a>
  162. },
  163. {
  164. title: "上传人",
  165. dataIndex: "accountName"
  166. },
  167. {
  168. title: "上传时间",
  169. dataIndex: "createTime",
  170. // eslint-disable-next-line react/display-name
  171. render: text => <span>{dayjsFormat(text, "YYYY-MM-DD")}</span>
  172. },
  173. {
  174. title: "操作",
  175. align: "center",
  176. // eslint-disable-next-line react/display-name
  177. render: (_, record) => <a href={consts.OSS_PATH.DOWNLOAD + record.filepath} download><SvgIcon type="xxh-download" /></a>
  178. }
  179. ]
  180. return (
  181. <Modal
  182. title="附件"
  183. getContainer={false}
  184. visible={visible}
  185. onCancel={onCancel}
  186. footer={<ZhCloseButton onClick={() => onCancel()} size="small">关闭</ZhCloseButton>}
  187. >
  188. {
  189. showUpload ?
  190. <div className="pi-mg-bottom-10">
  191. <Dragger
  192. name="file"
  193. fileList={fileList}
  194. action={OSSData.host}
  195. multiple={true}
  196. onChange={onChange}
  197. maxCount={10}
  198. beforeUpload={beforeUpload}
  199. data={getExtraData}
  200. showUploadList={false}
  201. onRemove={onRemove}
  202. >
  203. <p className="ant-upload-drag-icon">
  204. <InboxOutlined />
  205. </p>
  206. <p className="ant-upload-text">把文件拖入指定区域,完成上传,同样支持点击上传。</p>
  207. <p className="ant-upload-hint">
  208. 支持单文件/多文件上传。附件大小最大为30MB!
  209. </p>
  210. </Dragger>
  211. </div>
  212. : ''
  213. }
  214. <Table
  215. dataSource={files.data}
  216. columns={columns}
  217. rowKey={record => record.id}
  218. bordered
  219. pagination={{
  220. hideOnSinglePage: true,
  221. size: "small",
  222. pageSize: consts.PAGE_SIZE,
  223. onChange: (page, pageSize) => initData(page, pageSize),
  224. total: files.total
  225. }}
  226. />
  227. </Modal>
  228. )
  229. }