index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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('vnd.openxmlformats-officedocument.presentationml.presentation') || file.type.includes('vnd.ms-powerpoint')
  98. ? NaturalTypeEnum.PPT
  99. : 'other';
  100. console.log(type, 'type');
  101. if (type === 'other') {
  102. message.error(`文件格式不支持`);
  103. return false;
  104. }
  105. const size = type === 'IMG' ? 2 : type === 'SONG' ? 20 : 500;
  106. if (size) {
  107. isLt2M = file.file.size / 1024 / 1024 < size;
  108. if (!isLt2M) {
  109. const typeStr =
  110. type === 'IMG' ? '图片' : type === 'SONG' ? '音频' : '视频';
  111. message.error(`${typeStr}大小不能超过${size}M`);
  112. return false;
  113. }
  114. }
  115. if (!isLt2M) {
  116. return isLt2M;
  117. }
  118. // 是否裁切
  119. // if (props.cropper && type === 'IMG') {
  120. // getBase64(file.file, (imageUrl: any) => {
  121. // const target = Object.assign({}, props.options, {
  122. // img: imageUrl,
  123. // name: file.file.name // 上传文件名
  124. // });
  125. // visiable.value = true;
  126. // setTimeout(() => {
  127. // CropperModal.value?.edit(target);
  128. // }, 100);
  129. // });
  130. // return false;
  131. // }
  132. try {
  133. btnLoading.value = true;
  134. const name = file.file.name;
  135. const suffix = name.slice(name.lastIndexOf('.'));
  136. const fileName = `${props.path}${Date.now() + file.id + suffix}`;
  137. const obj = {
  138. filename: fileName,
  139. bucketName: props.bucketName,
  140. postData: {
  141. filename: fileName,
  142. acl: 'public-read',
  143. key: fileName,
  144. unknowValueField: []
  145. }
  146. };
  147. // const { data } = await policy(obj);
  148. const { data } = await getUploadSign(obj);
  149. state.push({
  150. id: file.id,
  151. tempFiileBuffer: file.file,
  152. policy: data.policy,
  153. signature: data.signature,
  154. acl: 'public-read',
  155. key: fileName,
  156. KSSAccessKeyId: data.kssAccessKeyId,
  157. name: fileName
  158. });
  159. } catch {
  160. //
  161. // message.error('上传失败')
  162. btnLoading.value = false;
  163. return false;
  164. }
  165. return true;
  166. };
  167. // const getBase64 = async (img: any, callback: any) => {
  168. // const reader = new FileReader();
  169. // reader.addEventListener('load', () => callback(reader.result));
  170. // reader.readAsDataURL(img);
  171. // };
  172. const onFinish = (options: any) => {
  173. onFinishAfter(options);
  174. };
  175. const onFinishAfter = async (options: any) => {
  176. console.log(options, 'onFinishAfter');
  177. const item = state.find((c: any) => c.id == options.file.id);
  178. const type = formatUrlType(options.file.url);
  179. let coverImg = '';
  180. if (type === 'IMG') {
  181. coverImg = options.file.url;
  182. } else if (type === 'SONG') {
  183. coverImg = PageEnum.SONG_DEFAULT_COVER;
  184. } else if (type === 'PPT') {
  185. coverImg = PageEnum.PPT_DEFAULT_COVER;
  186. } else if (type === 'VIDEO') {
  187. // 获取视频封面图
  188. coverImg = await getVideoCoverImg(item.tempFiileBuffer);
  189. }
  190. uploadList.value.push({
  191. coverImg,
  192. content: options.file.url,
  193. id: options.file.id,
  194. name: options.file.name
  195. ? options.file.name.slice(0, options.file.name.lastIndexOf('.'))
  196. : ''
  197. });
  198. visiable.value = false;
  199. btnLoading.value = false;
  200. };
  201. const getVideoMsg = (file: any) => {
  202. return new Promise(resolve => {
  203. // let dataURL = '';
  204. const videoElement = document.createElement('video');
  205. videoElement.currentTime = 1;
  206. videoElement.src = URL.createObjectURL(file);
  207. videoElement.addEventListener('loadeddata', function () {
  208. const canvas: any = document.createElement('canvas'),
  209. width = videoElement.videoWidth, //canvas的尺寸和图片一样
  210. height = videoElement.videoHeight;
  211. canvas.width = width;
  212. canvas.height = height;
  213. canvas.getContext('2d').drawImage(videoElement, 0, 0, width, height); //绘制canvas
  214. // dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
  215. // console.log(canvas);
  216. canvas.toBlob((blob: any) => {
  217. // console.log(blob);
  218. resolve(blob);
  219. });
  220. });
  221. });
  222. };
  223. const getVideoCoverImg = async (file: any) => {
  224. try {
  225. btnLoading.value = true;
  226. const imgBlob: any = await getVideoMsg(file || tempFiileBuffer.value);
  227. const fileName = `${props.path}${Date.now() + '.png'}`;
  228. const obj = {
  229. filename: fileName,
  230. bucketName: props.bucketName,
  231. postData: {
  232. filename: fileName,
  233. acl: 'public-read',
  234. key: fileName,
  235. unknowValueField: []
  236. }
  237. };
  238. const { data } = await getUploadSign(obj);
  239. const fileParams = {
  240. policy: data.policy,
  241. signature: data.signature,
  242. key: fileName,
  243. acl: 'public-read',
  244. KSSAccessKeyId: data.kssAccessKeyId,
  245. name: fileName,
  246. file: imgBlob
  247. } as any;
  248. const res = await onOnlyFileUpload(ossUploadUrl, fileParams);
  249. return res;
  250. } finally {
  251. btnLoading.value = false;
  252. }
  253. };
  254. const onRemove = async (file: any) => {
  255. const index = uploadList.value.findIndex(
  256. (update: any) => update.id === file.file.id
  257. );
  258. uploadList.value.splice(index, 1);
  259. btnLoading.value = false;
  260. return true;
  261. };
  262. const uploadStatus = computed(() => {
  263. let status = false;
  264. fileListRef.value.forEach((file: any) => {
  265. if (file.status !== 'finished') {
  266. status = true;
  267. }
  268. });
  269. return status || fileListRef.value.length <= 0;
  270. });
  271. const onCustomRequest = ({
  272. file,
  273. // data,
  274. // headers,
  275. // withCredentials,
  276. action,
  277. onFinish,
  278. onError,
  279. onProgress
  280. }: UploadCustomRequestOptions) => {
  281. const item = state.find((c: any) => {
  282. return c.id == file.id;
  283. });
  284. item.file = file;
  285. onFileUpload({ file, action, data: item, onProgress, onFinish, onError });
  286. };
  287. const onSubmit = async () => {
  288. const list: any = [];
  289. fileListRef.value.forEach((file: any) => {
  290. const item = uploadList.value.find(
  291. (child: any) => child.id === file.id
  292. );
  293. if (item) {
  294. list.push(item);
  295. }
  296. });
  297. emit('confrim', list);
  298. };
  299. return () => (
  300. <div class={styles.saveModal}>
  301. <NUpload
  302. ref={uploadRef}
  303. action={ossUploadUrl}
  304. // data={(file: any) => {
  305. // return { ...more };
  306. // }}
  307. customRequest={onCustomRequest}
  308. v-model:fileList={fileListRef.value}
  309. accept=".jpg,jpeg,.png,audio/mp3,video/mp4,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation"
  310. multiple={true}
  311. max={10}
  312. // disabled={props.disabled}
  313. showFileList={true}
  314. showPreviewButton
  315. onBeforeUpload={(options: any) => onBeforeUpload(options)}
  316. onFinish={(options: any) => {
  317. onFinish(options);
  318. }}
  319. onRemove={(options: any) => onRemove(options)}>
  320. <NUploadDragger>
  321. <div class={styles.uploadBtn}>
  322. <div class={styles.iconUploadAdd} />
  323. <h3>点击或者拖动文件到该区域来上传</h3>
  324. <p>
  325. 仅支持JPG、PNG、MP3、MP4、PPT格式文件,单次最多支持
  326. <br />
  327. 上传10个文件
  328. </p>
  329. </div>
  330. </NUploadDragger>
  331. </NUpload>
  332. <NSpace class={styles.btnGroup} justify="center">
  333. <NButton round onClick={() => emit('close')}>
  334. 取消
  335. </NButton>
  336. <NButton
  337. round
  338. type="primary"
  339. disabled={uploadStatus.value}
  340. onClick={onSubmit}>
  341. 确定
  342. </NButton>
  343. </NSpace>
  344. </div>
  345. );
  346. }
  347. });