upload.component.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
  2. import * as Parse from 'parse';
  3. import * as qiniu from 'qiniu-js';
  4. import {
  5. ionicStandaloneModules,
  6. LoadingController,
  7. ToastController,
  8. } from '../../../modules/ionic-standalone.modules';
  9. @Component({
  10. selector: 'app-upload',
  11. templateUrl: './upload.component.html',
  12. styleUrls: ['./upload.component.scss'],
  13. standalone: true,
  14. imports: [...ionicStandaloneModules],
  15. })
  16. export class UploadComponent implements OnInit {
  17. @Input('files') files: Array<any> = [];
  18. @Input('boxWidth') boxWidth: number = 390; //盒子宽度
  19. @Input('fileWidth') fileWidth: number = 86; //图片高度
  20. @Input('fileHeight') fileHeight: number = 86; //图片宽度
  21. @Input('maxlenght') maxlenght: number = 1; //文件数量限制
  22. @Input('type') type: string = 'image';
  23. @Input('size') size: number = 10240; //上传文件限制大小单位KB
  24. @Input('active') active: boolean = false; //选择文件后主动触发上传
  25. @Input('multiple') multiple: boolean = false; //是否允许选择多张
  26. @Output() onChange: EventEmitter<any> = new EventEmitter<any>();
  27. showBlockNum: number = 4; //每行显示数量
  28. get accept() {
  29. let type;
  30. switch (this.type) {
  31. case 'image':
  32. type = 'image/*';
  33. break;
  34. case 'pdf':
  35. type = 'application/pdf';
  36. break;
  37. case 'audio':
  38. type = 'audio/*';
  39. break;
  40. case 'video':
  41. type = 'video/*';
  42. break;
  43. default:
  44. type = 'file';
  45. break;
  46. }
  47. return type;
  48. }
  49. disabled: boolean = false;
  50. company: string = localStorage.getItem('company') || 'Qje9D4bqol';
  51. config: { uptoken: string; domain: string; bucket: string } = {
  52. uptoken: '',
  53. domain: '',
  54. bucket: '',
  55. };
  56. fileList: Array<{
  57. url: string;
  58. name: string;
  59. type: string;
  60. size?: number;
  61. file?: any;
  62. }> = [];
  63. Previewfilelist: any; //预览图片数组
  64. loading: any;
  65. currentPreviewImg: string = '';
  66. constructor(
  67. private toastController: ToastController,
  68. public loadCtrl: LoadingController
  69. ) {
  70. Parse.Cloud.run('qiniu_uptoken', { company: this.company }).then((data) => {
  71. this.config = {
  72. uptoken: data.uptoken,
  73. domain: data.domain,
  74. bucket: data.bucket,
  75. };
  76. });
  77. }
  78. ngOnInit() {
  79. /* 计算文件布局 */
  80. let n = Math.floor(this.boxWidth / this.fileWidth);
  81. if (n * this.fileWidth + (n - 1) * 10 > this.boxWidth) {
  82. this.showBlockNum = n - 1;
  83. } else {
  84. this.showBlockNum = n;
  85. }
  86. this.fileList = this.files?.map((item: any) => {
  87. console.log(item);
  88. return {
  89. url: item?.url || item,
  90. name: item?.name,
  91. type: 'http',
  92. // status: 'done',
  93. };
  94. });
  95. this.Previewfilelist = this.fileList;
  96. }
  97. onPreview(url: string) {
  98. this.currentPreviewImg = url;
  99. }
  100. onDelete(index: number) {
  101. console.log(index);
  102. this.fileList.splice(index, 1);
  103. this.active && this.onChange.emit(this.fileList);
  104. }
  105. async onAdd(e: any) {
  106. let files = e.target.files;
  107. if (this.fileList.length + files.length > this.maxlenght) {
  108. const toast = await this.toastController.create({
  109. message: '超出上传文件数量',
  110. color: 'warning',
  111. duration: 1000,
  112. });
  113. toast.present();
  114. return;
  115. }
  116. for (let index = 0; index < files.length; index++) {
  117. const f = files[index];
  118. await this.changeFileReader(f);
  119. }
  120. console.log(this.fileList);
  121. this.active && this.onUpload();
  122. }
  123. //转图片格式
  124. changeFileReader(file: any) {
  125. return new Promise(async (res) => {
  126. const windowURL = window.URL || window.webkitURL;
  127. const src = windowURL.createObjectURL(file);
  128. let size = file.size / 1024; //KB
  129. if(size > this.size){
  130. const toast = await this.toastController.create({
  131. message: `${file.name || '未知'}超过${Math.ceil(this.size/1024)}MB`,
  132. color: 'warning',
  133. duration: 1500,
  134. });
  135. toast.present();
  136. res(true);
  137. return
  138. }
  139. this.fileList.push({
  140. url: src,
  141. size: size,
  142. name: file.name || '未知',
  143. type: 'local',
  144. file: file,
  145. });
  146. res(true);
  147. return;
  148. // const reader: any = new FileReader();
  149. // reader.readAsDataURL(file);
  150. // reader.filename = file.name;
  151. // console.log(file);
  152. // let _this = this;
  153. // reader.onload = async function (e: any) {
  154. // let size = e.total / 1024; //KB
  155. // let baseUrl = e.target.result;
  156. // if (size > _this.size) {
  157. // const toast = await _this.toastController.create({
  158. // message: file.name + '文件过大',
  159. // color: 'warning',
  160. // duration: 1000,
  161. // });
  162. // toast.present();
  163. // res(true);
  164. // return;
  165. // }
  166. // _this.fileList.push({
  167. // url: baseUrl,
  168. // size: size,
  169. // name: file.name || '未知',
  170. // type: 'local',
  171. // file: file,
  172. // });
  173. // res(true);
  174. // };
  175. });
  176. }
  177. async onUpload() {
  178. let authLoad = await this.authSpaceCount();
  179. if (!authLoad) {
  180. const toast = await this.toastController.create({
  181. message: '文件存储空间已满,请联系维护人员。',
  182. color: 'warning',
  183. duration: 1000,
  184. });
  185. toast.present();
  186. return;
  187. }
  188. let fs = this.fileList;
  189. this.loading = await this.loadCtrl.create({ message: '上传中' });
  190. this.loading.present();
  191. try{
  192. for (let index = 0; index < fs.length; index++) {
  193. const f = fs[index];
  194. if (f.type == 'local') {
  195. console.log(f);
  196. let url = await this.onQiniuUpFile(f);
  197. await this.saveAttachment({
  198. size: f.size,
  199. url: url,
  200. name: f.name,
  201. type: this.fileList[index].file.type,
  202. });
  203. this.fileList[index].url = url;
  204. this.fileList[index].type = 'http';
  205. delete this.fileList[index].file;
  206. delete this.fileList[index].size;
  207. }
  208. }
  209. }catch(err){
  210. this.loading.dismiss();
  211. }
  212. this.onChange.emit(this.fileList);
  213. this.loading.dismiss();
  214. }
  215. /* 校验存储空间 */
  216. async authSpaceCount(): Promise<boolean> {
  217. let company = this.company;
  218. let query = new Parse.Query('Company');
  219. query.select('configSpace');
  220. let comObj = await query.get(company);
  221. let limit = comObj.get('configSpace') && comObj.get('configSpace').limit;
  222. // return new Promise((res, rej) => {
  223. let res = await fetch(
  224. `https://server.fmode.cn/api/storage/space?c=${company}`
  225. );
  226. let data = await res.json();
  227. console.log(data);
  228. if (data?.code == 200) {
  229. let spaceMap = data.data;
  230. let _spaceLimit = this.getLimitBytes(limit);
  231. let spaceUsed = spaceMap.totalSize;
  232. console.log('总空间:', _spaceLimit);
  233. console.log('已用空间:', spaceUsed);
  234. if (!_spaceLimit || spaceUsed > _spaceLimit) {
  235. return false;
  236. }
  237. return true;
  238. }
  239. return false;
  240. }
  241. // 单位
  242. getLimitBytes(limit: string) {
  243. let u: number = 1024 * 1024 * 1024;
  244. if (limit) {
  245. let num = Number(limit.slice(0, limit.length - 2));
  246. let unit: string = limit.slice(-2);
  247. switch (unit) {
  248. case 'EB':
  249. u = 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
  250. break;
  251. case 'PB':
  252. u = 1024 * 1024 * 1024 * 1024 * 1024;
  253. break;
  254. case 'TB':
  255. u = 1024 * 1024 * 1024 * 1024;
  256. break;
  257. case 'GB':
  258. u = 1024 * 1024 * 1024;
  259. break;
  260. case 'MB':
  261. u = 1024 * 1024;
  262. break;
  263. case 'KB':
  264. u = 1024;
  265. break;
  266. default:
  267. break;
  268. }
  269. return num * u;
  270. }
  271. return u;
  272. }
  273. onQiniuUpFile(file: any): Promise<string> {
  274. return new Promise((resolve, reject) => {
  275. console.log('进入了上传');
  276. let datepath = this.DateFormat(new Date(), 'hhmmss');
  277. let qiniuFileKey = this.company + '/' + datepath + '/' + file.name;
  278. const putExtra = {
  279. // fname: '',
  280. // params: {},
  281. // mimeType: this.accept === 'image/*' ? 'image/*' : undefined,
  282. };
  283. const config = {
  284. useCdnDomain: true, //使用cdn加速
  285. };
  286. const observable = qiniu.upload(
  287. file.file,
  288. qiniuFileKey,
  289. this.config.uptoken,
  290. putExtra,
  291. config
  292. );
  293. observable.subscribe({
  294. next: (result) => {
  295. // 主要用来展示进度
  296. console.log('上传===', result);
  297. },
  298. error: async (err) => {
  299. console.error(err);
  300. const toast = await this.toastController.create({
  301. message: '上传失败',
  302. color: 'warning',
  303. duration: 1000,
  304. });
  305. toast.present();
  306. reject(err);
  307. },
  308. complete: (res) => {
  309. console.log('上传完成');
  310. console.log(res.key);
  311. console.log(`${this.config.domain}${res.key}`);
  312. resolve(`${this.config.domain}${res.key}`);
  313. // this.loading.dismiss();
  314. },
  315. });
  316. });
  317. }
  318. async saveAttachment(
  319. file: { size?: number; url: string; name: string; type: string },
  320. cateId?: string
  321. ) {
  322. let Attachment = Parse.Object.extend('Attachment');
  323. let attachment = new Attachment();
  324. file.size && attachment.set('size', Math.ceil(file.size));
  325. attachment.set('url', file.url);
  326. attachment.set('name', file.name);
  327. attachment.set('mime', file.type);
  328. attachment.set('company', {
  329. __type: 'Pointer',
  330. className: 'Company',
  331. objectId: this.company,
  332. });
  333. cateId &&
  334. attachment.set('category', {
  335. __type: 'Pointer',
  336. className: 'Category',
  337. objectId: cateId,
  338. });
  339. return await attachment.save();
  340. }
  341. DateFormat(date: Date, fmt: string) {
  342. //author: meizz
  343. let o: any = {
  344. 'M+': date.getMonth() + 1, //月份
  345. 'd+': date.getDate(), //日
  346. 'h+': date.getHours(), //小时
  347. 'm+': date.getMinutes(), //分
  348. 's+': date.getSeconds(), //秒
  349. 'q+': Math.floor((date.getMonth() + 3) / 3), //季度
  350. S: date.getMilliseconds(), //毫秒
  351. };
  352. if (/(y+)/.test(fmt))
  353. fmt = fmt.replace(
  354. RegExp.$1,
  355. (date.getFullYear() + '').substr(4 - RegExp.$1.length)
  356. );
  357. for (let k in o)
  358. if (new RegExp('(' + k + ')').test(fmt))
  359. fmt = fmt.replace(
  360. RegExp.$1,
  361. RegExp.$1.length == 1
  362. ? o[k]
  363. : ('00' + o[k]).substr(('' + o[k]).length)
  364. );
  365. return fmt;
  366. }
  367. }