领先的免费Web技术教程,涵盖HTML到ASP.NET

网站首页 > 知识剖析 正文

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

nixiaole 2025-05-11 17:40:10 知识剖析 30 ℃

上一篇我们实现了保存新任务的文字部分,并用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,我们可以拼出一个完整的文件夹路径,我们直接在这个路径下遍历文件就行了,嗯,暂时先这样,如果用着不爽,大不了再在数据库里加一个字段而已。

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

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

最近发表
标签列表