当前位置:首页 > 技术分析 > 正文内容

uniapp做物业报修APP:任务创建(2),多图片上传

上一篇我们实现了保存新任务的文字部分,并用json返回了新任务的ID,这一篇我们实现上传图片。

其实在脑子里一想,这个功能很简单,但实际做起来,还是要啰嗦很多行代码。

上传图片要用到uni.uploadFile这个API,这个API本身支持多文件上传,如果是做APP之类的,可以直接使用。但是微信小程序里不提供多文件上传功能,因此我们要用递归实现一个一个的上传。

首先我们要封装一个上传文件的API,虽然用得少,但我还是把它封闭成全局的了,代码如下:

/**
 * 循环递归上传图片
 * sData为参数,里面的 .tempFilePaths数组 存储了要上传的临时图片文件们,
 *  .uploadIndex 为当前要上传哪个。
 * callBackFun 为上传成功后的回调(每成功一个调用一次)
 * progressFun 为上传中的进度条
 * 这个uni.uploadFile一次可以上传N个文件,但是
 * 在微信小程序里,每次只能上传一个。所以
 * 在这里递归调用,把所有文件上传。
 */
Vue.prototype.uploadAPI = function(sData, callBackFun, progressFun = false) {
	if (!sData.hasOwnProperty("class") || !sData.hasOwnProperty("fun") || sData.tempFilePaths.length < 1) {
		//如果data对象里没有这两个属性,就不是一个合格的调用。
		return false;
	}
	//keyStr我放外面去了
	sData.sKey = md5(md5(keyStr) + md5(sData.class) + md5(sData.fun) + md5(new Date().format("yyyy-MM-dd")));

	var that = this;
	var uploadTask = uni.uploadFile({
		url: "https://***.***.com/***/iLaoZhao/DaYeLaiWanA.php",
		filePath: sData.tempFilePaths[sData.uploadIndex], //每次只传一个文件
		name: 'file',
		formData: sData,
		success: (uploadFileRes) => {
			callBackFun(uploadFileRes, sData.uploadIndex);
			sData.uploadIndex ++; //文件游标+1
			if(sData.uploadIndex < sData.tempFilePaths.length){
				that.uploadAPI(sData, callBackFun, progressFun); //递归调用自己传下一个
			}
		}
	});

  //监视上传进度
	uploadTask.onProgressUpdate((res) => {
		if(progressFun != false) progressFun(res, sData.uploadIndex);
	});
}

新建任务的APP的VUE文件我全部放一下吧,因为改了很多东西:

<template>
	<view>
		<view class="title">
			工单内容:
			<button style="float:right;" type="primary" size="mini" @click="newTask">确定</button>
		</view>
		<view>
			<textarea v-model="content" class="ta" placeholder="工单内容" />
		</view>
		<view class="uploadPic">
			<block v-for="(imgsrc, index) in pics">
				<view class="item">
					<view @click="delPic(index)" class="delBtn">X</view>
					<image @click="preViewPic(index)" :src="imgsrc" style="width:100%;height:100%;"></image>
				</view>
			</block>
			<view class="item itemAdd" @click="selectPic"><image src="../../static/add-image.png" style="width:60rpx;height:60rpx;"></image></view>
		</view>
		
		<uni-popup ref="popup" type="top" backgroundColor="#fff" style="width:100%;box-shadow:0rpx 8rpx 50rpx #c8c8c8;">
			<view style="padding:50rpx;font-size:22rpx;line-height:200%;width:100%;">
				<view>创建任务:{{cTask_curr}}</view>
				<view>图片上传:{{cTask_picIndex}}/{{cTask_picCount}}</view>
				<view>{{cTask_picUpSize}}/{{cTask_picSize}}</view>
				<view style="width:80%;"><progress :percent="cTask_progress" show-info stroke-width="3" /></view>
			</view>
		</uni-popup>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				userMsg : false,
				pics:[], //存放选择的照片
				content:"",
				taskID : -1,
				uploadedPics:[],
				

				cTask_curr:'正在创建...',
				cTask_picCount:0,
				cTask_picIndex:0,
				cTask_picSize:0,
				cTask_picUpSize:0,
				cTask_progress:0

			}
		},
		onShow() {
			this.userMsg = this.getLoginMsg();
			if(this.userMsg == false){
				uni.showModal({
						title: '错误',
						content: '还没有登录呢。现在转到登录页吗?',
						success: function (res) {
							if (res.confirm) {
								console.log('用户点击确定');
								uni.reLaunch({
									url:"../login/login"
								})
							} else if (res.cancel) {
								console.log('用户点击取消');
							}
						}
					});
				return;
			}
		},
		methods: {
			selectPic:function(e){
				//从图库选择照片,或者从相机拍照片
				var that = this;
				uni.chooseImage({
				    success: function (res) {
						that.pics = that.pics.concat(res.tempFilePaths);
				    }
				});
			},
			preViewPic:function(index){
				//点击照片时进行全屏预览
				let photoList = this.pics.map(item => {
					return item;
				});
				uni.previewImage({
					current: index,     // 当前显示图片的链接/索引值
					urls: photoList,    // 需要预览的图片链接列表,photoList要求必须是数组
					loop:true   // 是否可循环预览
				});
			},
			delPic:function(index){
				//点删除按钮时删除该照片
				this.pics.splice(index,1);
			},
			newTask:function(e){
				if(this.content.length < 1){
					uni.showModal({
							title: '错误',
							content: '任务内容不能为空。',
							success: function (res) {
								if (res.confirm) {
									console.log('用户点击确定');
								} else if (res.cancel) {
									console.log('用户点击取消');
								}
							}
						});
					return;
				}
				//别看上面这么多行,其实就是两个函数,所以就不分开处理了
				//既登录了又有内容,下面就得新建任务了。
				
				this.$refs.popup.open('top');
				this.cTask_curr = '创建任务...';
				this.cTask_picCount = this.pics.length;
				this.cTask_picIndex = 0;
				this.cTask_picSize = 0;
				this.cTask_picUpSize = 0;
				this.cTask_progress = 0;
				
				var that=this;
				this.requestAPI({
					class : "tasks",
					fun : "new",
					phoneNum : that.userMsg.userPhone,
					vCode : that.userMsg.vCode,
					content : that.content
				},function(res){
					if(res.data.code == 1){
						//任务创建成功
						that.taskID = res.data.data; //这是创建的任务的ID
						//接下来要用这个ID来上传图片文件。
						that.uploadPics();
					}
					//console.log(res);
				});
			},
			uploadPics:function(){
				if (this.pics.length < 1)	return;
				
				this.cTask_curr = '上传图片...';
				this.cTask_picIndex = 1;
				
				var that = this;
				this.uploadAPI({
					class : "upload",
					fun : "pic",
					phoneNum : that.userMsg.userPhone,
					vCode : that.userMsg.vCode,
					taskID : that.taskID,
					tempFilePaths : that.pics,
					uploadIndex : 0,
				},function(res, index){
					//每上传完一个图,这里会被调用一次
					//console.log("第N个上传完毕:" + index);
					that.uploadedPics.push(res.data.data);
					if(index+1 == that.pics.length){
						that.cTask_curr = '上传完毕。';
						that.$refs.popup.close();
					}
				},function(res, index){
					//上传进度改变,会调用这里。

					that.cTask_picIndex = index+1;
					that.cTask_picSize = res.totalBytesExpectedToSend;
					that.cTask_picUpSize = res.totalBytesSent;
					that.cTask_progress = res.progress;
					// console.log('第几个' + index);
					// console.log('上传进度' + res.progress);
					// console.log('已经上传的数据长度' + res.totalBytesSent);
					// console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
				});
			}
			
		}
	}
</script>

下面是API端接收文件的代码API:upload/pic

<?php
/**
 * 在和API主目录同一级目录下的uploads目录下存储上传的图片文件
 文件名是当前日期时间和四位随机数(为的是有人同时上传图片时不会重名)
 */
if (checkUserPhoneAndVCode() == true){
	
	//上传根目录,用四次dirname是我不想用../,dirname可以剥离一级文件/文件夹,注意结尾没有/
	//这里的__FILE__是常量,它永远代表了当前文件的包含路径的全名
	$uploaddir = dirname(dirname(dirname(dirname(__FILE__))))."/uploads/";
	$urlDir = "/uploads/"; //前端显示的路径,这个是根据主机根目录来显示的
	
	$userMsg = getUserInfo(POST("phoneNum")); //这一步永远不会出错,不用考虑容错
	$cDir = $userMsg['users_groupID'] . "/"; //把组ID拼接进去,这样每个物业单独用一个目录
	$cDir = $cDir . $userMsg['users_ID'] . "/"; //再把用户ID拼进去,这样每个用户发的图单独一个目录
	$cDir = $cDir . POST("taskID") . "/"; //把任务ID拼接进去
	
	$uploaddir .= $cDir;
	$urlDir .= $cDir;
	
	//文件名,当前日期时间加四位随机数
	//因为移动设备传过来的文件名基本都不可用,所以要自定义文件名
	$baseName = date("YmdHis") . rand(1000,9999); 
	//连接上扩展名
	$fileNamePath = $uploaddir . $baseName . '.' .pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
	$fileUrl = $urlDir . $baseName . '.' .pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
	
	if(!is_dir($uploaddir)){ //路径是否存在
		//如果存在,就创建这个目录
	    mkdir($uploaddir,0777,true);
	}
	
	if (move_uploaded_file($_FILES['file']['tmp_name'], $fileNamePath)) {
	    output2Die("上传成功。",1, $fileUrl); //成功,返回文件的url
	} else {
	    output2Die("上传失败,也不知道啥原因呢。", -1, $fileNamePath);
	}
}
output2Die("没有上传权限", -2);

测试运行一下:

然后再去FTP上看上传的文件(文件在/uploads/组ID/用户ID/taskID下)

这里有一点要说明一下。

在逻辑中,我们并没有把上传的图片文件存到数据库对应的任务记录下面。这也是我突发奇想的一个点子,因为根据组和任务创建者ID,我们可以拼出一个完整的文件夹路径,我们直接在这个路径下遍历文件就行了,嗯,暂时先这样,如果用着不爽,大不了再在数据库里加一个字段而已。

(我在写这篇文章时,添加最后一张图时火狐会闪退,唉。)

下一篇该做用列表显示出任务来了,敬请期待。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/4242.html

标签: vue递归
分享给朋友:

“uniapp做物业报修APP:任务创建(2),多图片上传” 的相关文章

Gitlab+Jenkins通过钩子实现自动部署web项目,图文详细教程

扩展参考:Jenkins+Gitlab通过脚本自动部署回滚web项目至集群 一:基础环境介绍及准备1):Gitlab服务器:ubuntu 192.168.152.131 ---参考搭建:Linux安装gitlab,docker安装gitlab教程2):Jenkins服务器:ubunu 192.168...

基于gitlab的PR操作教程

基于gitlab的PR操作教程注:该教程主要基于git命令行操作,其他图形化工具也可完成以下所有操作步骤,顺手即可。推荐工具:Source Tree ,TortoiseGit参考:gitflow一 . 基于分支的PR操作1. 本地切换到master分支1. 拉取最新代码2. 基于master创建ho...

Git 分支管理策略汇总

最近,团队新入职了一些小伙伴,在开发过程中,他们问我 Git 分支是如何管理的,以及应该怎么提交代码?我大概说了一些规则,但仔细想来,好像也并没有形成一个清晰规范的流程。所以查了一些资料,总结出下面这篇文章,一共包含四种常见的分支管理策略,分享给大家。Git flow在这种模式下,主要维护了两类分支...

el-table内容\n换行解决办法

问题请求到的数据带有换行符 '\n'但页面展示时不换行statusRemark: "\"1、按期完成计划且准确率100%,得100分;\n2、各项目每延误1天,扣1分;每失误1次或者员工投诉1次,扣3分,失误层面达到公司级影响较大的,该项绩效分数为0\"\n&...

最快清除数组空值?分享 1 段优质 JS 代码片段!

本内容首发于工粽号:程序员大澈,每日分享一段优质代码片段,欢迎关注和投稿!大家好,我是大澈!本文约 600+ 字,整篇阅读约需 1 分钟。今天分享一段优质 JS 代码片段,用最简洁的代码清除了数组中的空值。老规矩,先阅读代码片段并思考,再看代码解析再思考,最后评论区留下你的见解!const arr...

一起学Vue:路由(vue-router)

前言学习vue-router就要先了解路由是什么?前端路由的实现原理?vue-router如何使用?等等这些问题,就是本篇要探讨的主要问题。vue-router是什么路由是什么?大概有两种说法:从路由的用途上来解释路由就是指随着浏览器地址栏的变化,展示给用户的页面也不相同。从路由的实现原理上来解释路...