l-file.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. <template>
  2. <view>
  3. <view class='t-toptips' :style="{top: top,background: cubgColor}" :class="[show?'t-top-show':'']">
  4. <view v-if="loading" class="flex flex-sub">
  5. <view class="flex flex-sub">
  6. <view class="cu-progress flex-sub round striped active sm">
  7. <view :style="{ background: color,width: value + '%'}"></view>
  8. </view>
  9. <text class="margin-left">{{value}}%</text>
  10. </view>
  11. <view @click="downOnAbort" v-if="value<100" class="close">取消</view>
  12. </view>
  13. <block v-else>{{msg}}</block>
  14. </view>
  15. <!-- #ifdef H5 -->
  16. <h5-file
  17. v-if="showH5"
  18. ref="h5File"
  19. :logo="logo"
  20. :showTip="showTip"
  21. :progress="value"
  22. @abort="onAbort"
  23. @close="onCloseH5"
  24. ></h5-file>
  25. <!-- #endif -->
  26. </view>
  27. </template>
  28. <script>
  29. import h5File from './h5-file.vue';
  30. export default {
  31. components: {h5File},
  32. props: {
  33. logo: {
  34. type: String,
  35. default: 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F00%2F00%2F07%2F155788a6d8a5c42.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1619847627&t=2da40b583002205c204d980b54b35040'
  36. },
  37. top: {
  38. type: String,
  39. default: 'auto'
  40. },
  41. bgColor: {
  42. type: String,
  43. default: 'rgba(49, 126, 243, 0.5)',
  44. },
  45. color: {
  46. type: String,
  47. default: '#55aa00',
  48. }
  49. },
  50. data() {
  51. this.uploadTask = null;
  52. this.downloadTask = null;
  53. return {
  54. cubgColor: '',
  55. loading: false,
  56. value: 5,
  57. show: false,
  58. msg: '执行完毕~',
  59. showH5: false,
  60. showTip: false,
  61. }
  62. },
  63. methods: {
  64. toast(title = '',{ duration = 2000, icon = 'none'} = {}) {
  65. uni.showToast({title,duration,icon});
  66. },
  67. getRequest(url) {
  68. let theRequest = new Object();
  69. let index = url.indexOf("?");
  70. if (index != -1) {
  71. let str = url.substring(index+1);
  72. let strs = str.split("&");
  73. for(let i = 0; i < strs.length; i ++) {
  74. theRequest[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]);
  75. }
  76. }
  77. return theRequest;
  78. },
  79. /*
  80. 上传说明:
  81. url:上传接口地址
  82. name:上传文件key值
  83. header: 上传接口请求头
  84. ...:body内其他参数
  85. */
  86. appChooseFile({cuWebview = '',url,name = 'file',header,addName = '',addSize = '',maxSize = 10,...formData} = {}) {
  87. // #ifdef APP-PLUS
  88. let wvPath = '/uni_modules/l-file/hybrid/html/index.html';
  89. let wv = plus.webview.create("",wvPath,{
  90. 'uni-app': 'none',
  91. top: 0,
  92. height: '100%',
  93. background: 'transparent'
  94. },{
  95. url,
  96. header,
  97. addName,
  98. addSize,
  99. maxSize,
  100. formData,
  101. key: name,
  102. logo: this.logo
  103. });
  104. wv.loadURL(wvPath);
  105. this.$root.$scope.$getAppWebview().append(wv);
  106. wv.overrideUrlLoading({mode:'reject'},(e)=>{
  107. let {fileName,size,str} = this.getRequest(e.url);
  108. fileName = unescape(fileName);
  109. str = unescape(str);
  110. return this.handleBack(fileName,str,size);
  111. });
  112. // #endif
  113. },
  114. wxChooseFile({maxSize = 10,...param}) {
  115. wx.chooseMessageFile({
  116. count: 1,
  117. type: 'file',
  118. success: ({tempFiles}) => {
  119. this.setdefUI();
  120. let file = tempFiles[0];
  121. if(file.size > (1024*1024 * Math.abs(maxSize))) {
  122. uni.showToast({
  123. title:`单个文件请勿超过${maxSize}M,请重新上传`,
  124. icon: 'none'
  125. });
  126. return this.errorHandler('文件选择失败',this.upErr);
  127. }
  128. this.handleWXUpload(param,file);
  129. },
  130. fail: () => this.errorHandler('文件选择失败',this.upErr)
  131. })
  132. },
  133. h5ChooseFile({maxSize = 10,...param} = {}) {
  134. this.showH5 = true;
  135. this.value = 0;
  136. this.$nextTick(()=>{
  137. this.$refs.h5File.hFile.onchange = (event) => {
  138. let file = event.target.files[0];
  139. if(file.size > (1024*1024 * Math.abs(maxSize))) {
  140. uni.showToast({
  141. title:`单个文件请勿超过${maxSize}M,请重新上传`,
  142. icon: 'none'
  143. });
  144. return;
  145. }
  146. this.handleH5Upload(param, file);
  147. }
  148. })
  149. },
  150. handleH5Upload({url,name = 'file',header,addName,addSize,...data} = {},tempFile) {
  151. let fileName = tempFile.name;
  152. let formData = new FormData();
  153. for (let keys in data) {formData.append(keys, data[keys]);}
  154. if (addName) {formData.append(addName, fileName);}
  155. if (addSize) {formData.append(addSize, tempFile.size);}
  156. formData.append(name, tempFile);
  157. this.uploadTask = new XMLHttpRequest();
  158. this.uploadTask.open("POST", url, true);
  159. for (let keys in header) {this.uploadTask.setRequestHeader(keys, header[keys]);}
  160. this.uploadTask.ontimeout = () => {
  161. setTimeout(()=>{
  162. this.showTip = false;
  163. return this.errorHandler('请求超时',this.upErr);
  164. },1000);
  165. };
  166. this.uploadTask.upload.addEventListener("progress",(event) => {
  167. if(event.lengthComputable){
  168. this.value = Math.ceil(event.loaded * 100 / event.total);
  169. if (this.value > 100) {this.value=100;}
  170. this.$forceUpdate();
  171. }
  172. }, false);
  173. this.uploadTask.onreadystatechange = (ev) => {
  174. if(this.uploadTask.readyState == 4) {
  175. console.log('status:'+this.uploadTask.status);
  176. if (this.uploadTask.status == 200) {
  177. return this.handleBack(fileName,this.uploadTask.responseText,tempFile.size);
  178. }
  179. else {
  180. this.showTip = false;
  181. if (this.uploadTask.status == 0) {
  182. console.log('请检查请求头Content-Type与服务端是否匹配,并确认服务端已正确开启跨域');
  183. }
  184. return this.errorHandler('文件上传失败',this.upErr);
  185. }
  186. }
  187. };
  188. this.showTip = true;
  189. this.uploadTask.send(formData);
  190. },
  191. handleWXUpload({url,name = 'file',header,addName,addSize,...formData} = {},tempFile) {
  192. let opt = {url,name,formData,header,filePath:tempFile.path};
  193. if (addName) {opt.formData[addName] = tempFile.name;}
  194. if (addSize) {opt.formData[addSize] = tempFile.size;}
  195. let fileName = tempFile.name;
  196. opt['fail'] = (e) => {
  197. this.showTip = false;
  198. return this.errorHandler('文件上传失败',this.upErr)
  199. };
  200. opt['success'] = (res) => {
  201. if (res.statusCode==200) {
  202. let data = JSON.parse(res.data);
  203. //可自行添加后台返回状态验证
  204. return this.onCommit(this.$emit('up-success',{fileName,size:tempFile.size,data}));
  205. }
  206. return this.errorHandler('文件上传失败',this.upErr);
  207. };
  208. this.showTip = true;
  209. this.uploadTask = uni.uploadFile(opt);
  210. this.uploadTask&&this.uploadTask.onProgressUpdate(({progress = 0}) => {
  211. if (progress <= 100) {
  212. this.value = progress;
  213. this.$forceUpdate();
  214. }
  215. });
  216. },
  217. onCloseH5() {
  218. this.showH5 = false;
  219. },
  220. onAbort() {
  221. this.uploadTask&&this.uploadTask.abort();
  222. this.showTip = false;
  223. },
  224. downOnAbort() {
  225. this.downloadTask&&this.downloadTask.abort();
  226. this.onCommit(false,'已取消');
  227. },
  228. // app+h5返回内容,此处按实际项目修改
  229. handleBack(fileName,str = '{}',size) {
  230. console.log('可根据需求自行修改emit内容,服务端返回:'+ str);
  231. try{
  232. str = JSON.parse(str);
  233. }catch(e){
  234. console.log('Tips: responseText are not JSON');
  235. }
  236. return this.onCommit(this.$emit('up-success',{statusCode: 200,fileName,size,data:str}));
  237. },
  238. /*
  239. 上传
  240. */
  241. upload(param = {}) {
  242. if (!param.url) {this.toast('上传地址不正确');return;}
  243. if (this.loading) {this.toast('还有个文件玩命处理中,请稍候..');return;}
  244. // #ifdef APP-PLUS
  245. return this.appChooseFile(param);
  246. // #endif
  247. // #ifdef MP-WEIXIN
  248. return this.wxChooseFile(param);
  249. // #endif
  250. // #ifdef H5
  251. this.h5ChooseFile(param);
  252. // #endif
  253. },
  254. /*
  255. 打开文件
  256. */
  257. open(filePath) {
  258. let system = uni.getSystemInfoSync().platform;
  259. if(system == 'ios'){filePath = encodeURI(filePath);}
  260. uni.openDocument({
  261. filePath,
  262. success: (res) => {console.log('打开文档成功');}
  263. });
  264. },
  265. /*
  266. APP自定义保存
  267. */
  268. plusSaveFile({url,customName='',opt}) {
  269. return new Promise((resolve,reject)=>{
  270. // 可自行修改参数
  271. // 参数api: http://www.html5plus.org/doc/zh_cn/downloader.html#plus.downloader.DownloadOptions
  272. let downloadOptions = {
  273. method: "GET",
  274. timeout: 120,
  275. retryInterval: 10,
  276. filename:'file://storage/emulated/0/lFile/' + customName
  277. };
  278. downloadOptions = {...downloadOptions,...opt};
  279. this.downloadTask = plus.downloader.createDownload(url, downloadOptions,(d, status)=>{
  280. // 下载完成
  281. if(status == 200){
  282. let tempFilePath = d.filename;
  283. this.value = 100;
  284. this.onCommit(resolve(tempFilePath))
  285. } else {
  286. this.errorHandler('下载失败',reject)
  287. }
  288. this.downloadTask = null;
  289. });
  290. this.downloadTask.addEventListener('statechanged',({downloadedSize=0,state=0,totalSize=0}={})=>{
  291. if (state===3) {
  292. let total = totalSize>0?totalSize:fileSize;
  293. let progressVal = Math.ceil(downloadedSize / total* 100);
  294. this.value = progressVal>100?100:progressVal;
  295. this.$forceUpdate()
  296. }
  297. },false);
  298. this.downloadTask.start();
  299. });
  300. },
  301. /*
  302. 下载
  303. type: temporary=返回临时地址,save=长期保存到本地
  304. */
  305. download({url,type = 'temporary',customName = '',...opt}) {
  306. if (this.loading) {
  307. this.toast('还有个文件玩命处理中,请稍候..');
  308. return;
  309. }
  310. this.setdefUI();
  311. // #ifdef APP-PLUS
  312. if (type == 'save') {
  313. return this.plusSaveFile({url,customName,opt});
  314. }
  315. // #endif
  316. return new Promise((resolve,reject)=>{
  317. url = encodeURI(url);
  318. this.downloadTask = uni.downloadFile({
  319. url,
  320. ...opt,
  321. success: ({statusCode,tempFilePath}) => {
  322. if (statusCode === 200) {
  323. // #ifndef H5
  324. if (type == 'save') {
  325. uni.saveFile({
  326. tempFilePath,
  327. success:({savedFilePath}) => this.onCommit(resolve(savedFilePath)),
  328. fail: () => this.errorHandler('下载失败',reject)
  329. });
  330. }
  331. else {
  332. this.onCommit(resolve(tempFilePath))
  333. }
  334. // #endif
  335. // #ifdef H5
  336. this.onCommit(resolve(tempFilePath))
  337. // #endif
  338. }
  339. },
  340. fail: () => this.errorHandler('下载失败',reject)
  341. });
  342. this.downloadTask.onProgressUpdate(({progress = 0}) => {
  343. if (progress <= 100) {
  344. this.value = progress;
  345. this.$forceUpdate();
  346. }
  347. });
  348. })
  349. },
  350. onCommit(resolve,msg = '执行完毕~') {
  351. this.msg = msg;
  352. this.loading = false;
  353. this.showTip = false;
  354. this.cubgColor = 'rgba(57, 181, 74, 0.5)';
  355. this.uploadTask = null;
  356. this.downloadTask = null;
  357. setTimeout(()=>{this.show = false;this.showH5 = false;},1500);
  358. return resolve;
  359. },
  360. setdefUI() {
  361. this.cubgColor = this.bgColor;
  362. this.value = 0;
  363. this.loading = true;
  364. this.show = true;
  365. },
  366. upErr(errText) {
  367. this.$emit('up-error',errText);
  368. },
  369. errorHandler(errText,reject) {
  370. this.msg = errText;
  371. this.loading = false;
  372. this.cubgColor = 'rgba(229, 77, 66, 0.5)';
  373. this.uploadTask = null;
  374. this.downloadTask = null;
  375. setTimeout(()=>{this.show = false;},1500);
  376. return reject(errText);
  377. }
  378. }
  379. }
  380. </script>
  381. <style scoped>
  382. .t-toptips {
  383. width: 100%;
  384. padding: 18upx 30upx;
  385. box-sizing: border-box;
  386. position: fixed;
  387. z-index: 90;
  388. color: #fff;
  389. font-size: 30upx;
  390. left: 0;
  391. display: flex;
  392. align-items: center;
  393. justify-content: center;
  394. word-break: break-all;
  395. opacity: 0;
  396. transform: translateZ(0) translateY(-100%);
  397. transition: all 0.3s ease-in-out;
  398. }
  399. .close {
  400. width: 3em;
  401. text-align: right;
  402. }
  403. .t-top-show {
  404. transform: translateZ(0) translateY(0);
  405. opacity: 1;
  406. }
  407. .flex {
  408. display: flex;
  409. align-items: center;
  410. }
  411. .flex-sub {
  412. flex: 1;
  413. }
  414. .round {
  415. border-radius: 5000upx;
  416. }
  417. /* ==================
  418. 进度条
  419. ==================== */
  420. .cu-progress {
  421. overflow: hidden;
  422. height: 28upx;
  423. background-color: #ebeef5;
  424. display: inline-flex;
  425. align-items: center;
  426. width: 100%;
  427. }
  428. .cu-progress+view,
  429. .cu-progress+text {
  430. line-height: 1;
  431. }
  432. .cu-progress.xs {
  433. height: 10upx;
  434. }
  435. .cu-progress.sm {
  436. height: 20upx;
  437. }
  438. .cu-progress view {
  439. width: 0;
  440. height: 100%;
  441. align-items: center;
  442. display: flex;
  443. justify-items: flex-end;
  444. justify-content: space-around;
  445. font-size: 20upx;
  446. color: #ffffff;
  447. transition: width 0.6s ease;
  448. }
  449. .cu-progress text {
  450. align-items: center;
  451. display: flex;
  452. font-size: 20upx;
  453. color: #333333;
  454. text-indent: 10upx;
  455. }
  456. .cu-progress.text-progress {
  457. padding-right: 60upx;
  458. }
  459. .cu-progress.striped view {
  460. background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
  461. background-size: 72upx 72upx;
  462. }
  463. .cu-progress.active view {
  464. animation: progress-stripes 2s linear infinite;
  465. }
  466. @keyframes progress-stripes {
  467. from {
  468. background-position: 72upx 0;
  469. }
  470. to {
  471. background-position: 0 0;
  472. }
  473. }
  474. </style>