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,我们可以拼出一个完整的文件夹路径,我们直接在这个路径下遍历文件就行了,嗯,暂时先这样,如果用着不爽,大不了再在数据库里加一个字段而已。
(我在写这篇文章时,添加最后一张图时火狐会闪退,唉。)
下一篇该做用列表显示出任务来了,敬请期待。