tm-uploadfile.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. <template>
  2. <view class="tm-upload ">
  3. <tm-dialog v-model="showalert" title="视频预览" :showCancel="false" confirmText="关闭预览" confirmColor="primary text">
  4. <view>
  5. <video :src="vedioUrl" style="width: 500rpx;height: 300rpx;" objectFit="contain"></video>
  6. </view>
  7. </tm-dialog>
  8. <view class="flex-col">
  9. <view v-for="(item,index) in list" :key="index" class="text border-b-2" :class="[color_tmeme,black_tmeme?'grey-darken-4 bk ':'',]">
  10. <view class="flex-between px-24">
  11. <view
  12. class="flex-6 py-18 flex-start text-overflow text-size-m "
  13. >
  14. <view class="d-inline-block pr-12">
  15. <tm-icons v-if="item.statusCode==3" :black="black_tmeme" name="icon-check" size="28" color="green"></tm-icons>
  16. <tm-icons v-if="item.statusCode==0" :black="black_tmeme" name="icon-arrow-alt-from-botto" size="28" color="primary"></tm-icons>
  17. <tm-icons v-if="item.statusCode==2" :black="black_tmeme" name="icon-times-circle" size="28" color="red"></tm-icons>
  18. <tm-icons v-if="item.statusCode==4" :black="black_tmeme" name="icon-exclamation-circle" size="28" color="red"></tm-icons>
  19. <tm-icons v-if="item.statusCode==1" :black="black_tmeme" name="icon-loading" size="28" color="primary"></tm-icons>
  20. </view>
  21. <slot name="file" :info="{item}">
  22. {{item['name']||item['file_name']||item['filename']||item.path}}
  23. </slot>
  24. </view>
  25. <view v-if="!disabled" class="flex-end flex-6" >
  26. <slot name="right" :info="{data:item,index:index}">
  27. <view class="d-inline-block pr-24">
  28. <tm-icons @click="opendoc(item)" :black="black_tmeme" name="icon-eye-fill" size="36" :color="color"></tm-icons>
  29. </view>
  30. <view class="d-inline-block pl-12">
  31. <tm-icons @click="del(index)" :black="black_tmeme" name="icon-times" size="32" color="red"></tm-icons>
  32. </view>
  33. </slot>
  34. </view>
  35. </view>
  36. <!-- 上传提示语。 -->
  37. <view v-if="tips&&!disabled" class=" text-size-xs round-b-2 px-24 py-8"
  38. :class="[
  39. item.statusCode==2||item.statusCode==4?'red text':'',
  40. item.statusCode==1||item.statusCode==0?'black text':'',
  41. item.statusCode==3?color_tmeme+' text':'',
  42. black_tmeme?'bk':''
  43. ]"
  44. >{{item.status}}</view>
  45. <!-- 上传的进度。 -->
  46. <view v-if="item.progress>0&&item.progress!=100&&!disabled" class="tm-upload-pro green"
  47. :style="{width:item.progress+'%'}"></view>
  48. </view>
  49. <view @click="addfile" v-if="list.length<max&&!disabled" class=" grey-lighten-4 ">
  50. <view class="tm-upload-item-ck border-a-0 flex-center text py-50" :class="[color_tmeme,black_tmeme?'grey-darken-4 bk':'']">
  51. <slot name="upload">
  52. <tm-icons name="icon-plus" size="36" :color="color_tmeme"></tm-icons>
  53. <text class="text-size-n pl-12">添加文件</text>
  54. </slot>
  55. </view>
  56. </view>
  57. </view>
  58. </view>
  59. </template>
  60. <script>
  61. /**
  62. * 上传图片组件
  63. * @property {Function} change 每一张图片上传成功都传动触发,并返回上传成功的图片列表。
  64. * @property {Function} del 删除一张图片时触发,返回当前删除的图片数据。
  65. * @property {Number} code = [] 默认:0,服务器上传返回数据中表示成功的标志码。
  66. * @property {Number|String} max = [9] 默认:9,最大上传数量
  67. * @property {String} fileType = [all|image|file|vedio] 默认:all,上传的文件类型
  68. * @property {Array} extension = [*] 默认:[],上传的文件后缀过滤比如:[".jpg",".doc"]
  69. * @property {String|Boolean} disabled = [true|false] 默认:false, 如果禁用,会隐藏上传和删除按钮,只显示已上传的图片。
  70. * @property {String} url = [] 默认:"",上传的地址。
  71. * @property {Array} filelist = [] 默认:[],默认上传显示的图片。如果加上filelist.sync的话,会自动更新数据实现双向绑定。类似于v-model;
  72. * @property {String} url-key = [] 默认:"",返回数据时,如果返回的是对象。则需要提供对象图像地址的key。默认没有,返回的即是图片地址。
  73. * @property {Object} header = [] 默认:{},上传的头部参数。
  74. * @property {String} file-name = [file] 默认:file,上传时的文件key名。
  75. * @property {String} name = [] 默认:'',提交表单时的的字段名称标识
  76. * @property {Boolean|String} tips = [true|false] 默认:true,是否显示底部的上传提示语。上传中,失败等。
  77. * @property {Boolean|String} black = [true|false] 默认:null,暗黑模式。
  78. * @property {Boolean|String} auto-upload = [true|false] 默认:false,是否自动上传,即添加完图片后立即上传。
  79. * @property {Object} responseStu = [] 默认: {code:'code',//服务器返回的码的字段名称data:'data',//服务上传成功后返回 的数据字段名称msg:'msg'//服务器响应信息的字段名称。},服务器响应结构字段映射表
  80. * @property {Number|String} maxsize = [] 默认:10*1024*1024,最大上传的图片大小,10mb大小
  81. * @example <tm-uploadfile></tm-uploadfile>
  82. * @description 可以通过refs.组件获得:addfile主动触发添加文件,stopupload停止上传,startupload开始或者继续上传,del删除一张图片。
  83. */
  84. import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
  85. import tmDialog from "@/tm-vuetify/components/tm-dialog/tm-dialog.vue"
  86. export default {
  87. components:{tmIcons,tmDialog},
  88. name: "tm-uploadfile",
  89. props: {
  90. black:{
  91. type:Boolean|String,
  92. default:null
  93. },
  94. // 最大上传数量,默认9
  95. max: {
  96. type: String | Number,
  97. default: 9
  98. },
  99. // 最大上传数量,默认9
  100. maxsize: {
  101. type: String | Number,
  102. default: 10*1024*1024
  103. },
  104. // 主题色
  105. color: {
  106. type: String,
  107. default: 'primary'
  108. },
  109. // 如果禁用,会隐藏上传和删除按钮。
  110. disabled: String | Boolean,
  111. // 上传的地址。
  112. url: {
  113. type: String,
  114. default: ''
  115. },
  116. // 默认上传显示的图片。如果加上filelist.sync的话,会自动更新数据实现双向绑定。类似于v-model;
  117. filelist: {
  118. type: Array,
  119. default: () => {
  120. return [];
  121. }
  122. },
  123. //返回数据时,如果返回的是对象。则图像地址的key名。默认没有,返回的即是图片地址。
  124. urlKey:{
  125. type:String,
  126. default:''
  127. },
  128. // 上传的头部参数。
  129. header:{
  130. type:Object,
  131. default:()=>{
  132. return {};
  133. }
  134. },
  135. fileType:{
  136. type:String,//上传的文件类型,默认所有。
  137. default:'all'
  138. },
  139. extension:{
  140. type:Array,//上传的文件类型,默认所有。
  141. default:()=>[]
  142. },
  143. // 上传时的文件key名。默认file
  144. fileName:{
  145. type:String,
  146. default:'file'
  147. },
  148. // 是否显示底部的上传提示语。上传中,失败等。
  149. tips: {
  150. type: Boolean|String,
  151. default: true,
  152. },
  153. // 是否自动上传,即添加完图片后立即上传。
  154. autoUpload: {
  155. type: Boolean,
  156. default: false,
  157. },
  158. //提交表单时的的字段名称
  159. name:{
  160. type:String,
  161. default:''
  162. },
  163. // 跟随主题色的改变而改变。
  164. fllowTheme:{
  165. type:Boolean|String,
  166. default:true
  167. },
  168. //定义上传成功返回的code码,默认是0表示上传成功 。
  169. code:{
  170. type:Number,
  171. default:0
  172. },
  173. //上成功后,服务器顺应数据的字段映射表。
  174. responseStu:{
  175. type:Object,
  176. default:()=>{
  177. return {
  178. code:'code',//服务器返回的码的字段名称
  179. data:'data',//服务上传成功后返回 的数据字段名称
  180. msg:'msg'//服务器响应信息的字段名称。
  181. }
  182. }
  183. }
  184. },
  185. computed: {
  186. header_obj:function () {
  187. return this.header;
  188. },
  189. black_tmeme: function() {
  190. if (this.black !== null) return this.black;
  191. return this.$tm.vx.state().tmVuetify.black;
  192. },
  193. color_tmeme:function(){
  194. if(this.$tm.vx.state().tmVuetify.color!==null&&this.$tm.vx.state().tmVuetify.color && this.fllowTheme){
  195. return this.$tm.vx.state().tmVuetify.color;
  196. }
  197. return this.color;
  198. },
  199. },
  200. data() {
  201. return {
  202. showalert:false,
  203. list: [],
  204. vedioUrl:'',
  205. upObje:null,
  206. };
  207. },
  208. created() {
  209. },
  210. async mounted() {
  211. let t = this;
  212. if (typeof t.filelist === 'object' && Array.isArray(t.filelist)) {
  213. let plist = [...t.filelist];
  214. plist.forEach((item, index) => {
  215. let url = "";
  216. if (typeof item === 'string') {
  217. url = item;
  218. } else if (typeof item === 'object') {
  219. url = item[t.urlKey]
  220. }
  221. t.list.push({
  222. url: url,
  223. status: "上传成功",
  224. progress: 100,
  225. fileId: t.$tm.guid(),
  226. statusCode: 3,
  227. data: item,
  228. })
  229. })
  230. }
  231. },
  232. methods: {
  233. opendoc(item){
  234. this.url="";
  235. let type = item.type;
  236. let image = ['jpg','png','jpeg','gif'];
  237. let video = ['mp4','avi','mov','webm','ogg','flv','m3u8'];
  238. let doc = ['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx','text','txt'];
  239. let isSou = [...image,...video,...doc].filter(el=>el==item.type);
  240. if(isSou.length==0){
  241. uni.showToast({
  242. title:"未知文件格式,不支持预览",icon:'none'
  243. })
  244. return;
  245. }
  246. let imageOpen = image.filter(el=>el==item.type);
  247. if(imageOpen.length>0){
  248. this.$tm.preview.previewImg(item.url,[item.url],'url')
  249. return;
  250. }
  251. let videoOpen = video.filter(el=>el==item.type);
  252. if(videoOpen.length>0){
  253. this.url = item.url;
  254. this.showalert = true;
  255. return;
  256. }
  257. let docOpen = doc.filter(el=>el==item.type);
  258. // #ifdef H5
  259. uni.showToast({
  260. title:"不支持预览稿件",icon:'none'
  261. })
  262. return;
  263. // #endif
  264. if(docOpen.length>0){
  265. if(item.url.indexOf('http://tmp/')>-1){
  266. uni.openDocument({
  267. filePath:item.url
  268. })
  269. }else{
  270. uni.showLoading({
  271. title:'下载中',
  272. mask:true
  273. })
  274. uni.downloadFile({
  275. url: item.url,
  276. success: (res) => {
  277. if (res.statusCode !== 200) {
  278. uni.hideLoading()
  279. uni.showToast({
  280. title:"下载失败",
  281. icon:'error'
  282. })
  283. return;
  284. }
  285. uni.hideLoading()
  286. let url = res.tempFilePath;
  287. uni.openDocument({
  288. filePath:url
  289. })
  290. },
  291. fail:()=>{
  292. uni.hideLoading()
  293. uni.showToast({
  294. title:"下载失败",
  295. icon:'error'
  296. })
  297. }
  298. });
  299. }
  300. return;
  301. }
  302. },
  303. errorFile(item,index){
  304. let id = item;
  305. id['loaderror'] = true;
  306. this.list.splice(index,1,id)
  307. },
  308. //动态添加默认已上传的文件。
  309. pushFile(list){
  310. let t= this;
  311. let plist = list||[];
  312. plist.forEach((item, index) => {
  313. let name = "";
  314. let type = "";
  315. let url = "";
  316. if (typeof item === 'string') {
  317. url = item;
  318. name = item;
  319. type = item.substr(item.lastIndexOf(".")+1);
  320. } else if (typeof item === 'object') {
  321. url = item[t.urlKey];
  322. name = item['name']||url;
  323. type = name.substr(name.lastIndexOf(".")+1);
  324. }
  325. t.list.push({
  326. url: url,
  327. type:type,
  328. name:name,
  329. status: "上传成功",
  330. progress: 100,
  331. fileId: t.$tm.guid(),
  332. statusCode: 3,
  333. data: item,
  334. })
  335. })
  336. },
  337. async addfile() {
  338. if(this.disabled) return;
  339. let t = this;
  340. let maxfile = parseInt(this.max) - this.list.length;
  341. if (maxfile <= 0) {
  342. this.$tm.toast("已达上传上限");
  343. return;
  344. };
  345. let url = this.url;
  346. if(!this.upObje){
  347. this.upObje = new this.$tm.upload.uploadfile({
  348. opts:{header:this.header_obj,name:this.fileName},
  349. maxfile:maxfile,
  350. uploadUrl:url,
  351. isAuto:this.autoUpload,
  352. maxsize:this.maxsize,
  353. code:this.code,
  354. responseStu:this.responseStu,
  355. type:this.fileType,
  356. extension:this.extension
  357. });
  358. // 添加已有的图片。
  359. this.upObje.addfile(this.list);
  360. this.upObje.success = function(item){
  361. t.changeSuccess();
  362. }
  363. }else{
  364. this.upObje.setConfig({
  365. type:this.fileType,
  366. extension:this.extension,
  367. maxsize:this.maxsize,maxfile:maxfile,code:this.code,responseStu:this.responseStu,opts:{header:this.header_obj,name:this.fileName}});
  368. }
  369. let clist = await this.upObje.chooseMPH5weixinFile().catch(e=>{
  370. console.error(e);
  371. });
  372. if(clist){
  373. t.list = clist;
  374. }
  375. },
  376. // 停止下载
  377. stopupload(){
  378. if(this.disabled) return;
  379. if(this.upObje){
  380. this.upObje.stop();
  381. }
  382. },
  383. // 继续上传或者开始上传。
  384. startupload(){
  385. if(this.disabled) return;
  386. if(this.upObje){
  387. this.upObje.start();
  388. }
  389. },
  390. // 删除一张图片。
  391. del(index) {
  392. this.$emit("del",this.list[index])
  393. this.list.splice(index, 1);
  394. this.changeSuccess();
  395. },
  396. // 只有上传成功才会触发change。并更新发送数据。
  397. changeSuccess() {
  398. let filelist = [];
  399. this.list.forEach((item, index) => {
  400. if (item.statusCode === 3) {
  401. filelist.push(item.data);
  402. }
  403. })
  404. this.$emit('change', filelist);
  405. this.$emit('update:filelist', filelist);
  406. },
  407. //获取已经上传的图像。
  408. getFile(){
  409. let filelist = [];
  410. this.list.forEach((item, index) => {
  411. if (item.statusCode === 3) {
  412. filelist.push(item.data);
  413. }
  414. })
  415. return filelist;
  416. },
  417. //清除所有已上传的文件。
  418. clearAllFile(){
  419. this.$emit("clear",[])
  420. this.list=[];
  421. this.changeSuccess();
  422. }
  423. },
  424. }
  425. </script>
  426. <style lang="scss" scoped>
  427. </style>