index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import { PropType, computed, defineComponent, reactive, ref } from 'vue';
  2. import styles from './index.module.less';
  3. import {
  4. NButton,
  5. NSpace,
  6. NUpload,
  7. NUploadDragger,
  8. UploadCustomRequestOptions,
  9. UploadFileInfo,
  10. useMessage
  11. } from 'naive-ui';
  12. import iconUploadAdd from '../../../images/icon-upload-add.png';
  13. import { NaturalTypeEnum, PageEnum } from '/src/enums/pageEnum';
  14. import { policy } from '/src/components/upload-file/api';
  15. import { formatUrlType } from '../upload-modal';
  16. import axios from 'axios';
  17. import {
  18. getUploadSign,
  19. onFileUpload,
  20. onOnlyFileUpload,
  21. ossSwitch
  22. } from '/src/helpers/oss-file-upload';
  23. export default defineComponent({
  24. name: 'save-modal',
  25. props: {
  26. fileList: {
  27. type: String,
  28. default: ''
  29. },
  30. imageList: {
  31. type: Array,
  32. default: () => []
  33. },
  34. accept: {
  35. // 支持类型
  36. type: String,
  37. default: '.jpg,.png,.jpeg,.gif'
  38. },
  39. showType: {
  40. type: String as PropType<'default' | 'custom'>,
  41. default: 'default'
  42. },
  43. showFileList: {
  44. type: Boolean,
  45. default: true
  46. },
  47. max: {
  48. type: Number as PropType<number>,
  49. default: 1
  50. },
  51. multiple: {
  52. type: Boolean as PropType<boolean>,
  53. default: false
  54. },
  55. disabled: {
  56. type: Boolean as PropType<boolean>,
  57. default: false
  58. },
  59. bucketName: {
  60. type: String,
  61. default: 'gyt'
  62. },
  63. directoryDnd: {
  64. type: Boolean as PropType<boolean>,
  65. default: false
  66. },
  67. path: {
  68. type: String,
  69. default: ''
  70. },
  71. fileName: {
  72. type: String,
  73. default: ''
  74. }
  75. },
  76. emits: ['close', 'confrim'],
  77. setup(props, { emit }) {
  78. const ossUploadUrl = `https://${props.bucketName}.ks3-cn-beijing.ksyuncs.com/`;
  79. const message = useMessage();
  80. const visiable = ref<boolean>(false);
  81. const btnLoading = ref<boolean>(false);
  82. const tempFiileBuffer = ref();
  83. const uploadRef = ref();
  84. const state = reactive([]) as any;
  85. const fileListRef = ref<UploadFileInfo[]>([]);
  86. const uploadList = ref([] as any);
  87. const onBeforeUpload = async (options: any) => {
  88. const file = options.file;
  89. // 文件大小
  90. let isLt2M = true;
  91. const type = file.type.includes('image')
  92. ? NaturalTypeEnum.IMG
  93. : file.type.includes('audio')
  94. ? NaturalTypeEnum.SONG
  95. : file.type.includes('video')
  96. ? NaturalTypeEnum.VIDEO
  97. : file.type.includes(
  98. 'vnd.openxmlformats-officedocument.presentationml.presentation'
  99. ) || file.type.includes('vnd.ms-powerpoint')
  100. ? NaturalTypeEnum.PPT
  101. : 'other';
  102. console.log(type, 'type');
  103. if (type === 'other') {
  104. message.error(`文件格式不支持`);
  105. return false;
  106. }
  107. const size = type === 'IMG' ? 2 : type === 'SONG' ? 20 : 500;
  108. if (size) {
  109. isLt2M = file.file.size / 1024 / 1024 < size;
  110. if (!isLt2M) {
  111. const typeStr =
  112. type === 'IMG' ? '图片' : type === 'SONG' ? '音频' : '视频';
  113. message.error(`${typeStr}大小不能超过${size}M`);
  114. return false;
  115. }
  116. }
  117. if (!isLt2M) {
  118. return isLt2M;
  119. }
  120. // 是否裁切
  121. // if (props.cropper && type === 'IMG') {
  122. // getBase64(file.file, (imageUrl: any) => {
  123. // const target = Object.assign({}, props.options, {
  124. // img: imageUrl,
  125. // name: file.file.name // 上传文件名
  126. // });
  127. // visiable.value = true;
  128. // setTimeout(() => {
  129. // CropperModal.value?.edit(target);
  130. // }, 100);
  131. // });
  132. // return false;
  133. // }
  134. try {
  135. btnLoading.value = true;
  136. const name = file.file.name;
  137. const suffix = name.slice(name.lastIndexOf('.'));
  138. const fileName = `${props.path}${Date.now() + file.id + suffix}`;
  139. const obj = {
  140. filename: fileName,
  141. bucketName: props.bucketName,
  142. postData: {
  143. filename: fileName,
  144. acl: 'public-read',
  145. key: fileName,
  146. unknowValueField: []
  147. }
  148. };
  149. // const { data } = await policy(obj);
  150. const { data } = await getUploadSign(obj);
  151. state.push({
  152. id: file.id,
  153. tempFiileBuffer: file.file,
  154. policy: data.policy,
  155. signature: data.signature,
  156. acl: 'public-read',
  157. key: fileName,
  158. KSSAccessKeyId: data.kssAccessKeyId,
  159. name: fileName
  160. });
  161. } catch {
  162. //
  163. // message.error('上传失败')
  164. btnLoading.value = false;
  165. return false;
  166. }
  167. return true;
  168. };
  169. // const getBase64 = async (img: any, callback: any) => {
  170. // const reader = new FileReader();
  171. // reader.addEventListener('load', () => callback(reader.result));
  172. // reader.readAsDataURL(img);
  173. // };
  174. const onFinish = (options: any) => {
  175. onFinishAfter(options);
  176. };
  177. const onFinishAfter = async (options: any) => {
  178. console.log(options, 'onFinishAfter');
  179. const item = state.find((c: any) => c.id == options.file.id);
  180. const type = formatUrlType(options.file.url);
  181. let coverImg = '';
  182. if (type === 'IMG') {
  183. coverImg = options.file.url;
  184. } else if (type === 'SONG') {
  185. coverImg = PageEnum.SONG_DEFAULT_COVER;
  186. } else if (type === 'PPT') {
  187. coverImg = PageEnum.PPT_DEFAULT_COVER;
  188. } else if (type === 'VIDEO') {
  189. // 获取视频封面图
  190. coverImg = await getVideoCoverImg(item.tempFiileBuffer);
  191. }
  192. uploadList.value.push({
  193. coverImg,
  194. content: options.file.url,
  195. id: options.file.id,
  196. name: options.file.name
  197. ? options.file.name.slice(0, options.file.name.lastIndexOf('.'))
  198. : ''
  199. });
  200. visiable.value = false;
  201. btnLoading.value = false;
  202. };
  203. const getVideoMsg = (file: any) => {
  204. return new Promise(resolve => {
  205. // let dataURL = '';
  206. const videoElement = document.createElement('video');
  207. videoElement.currentTime = 1;
  208. videoElement.src = URL.createObjectURL(file);
  209. videoElement.addEventListener('loadeddata', function () {
  210. const canvas: any = document.createElement('canvas'),
  211. width = videoElement.videoWidth, //canvas的尺寸和图片一样
  212. height = videoElement.videoHeight;
  213. canvas.width = width;
  214. canvas.height = height;
  215. canvas.getContext('2d').drawImage(videoElement, 0, 0, width, height); //绘制canvas
  216. // dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
  217. // console.log(canvas);
  218. canvas.toBlob((blob: any) => {
  219. // console.log(blob);
  220. resolve(blob);
  221. });
  222. });
  223. });
  224. };
  225. const getVideoCoverImg = async (file: any) => {
  226. try {
  227. btnLoading.value = true;
  228. const imgBlob: any = await getVideoMsg(file || tempFiileBuffer.value);
  229. const fileName = `${props.path}${Date.now() + '.png'}`;
  230. const obj = {
  231. filename: fileName,
  232. bucketName: props.bucketName,
  233. postData: {
  234. filename: fileName,
  235. acl: 'public-read',
  236. key: fileName,
  237. unknowValueField: []
  238. }
  239. };
  240. const { data } = await getUploadSign(obj);
  241. const fileParams = {
  242. policy: data.policy,
  243. signature: data.signature,
  244. key: fileName,
  245. acl: 'public-read',
  246. KSSAccessKeyId: data.kssAccessKeyId,
  247. name: fileName,
  248. file: imgBlob
  249. } as any;
  250. const res = await onOnlyFileUpload(ossUploadUrl, fileParams);
  251. return res;
  252. } finally {
  253. btnLoading.value = false;
  254. }
  255. };
  256. const onRemove = async (file: any) => {
  257. const index = uploadList.value.findIndex(
  258. (update: any) => update.id === file.file.id
  259. );
  260. uploadList.value.splice(index, 1);
  261. btnLoading.value = false;
  262. return true;
  263. };
  264. const uploadStatus = computed(() => {
  265. let status = false;
  266. fileListRef.value.forEach((file: any) => {
  267. if (file.status !== 'finished') {
  268. status = true;
  269. }
  270. });
  271. return status || fileListRef.value.length <= 0 || btnLoading.value;
  272. });
  273. const onCustomRequest = ({
  274. file,
  275. // data,
  276. // headers,
  277. // withCredentials,
  278. action,
  279. onFinish,
  280. onError,
  281. onProgress
  282. }: UploadCustomRequestOptions) => {
  283. const item = state.find((c: any) => {
  284. return c.id == file.id;
  285. });
  286. item.file = file;
  287. onFileUpload({ file, action, data: item, onProgress, onFinish, onError });
  288. };
  289. const onSubmit = async () => {
  290. const list: any = [];
  291. fileListRef.value.forEach((file: any) => {
  292. const item = uploadList.value.find(
  293. (child: any) => child.id === file.id
  294. );
  295. if (item) {
  296. list.push(item);
  297. }
  298. });
  299. emit('confrim', list);
  300. };
  301. return () => (
  302. <div class={styles.saveModal}>
  303. <NUpload
  304. ref={uploadRef}
  305. action={ossUploadUrl}
  306. // data={(file: any) => {
  307. // return { ...more };
  308. // }}
  309. customRequest={onCustomRequest}
  310. v-model:fileList={fileListRef.value}
  311. accept=".jpg,jpeg,.png,audio/mp3,video/mp4,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation"
  312. multiple={true}
  313. max={10}
  314. // disabled={props.disabled}
  315. showFileList={true}
  316. showPreviewButton
  317. onBeforeUpload={(options: any) => onBeforeUpload(options)}
  318. onFinish={(options: any) => {
  319. onFinish(options);
  320. }}
  321. onRemove={(options: any) => onRemove(options)}>
  322. <NUploadDragger>
  323. <div class={styles.uploadBtn}>
  324. <div class={styles.iconUploadAdd} />
  325. <h3>点击或者拖动文件到该区域来上传</h3>
  326. <p>
  327. 仅支持JPG、PNG、MP3、MP4、PPT格式文件,单次最多支持
  328. <br />
  329. 上传10个文件
  330. </p>
  331. </div>
  332. </NUploadDragger>
  333. </NUpload>
  334. <NSpace class={styles.btnGroup} justify="center">
  335. <NButton round onClick={() => emit('close')}>
  336. 取消
  337. </NButton>
  338. <NButton
  339. round
  340. type="primary"
  341. disabled={uploadStatus.value}
  342. onClick={onSubmit}>
  343. 确定
  344. </NButton>
  345. </NSpace>
  346. </div>
  347. );
  348. }
  349. });