UniAPP优美地实现多图片上传、多行文本输入框以、单选框

太忙了,偶尔也搞搞前端,德智体美全方位发展,但是不能忘记自己最爱的还是Java哈哈哈

多亏:
uniapp插件市场

需求概述:

提交一条故障核实的记录数据,其中包括:
①是否准确(单选)
②故障描述(多行文本300字以内)
③故障照片(3张以内,单张不超过10M)

业务逻辑:
对于照片的选择机制,基本在发朋友圈模式上进行了一些改动:选择照片按钮和照片一样大,选完后照片出现在原来按钮的位置,按钮移动到下一张照片位置,当选完第三张时,选择按钮图片消失,并提示最多选择3张,如图所示:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
选择照片并显示以后,照片暂存在本地,并不上传至服务器,直到用户点击“故障确认”按钮,才将图片上传至服务器。
每张照片右上角有1/4圆的删除图片按钮,点击可以删除图片

进到这个页面有两种情况,
第一:数据库中没有此条记录的数据,则为add状态,可以正常选择后提交到后台执行insert语句
第二:数据库中有此条记录的数据,则为edit状态,但是先禁用页面所有可输入的按钮,若想改变数据内容,点击右上角编辑按钮,即可编辑。
点击故障核实提交后,先执行上传图片的request,然后把图片路径返回给前端,再执行入库的request,这就要求第一个request必须是同步的,由于是用的uni.uploadFile,没找到它自己设置同步的办法,所以用了一个定时器来阻塞,这个还是比较巧妙的值得借鉴的。

源代码

主页面

<template>
	
	<view>
		<view class="cell" >
			<view class="cell-left">
				<view class="cell-text">是否准确:</view>
				<view class="cell-text">
					<view class="radio-content">
						<view class="radio-content" @click="changeRadio1">
							<text style="color: black;">是</text>
							<view class="radio" :class="radio1 == 1 ? 'radio-default':''">
								<view :class="radio1 == 1 ? 'radio-active':''" :style="radiobakcolor"></view>
							</view>
							
						</view>
						<view class="radio-content radio-right" @click="changeRadio2">
							<text style="color: black;">否</text>
							<view class="radio" :class="radio1 == 2 ? 'radio-default':''">
								<view :class="radio1 == 2 ? 'radio-active':''" :style="radiobakcolor"></view>
							</view>
							
						</view>
				  </view>
				</view>
			</view>
			<view class="cell-left">
				<view class="cell-text">故障描述:</view>
			</view>
			<view class="cell-left">
				
				<view class="cell-text">
					<MyTextarea ref="content" pl="请在此处输入故障描述" :len='300' ></MyTextarea>
				</view>
			</view>
			<view class="cell-left">
				<view class="cell-text">照片上传:</view>
				<view class="cell-text" style="color: #8C8C8C;width: 180px;">(最多上传3张图片)</view>
			</view>
			<view class="cell-left">
				<view class="qtpicker">
					<!-- 选中待上传的图片 -->
					<view class="preImgs" v-for="(val,index) in preImgUrl" :key='index'>
						<image style="border-radius: 6px;" mode="" :src="val" @click="showImg(val,index)">
						</image>
						<!-- 删除某张图片 -->
						<!--
						<view v-show="isShowDelImgBtn" class="cuo" @click="delImg(index)">×</view>
						-->
						<view v-show="isShowDelImgBtn">
							<image class="cuo"
								mode="" src="../../static/del_pic.png" @click="delImg(index)">
							</image>
						</view>
						
					</view>
				 
					<!-- 添加图片的按钮 -->
					<!--
						<view class="addImg" v-show="isShowAddImgBtn" @click="chooseImg" style="border-radius: 9px;">
							<view>+</view>
						</view>
						-->
					<view v-show="isShowAddImgBtn">
						<image  style="width: 200rpx;height: 200rpx;margin-left: 13rpx;margin-top: 13rpx;" 
						 @click="chooseImg"
							mode="" src="../../static/add_pic.png">
						</image>
					</view>
					
				</view>
			</view>
			
		</view>
		<!-- 确定上传到服务器 -->
		<button :disabled="submitForbide" type="primary" style="width: 50%;margin-top: 40px;" @click="submitImg">故障确认</button>
	</view>
</template>
 
<script>
	import MyTextarea from "@/components/my-textarea/my-textarea.vue"
	import { toPicDetail ,
	BASE_URL,
	picSubmit,
	deletePic} from '../../common/js/api.js'
	import { getFaultConfirmInfo,submitFaultConfirm } from '@/common/js/api.js'
	export default {
		components: {
			MyTextarea
		},
		data() {
			return {
				title: 'Hello',
				radio1: 0,
				preImgUrl: [], //本地预览的图片数据
				imgNum: 0, //一共选中了多少张照片,用来限制本次最大上传数量
				afterUploadPath: '', //上传图片后,返回上传后的图片地址入库用
				isShowAddImgBtn: true,
				content:'',
				fault_id:'',
				addoredit:'add',
				editclick:0,//是否点击了右上角的编辑按钮
				faultConfirmId:'',
				isShowDelImgBtn:true,
				submitForbide:false,
				radiobakcolor:''
			}
		},
		onLoad(opts) {
			this.fault_id=opts.qry_faultid
			this.getFaultConfirmInfoVue(opts.qry_faultid)
		},
		methods: {
			getFaultConfirmInfoVue(qry_faultid) {
				getFaultConfirmInfo({FAULT_ID: qry_faultid})
				.then((res) => {
					console.log("成功:"+res.listDataMap);
					//如果能查询到核实记录,则回显
					if(res.listDataMap.length>0){
						//页面置为编辑状态
						this.addoredit='edit'
						let dataObj = res.listDataMap[0]
						//禁用输入框和图片编辑和是否准确按钮,改变样式
						this.radiobakcolor='background-color: grey;'
						this.$refs["content"].textForbide = true
						this.isShowDelImgBtn=false
						this.submitForbide=true
						//此条记录的id,更新用
						this.faultConfirmId = dataObj.id
						//故障描述
						this.$refs["content"].content = dataObj.confirm_comment
						//是否准确
						this.radio1=dataObj.confirm_type
						let imgPathStr = dataObj.img_path+""
						//每一个文件的文件名:[xxx.jpg,yyy.png]
						let imgPathArr = imgPathStr.split(",")
						//拼接图片回显路径的头:http://localhost:8080/uploadPic/
						let imgHeadPath = res.actionResult.message
						
						//回显图片
						if(imgPathStr.indexOf(",")!=-1){
							//如果够3张了则隐藏添加图片按钮
							this.imgNum=imgPathArr.length
							if(this.imgNum==3){
								this.isShowAddImgBtn=false
							}
							imgPathArr.forEach((item) => {
								this.preImgUrl.push(imgHeadPath+item)
							})
						}
						
					}
				})
			},
			//这个方法很奇怪,在pages.vue里配上buttons,在页面上写上这个方法,点击就可以执行了
			onNavigationBarButtonTap(e) {
				console.log("点击编辑按钮")
				//启用输入框和图片编辑和是否准确按钮,改变样式
				this.radiobakcolor=''
				this.editclick=1
				this.$refs["content"].textForbide = false
				this.isShowDelImgBtn=true
				this.submitForbide=false
				//如果要弹出筛选框
				//this.$refs.popup.open()
			},
			// 选择图片
			chooseImg() {
				//如果是之前确认过,但是没点击编辑按钮,则直接return
				if(this.addoredit=='edit'&&this.editclick==0){
					return
				}
				let that = this
				uni.chooseImage({
					// count:  允许上传的照片数量
					count: 3, //h5无法限制
					// sizeType:  original 原图,compressed 压缩图,默认二者都有
					sizeType: "original,compressed",
					success: function(res) { //选择成功,将图片存入本地临时路径,可删除,可查看,等待上传
						console.log(res, '选择成功')

						// 如果限制图片大小,则添加判断
						res.tempFiles.map(val => {
 
							// 判断本次上传限制的图片大小
							if (val.size > 10485760) {
								uni.showToast({
									icon: 'none',
									title: '上传的图片大小不超过10M'
								})
								return
							}
 
							// 判断本次最多上传多少照片
							that.imgNum++
							if(that.imgNum==3){
								that.isShowAddImgBtn=false
								uni.showToast({
									icon: 'none',
									title: '最多上传3张图片'
								})
							}
							if (that.imgNum > 3) {
								that.imgNum = 3
								uni.showToast({
									icon: 'none',
									title: '上传的图片最多不能超过3张'
								})
								return
							}
							//把临时路径添加进数组,渲染到页面
							that.preImgUrl.push(val.path) 
						})
					}
				})
			},
			// 确定上传图片,传到服务器
			submitImg() {
				let that = this
				let fault_comment = that.$refs["content"].content;
				if(that.radio1!=1&&that.radio1!=2){
					uni.showToast({
						icon: 'none',
						title: '请选择故障是否准确'
					})
					return
				}
				if(fault_comment==""){
					uni.showToast({
						icon: 'none',
						title: '请输入故障描述'
					})
					return
				}
				// 如果没有选择图片则返回,本次需求没有此规则,故注释掉
				/*
				if (that.preImgUrl.length == 0) {
					uni.showToast({
						icon: 'none',
						title: '请选择图片'
					})
					return
				}*/
				// 获取图片地址的数量,用于实现同步请求
				let len = that.preImgUrl.length
				// 创建变量,用来创建定时器,用于实现同步请求
				let timer = null 
				//阻塞阈值,因为使用定时器实现同步,所以需要防止无限循环,用于实现同步请求
				let threshold = 3000;
				
				// 遍历要上传的图片临时地址,进行上传
				that.preImgUrl.map(val => {
					uni.uploadFile({
						url: BASE_URL + "dist/faultConfirm_uploadFaultPic.action", //服务器地址
						filePath: val, //存在本地要上传的临时图片地址
						name: 'file', //名字可以随便写
						formData:{
							"edit_Id" : this.faultConfirmId,
							//故障ID,对应_FI表TYPE为0的线路故障ID
							"FAULT_ID": this.fault_id,
							//是否准确:0/1
							"CONFIRM_TYPE": that.radio1,
							//故障描述
							"CONFIRM_COMMENT": fault_comment,
							//故障核实状态:0/1
							"CONFIRM_STATUS": "1",
							//是编辑还是新增:add/edit
							"ADDOREDIT": this.addoredit
						},
						success(res) { //上传成功的回调函数
							//console.log(res, '上传图片成功')
							//字符串转json
							let myObj = JSON.parse(res.data)
							//将上传后的图片路径拼接起来,入库用
							that.afterUploadPath += myObj.actionResult.message+","
						},
						complete: (X)=>{
							// 不论success还是fail,都把len-1
							len--;
						},
						fail(res) {
							console.log(res, '上传失败')
							uni.showToast({
								icon: 'none',
								title: '上传失败'
							})
						}
					})
				})
				  // 关键部分
				  // 通过此部分来延续方法的时长,待到异步的回调执行完毕后则关闭
				  // 以防万一,建议设定一个阈值,防止出现意外导致无限循环
				  timer = setInterval(() => {
					  threshold--;
					 console.log(threshold);
					 // 只要len结束,将定时器清除
					  if(!len || threshold == 0 ) {	
						  console.log("同步结束")
						  // 上传操作完成,消除阻塞
						  clearInterval(timer)	
						  this.doSubmitFaultConfirm()
					  }
				  // 这个参数一定要写,如果不写在IOS端会报错导致同步失败
				  },1)	
			},
			doSubmitFaultConfirm(){
				//图片路径
				let imgpath = this.afterUploadPath
				//故障描述
				let fault_comment = this.$refs["content"].content
				//去掉最后一位逗号
				imgpath = imgpath.substring(0,imgpath.length-1)
				console.log("开始数据入库"+imgpath)
				submitFaultConfirm({
					"edit_Id" : this.faultConfirmId,
					//故障ID,对应_FI表TYPE为0的线路故障ID
					"FAULT_ID": this.fault_id,
					//是否准确:0/1
					"CONFIRM_TYPE": this.radio1,
					//故障描述
					"CONFIRM_COMMENT": fault_comment,
					//故障核实状态:0/1
					"CONFIRM_STATUS": "1",
					//是编辑还是新增:add/edit
					"ADDOREDIT": this.addoredit,
					"IMG_PATH": imgpath
				})
				.then((res) => {
					uni.showToast({
						icon: 'none',
						title: '故障核实提交成功'
					})
				})
			},
			//删除某张图片,从本地的临时路径图片中, 删除路径即可
			delImg(index) {
				this.imgNum--;
				this.preImgUrl.splice(index, 1)
				if(this.imgNum<3){
					this.isShowAddImgBtn=true;
				}
			},
			bindTextAreaBlur: function (e) {
				console.log(e.detail.value)
		    },
		    changeRadio1() {
				//如果是之前确认过,但是没点击编辑按钮,则直接return
				if(this.addoredit=='edit'&&this.editclick==0){
					return
				}
				this.radio1 = 1;
				console.log("是")
		    },
		    changeRadio2() {
				//如果是之前确认过,但是没点击编辑按钮,则直接return
				if(this.addoredit=='edit'&&this.editclick==0){
					return
				}
				this.radio1 = 2;
				console.log("否")
			},
			//点击小图查看大图片
			showImg(val, index) {
				console.log(val, '点击了')
				let that = this
				uni.previewImage({
					// 对选中的图片进行预览
					urls: that.preImgUrl, //图片数组  // urls:['','']  图片的地址 是数组形式
					current: index, //当前图片的下标
				})
			}
		}
	}
</script>
 
<style scoped lang="scss">
	* {
		box-sizing: border-box;
	}
 
	.qtpicker {
		width: 100%;
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		margin: 0 auto;
		padding: 10rpx 0;
 
		.preImgs {
			margin: 13rpx;
			position: relative;
 
			image {
				width: 200rpx;
				height: 200rpx;
			}
 
			.cuo {
				width: 17pt;
				height: 17pt;
				//line-height: 12px;
				//text-align: center;
				///* font-size: 16px; */
				//border-radius: 50%;
				//background-color: #223E4B;
				//color: #FFFFFF;
				position: absolute;
				right: 0px;
				top: 0px;
			}
		}
 
 
		.addImg {
			width: 200rpx;
			height: 200rpx;
			border: 2rpx solid #d4d4d4;
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: center;
			font-size: 20rpx;
			background-color: #e2dddd;
			margin-left: 13rpx;
			margin-top: 13rpx;
		}
	}
	.cell {
		height: auto;
		padding-left: 20rpx;
		padding-right: 20rpx;
		display: flex;
		flex-direction: column;
		justify-content: space-between;
		align-items: flex-start;
		background: #fff;
		//border-bottom: 1px solid #BDD8F6;
	
		.cell-left {
			display: flex;
			align-items: center;
			padding: 8px;
	
			.cell-icon {
				width: 50rpx;
				height: 50rpx;
			}
	
			.cell-text {
				color: #595959 ;
				font-size: 15px;
				margin-left: 20rpx;
				//width: 180rpx;
			}
		}
	
		.iconfont {
			font-size: 40rpx;
			color: #999;
		}
	}
	.radio-content {
	  height: 40rpx;
	  display: flex;
	  align-items: center;
	}
	.radio {
		width: 24rpx;
		height: 24rpx;
		border-radius: 50%;
		border: 1rpx solid #CCCCCC;
		display: flex;
		flex-direction: column; 
		align-items: center;
		justify-content: center;
		margin: 0rpx 26rpx 0rpx 15rpx;
	}
	.radio-active{
				width: 10rpx; 
				height: 10rpx;
				border-radius: 50%;
				background-color: #0079FC;
	}
	.radio-default{
				border: 2rpx solid #0079FC;
	}
	.radio-right {
				margin-left: 36rpx;
	}
</style>

多行文本输入框

<template>
	<view class="r-top">
		<textarea :disabled="textForbide" id="fault_comment" :class="textForbide ? 'textDisabled':''" style="border: 1px solid #ccc;" :maxlength="len" placeholder-style="color:rgba(153,153,153,1);" v-model="content" :placeholder="pl" />
		
		<view class="r-top-t">
		 	<view></view>
			<view> {{contentLength}}/{{len}}字</view>
		 </view>
	</view>
</template>

<script>
	export default {
		computed:{
			contentLength(){
				return this.content.length;
			}
		},
		props:{
			pl:{
				type: String,
				default: "请输入"
			},
			len:{
				type: Number,
				default: 200
			}
		},
		data() {
			return {
				content:'',
				textForbide:false
			}
		},
	}
</script>

<style>
.r-top{
	padding: 10rpx;
	background: #FFFFFF;
	border-radius: 20rpx;
}
.r-top-t{
	display: flex;
	justify-content: space-between;
	font-family:PingFang SC;
	font-weight:400;
	line-height:20px;
	color:rgba(153,153,153,1);
	opacity:1;
}
.textDisabled{
	border: 1px solid #ccc;
	background-color: #D9D9D9 ;
}
</style>

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值