index.tsx 10 KB

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