Files
ys-app/src/pages/business/polling/optionDetail.vue
2025-12-02 11:44:38 +08:00

813 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="巡检详情"
:leftFlag="true" :rightFlag="true"
>
<template #right>
<!-- 状态是4-已完成 5-已过期的不能再问题上报了 -->
<block v-if="optionObj.taskStatus==5||optionObj.taskStatus==4"></block>
<view v-else class="head-right" @click="handleQuestion">
<view class="btn-yellow">
<img :src="'static/images/polling/icon-repair.png'" class="img-repair" />新建问题上报
</view>
</view>
</template>
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 下拉刷新 -->
<mescroll-uni ref="mescrollRef" @init="mescrollInit"
:down="downOption" @down="downCallback"
:fixed="false" class="scroll-h"
>
<!-- 1 单选 2多选 3判断 4 问答 5 读卡 6 扫码 7 拍照 8 视频 9 定位暂时去掉了 -->
<view class="white-bg">
<view class="blue-title">{{optionObj.groupName}}</view>
<view class="blue-sub-title" style="margin-bottom:20rpx;">
巡检日期<text>{{parseTime(optionObj.lastCheckTime,'{y}-{m}-{d} 星期{a}')}}</text>
</view>
<block v-if="optionObj.pointList">
<block v-if="optionObj.pointList.length>0">
<view class="report-list" v-for="(item, index) in optionObj.pointList" :key="index">
<!-- 单选 -->
<block v-if="item.pointType==1">
<view class="r-left" style="width:100%">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<multipleSelect :multiple="false" :value="item.chooseList" downInner
:options="item.optionList" @change="(...args) => changeCheck(...args, item)"
:slabel="'optionContent'" :svalue="'optionTitle'"
>
</multipleSelect>
</block>
<!-- 多选 -->
<block v-else-if="item.pointType==2">
<view class="r-left" style="width:100%">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<multipleSelect :multiple="true" :value="item.chooseList" downInner
:options="item.optionList" @change="(...args) => changeCheck(...args, item)"
:slabel="'optionContent'" :svalue="'optionTitle'"
>
</multipleSelect>
</block>
<!-- 判断 -->
<block v-if="item.pointType==3">
<view class="r-list">
<view class="r-left">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<view class="r-right">
<radio-group @change="radioChange($event,item)" style="transform:scale(0.9)">
<radio :value="item2.optionTitle" color="#02C74C"
v-for="(item2,index) in item.optionList" :key="index"
:style="{marginRight:index==0?'30rpx':'0'}"
:checked="item2.optionTitle === item.resultContent"
>
{{item2.optionContent}}
</radio>
</radio-group>
</view>
</view>
</block>
<!-- 问答 -->
<block v-else-if="item.pointType==4">
<view class="r-left" style="width:100%">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<view class="r-input">
<!-- <input class="uni-input" placeholder="请输入" placeholder-class="place-input" /> -->
<textarea class="textarea" v-model="item.resultContent" auto-height placeholder="请输入" placeholder-class="place-input"></textarea>
</view>
</block>
<!-- 读卡 -->
<block v-else-if="item.pointType==5">
<view class="r-list" >
<view class="r-left">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<!-- #ifdef APP-PLUS -->
<view class="r-right r-active" v-if="item.resultContent" @click.stop="initNFC(item,index)">
<img :src="'static/images/polling/icon-NFCcode-b.png'" class="img-nfc" />
<view>识别成功<view class="r-font">点击再次识别</view></view>
</view>
<view class="r-right" v-else @click.stop="initNFC(item,index)">
<img :src="'static/images/polling/icon-NFCcode.png'" class="img-nfc" /> 开始识别
</view>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<view class="r-right r-active">
<img :src="'static/images/polling/icon-NFCcode-b.png'" class="img-nfc" />
<view>识别成功</view>
</view>
<!-- #endif -->
</view>
</block>
<!-- 扫码 -->
<block v-else-if="item.pointType==6">
<view class="r-list">
<view class="r-left">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<!-- #ifdef APP-PLUS -->
<view class="r-right r-active" v-if="item.resultContent" @click="handleScan(item)">
<img :src="'static/images/polling/icon-QRcode-b.png'" class="img-nfc" />
<view>扫码成功<view class="r-font">点击再次扫码</view></view>
</view>
<view class="r-right" v-else @click="handleScan(item)">
<img :src="'static/images/polling/icon-QRcode.png'" class="img-nfc" /> 开始扫码
</view>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<view class="r-right r-active">
<img :src="'static/images/polling/icon-QRcode-b.png'" class="img-nfc" />
<view>扫码成功</view>
</view>
<!-- #endif -->
</view>
</block>
<!-- 拍照 -->
<block v-else-if="item.pointType==7">
<view class="r-left" style="width:100%">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<view class="img-flex">
<view class="img-show" v-for="(item2,index) in imgArr2" :key="index" @click="showMediaPreview(item2)">
<img :src="item2.shortUrl" />
</view>
<view class="img-con" @click="chooseImage(item)">
<img :src="'static/images/polling/icon-AddPic.png'" class="img-pic" />
<view>添加照片</view>
</view>
</view>
</block>
<!-- 视频 -->
<block v-else-if="item.pointType==8">
<view class="r-left" style="width:100%">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<view class="img-flex">
<view class="img-show" v-for="(item2,index) in videoArr2" :key="index" @click="showMediaPreview(item2)">
<video :src="item2.url" controls v-show="videoShow"></video>
</view>
<view class="img-con" @click="chooseVideo(item)">
<img :src="'static/images/polling/icon-AddVideo.png'" class="img-pic" />
<view>添加视频</view>
</view>
</view>
</block>
<view class="report-border" :style="{borderColor:index<optionObj.pointList.length-1?'#E7E7E7':'#fff'}"></view>
</view>
<!-- 状态是4-已完成 5-已过期的不能再修改了 -->
<block v-if="optionObj.taskStatus==5||optionObj.taskStatus==4"></block>
<view v-else class="btn-submit">
<button type="primary" @click="handleSubmit" :loading="submitLoading" >提交</button>
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
</view>
</mescroll-uni>
</view>
<!-- 弹窗提示 -->
<pollingShowModal :visible="visible"
:title="title"
:content="content"
:subContent="subContent"
:btnFlag2="btnFlag2"
:btnTxt="btnTxt"
:isGreen="isGreen"
:subTime="subTime"
@close="handleClose"
@confirm="handleConfirm"
ref="showModel"
></pollingShowModal>
<!-- 普通弹窗提示 -->
<customShowModal
:title="''"
:content="content3"
:visible="visible3"
:btnFlag2="false"
@confirm="handleClose3"
ref="showModel3"
></customShowModal>
<!-- 图片放大 -->
<mediaPreview :visible="isVisible"
:url="mediaUrl"
@close="handlePreviewClose"
></mediaPreview>
<!-- NFC 数据读取 -->
<NFCTemplate
v-if="nfcShow"
ref="nfcReaderRef"
@changeNfc="handleNfcData"
@close="nfcClose"
/>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive,getCurrentInstance } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import { MINIO_KEY } from '@/enums/cacheEnums';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import multipleSelect from "@/components/multipleSelect.vue";
import pollingShowModal from "@/components/pollingShowModal.vue";
import customShowModal from "@/components/customShowModal.vue"
import mediaPreview from "@/components/mediaPreview.vue"
import NFCTemplate from "@/components/NFCTemplate.vue"
import { parseTime } from '@/utils/datetime.js';
import { formatTaskStatus } from '@/utils/status.js';
import { taskGroupDetail,submitResult,minioUpload } from '@/api/polling.js'
import {compressImageUni} from '@/utils/common.js'
// import {uploadFileMinio} from '@/utils/minio.js'
const { proxy } = getCurrentInstance();
let taskId = ref(undefined);
let groupId = ref(undefined);
let minioObj = {};
onLoad(option => {
// console.log(option)
taskId.value = option.taskId;
groupId.value = option.groupId;
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
// console.log(minioObj)
})
// 下拉刷新
const mescrollRef = ref(null);
const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll;
};
const downOption = ref({
auto: true,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
// 下拉刷新
const downCallback = async (mescroll) => {
try {
getList();
} catch (error) {
mescroll.endErr();
} finally {
mescroll.endSuccess();
}
}
// 查询列表
let list = ref([]);
let optionObj = ref({})
// 获取数据列表
const getList = async() => {
let param = {
taskId:taskId.value,
groupId:groupId.value
}
// 1 单选 2多选 3判断 4问答 5 读卡 6 扫码 7 拍照 8 视频 9 定位
let res = await taskGroupDetail(param);
let data = res||{};
data.pointList.forEach(item=>{
if(item.resultContent){
if(item.pointType==2 ||item.pointType==1){
item['chooseList'] = item.resultContent.split(",") //JSON.parse(item.resultContent)
}else if(item.pointType==7){
imgArr.value=[];
imgArr2.value=[];
let imgList = item.resultContent?.split(",")||[];
imgList.forEach(imgUrl=>{
imgArr.value.push(imgUrl);
imgArr2.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+imgUrl,
url:minioObj.minioUrl +"/"+imgUrl,
})
})
}else if(item.pointType==8){
videoArr.value=[];
videoArr2.value=[]
let videoList = item.resultContent?.split(",")||[];
videoList.forEach(videoUrl=>{
videoArr.value.push(videoUrl);
videoArr2.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+videoUrl,
url:minioObj.minioUrl +"/"+videoUrl,
})
})
}
}
})
// console.log(data.pointList)
optionObj.value = data
}
// 跳转问题上报页面
const handleQuestion=()=>{
uni.navigateTo({
url: '/pages/business/polling/problemReport?taskId='+taskId.value+"&groupId="+groupId.value
});
}
// radio 判断选择
const radioChange=(e,item)=>{
// console.log("radioChange=>",e,item)
let radioValue = e.detail.value;
item['resultContent']=radioValue;
console.log("222判断radioChange=>",item)
}
// 单选多选处理
const changeCheck = (param,param2,item) => {
// console.log("111多选changeCheck=>",param,param2)
item['resultContent'] = param2.join(",");//JSON.stringify(param);
console.log("222多选changeCheck=>",item)
}
// 照片及拍照处理
const imgArr = ref([]);
const imgArr2=ref([]);
const chooseImage = (item) => {
console.log("item=>",item)
try {
uni.chooseImage({
// count: 1, // 默认是9这里设置为1次只选1张
// sizeType: ['compressed'], // 可以指定是原图还是压缩图,可选 'original' 或 'compressed'
sourceType: ['album', 'camera'], // 指定来源是相册还是相机,默认二者都有
// quality:80,
// width:'1920px',
// height:'1920px',
success: async (res) => {
// 选择成功,返回本地文件路径列表
let files = res.tempFilePaths;
// console.log('111图片临时路径:', files, res.tempFiles);
files.forEach(async file=>{
let compressImg = file;
// #ifdef APP-PLUS
// 压缩图片
compressImg = await compressImageUni(file);
// console.log("333压缩图片返回=>",compressImg)
// #endif
// 执行上传
let param = {
filePath: compressImg,
name: 'file',
formData: {
directory:'polling'
},
}
minioUpload(param).then(res=>{
let data = res.data;
// console.log("444图片上传成功=>",data)
// imgArr2.value.push(data.fileUrl)
imgArr2.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
})
imgArr.value.push(data.fileName)//传给后台的路径
item.resultContent = imgArr.value.join(",")
})
})
},
fail: (err) => {
console.log('选择图片失败', err);
}
});
} catch (error) {
console.log(error)
}
};
// 视频处理
const videoSrc = ref('');
const videoArr = ref([]);
const videoArr2 = ref([]);
const chooseVideo = (item) => {
uni.chooseVideo({
sourceType: ['album', 'camera'], // 来源:相册和相机
maxDuration: 60, // 最大时长(秒)
camera: 'back', // 使用后置摄像头
compressed: true, // 压缩视频
success: (res) => {
console.log("res=>",res)
// videoSrc.value=res.tempFilePath;
// videoArr.value.push(res.tempFilePath)
console.log('视频路径:', res.tempFilePath);
// console.log('视频时长:', res.duration);
// console.log('视频大小:', res.size);
// 执行上传
let param = {
filePath: res.tempFilePath,
name: 'file',
formData: {
directory:'polling'
},
}
minioUpload(param).then(uploadRes=>{
let data = uploadRes.data;
videoArr.value.push(data.fileName)//传给后台的路径
videoArr2.value.push(data.fileUrl)
item.resultContent = videoArr.value.join(",")
}).finally(()=>{
})
},
fail: (err) => {
console.error('选择视频失败:', err);
}
});
};
// 放大视频或图片
let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(item)=>{
// console.log("showMediaPreview===")
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = item.url
}
const handlePreviewClose=()=>{
isVisible.value = false;
videoShow.value = true;
}
// nfc 处理
let nfcShow = ref(false);
const nfcReaderRef = ref(null);
// optionObj.pointList
let nfcIndex = ref(0);
const initNFC = async(item,index) => {
nfcShow.value = true;
nfcIndex.value=index;
// uni.navigateTo({url:'/pages/business/polling/nfcTest/index'})
// #ifdef APP-PLUS
setTimeout(()=>{
if(nfcReaderRef.value){
nfcReaderRef.value.open();
}
},50)
// #endif
}
const nfcClose = async(item) => {
nfcShow.value = false;
}
const handleNfcData=(data)=>{
console.log("NFC数据:", data);
console.log("NFC数据1111:", optionObj.value.pointList,nfcIndex.value)
optionObj.value.pointList[nfcIndex.value].resultContent = data;
console.log("NFC数据1111:", optionObj.value.pointList[nfcIndex.value])
// nfcShow.value = false;
}
// 扫二维码
const scanStr = ref(undefined);
const handleScan = (item) => {
// 调用扫码API
uni.scanCode({
success: (res) => {
scanStr.value = res.result;
console.log('扫码结果:', res.result); // 二维码内容
console.log('码类型:', res.scanType); // 码类型,如 QR_CODE
item.resultContent = res.result
},
fail: (err) => {
console.error('扫码失败:', err);
}
});
}
// 提交
let submitParam = {}
let submitLoading = ref(false)
const handleSubmit=()=>{
submitLoading.value = true;
let list = optionObj.value.pointList;
let flag = false;
for (let i = 0; i < list.length; i++) {
const item = list[i];
if(!item.resultContent){
flag = true;
break;
}
}
// 如果有未完成的项提示
if(flag){
showModel('有未完成的巡检项');
}
// 循环处理给后台传值
let arr = [];
list.forEach(item => {
arr.push({
pointId:item.pointId,
resultContent:item.resultContent||'',
resultId: item.resultId || undefined
})
});
submitParam = {
taskId:taskId.value,
groupId:groupId.value,
resultList:arr
}
console.log(flag)
// 都答好了直接提交
if(!flag){
handleConfirm();
}
}
// 自定义弹窗
let title = ref('');
let content = ref('');
let visible = ref(false);
let subContent= ref('');
let subTime= ref(undefined);
let btnFlag2=ref(true)
let btnTxt=ref('')
let isGreen =ref(false);
const showModel=(str)=>{
visible.value = true;
content.value = str;
subContent.value='是否确认提交?';
btnTxt.value='提交'
btnFlag2.value=true;
isGreen.value = false;
subTime.value=undefined;
}
const showModel2=(str)=>{
content.value = str;
subContent.value='提交时间';
btnTxt.value='确定';
btnFlag2.value=false;
isGreen.value = true;
visible.value = true;
}
//关闭
const handleClose=()=>{
visible.value = false;
submitLoading.value = false;
}
// 执行提交
const handleConfirm=()=>{
try {
if(isGreen.value){
visible.value = false;
isGreen.value=false;
}else{
// 执行提交
submitResult(submitParam).then(res=>{
subTime.value = res;
visible.value = false;
// console.log("submitResult=>",visible.value)
showModel2('此巡检项已完成');
uni.navigateBack();
}).finally(() => {
submitLoading.value = false;
})
}
} catch (error) {
console.log("==========",error)
visible.value = false;
}
}
// 基础弹窗
const visible3 = ref(false);
const content3 = ref('');
const showModel3=(str)=>{
visible3.value = true;
content3.value = str;
}
const handleClose3=()=>{
visible3.value = false;
submitLoading.value = false;
}
onUnmounted(() => {
})
</script>
<style scoped>
.scroll-h{
/* #ifdef APP-PLUS */
height:calc(100vh - 78px) !important;
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 60px) !important;
/* #endif */
}
.head-right{}
.head-right .btn-yellow{
background-color: #FCC123;
color: #4C2D11;
font-size: 28rpx;
font-weight: bold;
border-radius: 10rpx;
padding:10rpx 25rpx;
display: flex;
align-items: center;
}
.head-right .btn-yellow .img-repair{
width: 30rpx;
height: 30rpx;
}
.week{
padding:0 40rpx;
color: #fff;
font-size:32rpx;
margin-bottom:20rpx;
}
.white-bg{
width: 680rpx;
padding: 15rpx 30rpx 40rpx 40rpx;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
}
.white-bg2{
/* padding:0 40rpx; */
margin-top:-30rpx;
}
.report-list{
position: relative;
}
.report-border{
border-bottom:1px solid #E7E7E7;
width:710rpx;
height: 1px;
margin:20rpx 0;
}
.report-list .r-list{
padding: 5rpx 0;
}
.report-list .r-left{
display: flex;
align-items: center;
width:70%;
font-size:28rpx;
}
.report-list .r-left view:first-child{
margin-right: 10rpx;
}
.report-list .r-right{
display: flex;
font-size:30rpx;
align-items: center;
justify-content: center;
}
.report-list .r-right.r-active{
color:#05A3F4
}
.report-list .r-active .r-font{
color:#919191;
font-size:18rpx;
text-align: center;
}
.report-list .r-input{
border:1px solid #E7E7E7;
border-radius: 10rpx;
width:620rpx;
padding:15rpx 25rpx;
margin:20rpx 0 30rpx;
}
.report-list .place-input{
color:#BFBFBF;
font-size:28rpx;
}
.report-list .textarea{
min-height: 50rpx;
}
.r-right :deep(.uni-radio-input){
width:36rpx;
height: 36rpx;
}
.r-right .uni-input{
width:100%;
}
.report-list :deep(.uni-select-dc){
margin:20rpx 0 30rpx;
}
.report-list :deep(.uni-select-dc .uni-select-dc-select){
color:#BFBFBF;
font-size:28rpx;
}
.img-nfc{
width:50rpx;
height: 40rpx;
margin-right:10rpx;
}
.report-list .img-flex{
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
}
.report-list .img-show img,
.report-list .img-show video{
width:210rpx;
height: 140rpx;
}
.report-list .img-show :deep(.uni-video-cover-play-button){
width:64rpx;
height: 64rpx;
line-height: 64rpx;
font-size:60rpx;
}
.report-list .img-con{
background-color: #EEEEEE;
width:210rpx;
height: 140rpx;
text-align: center;
color:#919191;
font-size: 24rpx;
}
.report-list .img-con .img-pic{
width:70rpx;
height: 50rpx;
margin-top:30rpx;
}
.report-list .img-show,
.report-list .img-con{
width: calc(100% / 3 - 10px); /* 减去一些间隙以避免溢出 */
margin:20rpx 20rpx 0 0;
}
.bg-border{
width:750rpx;
height:20rpx;
background-color: #F5F5F5;
margin-left:-40rpx;
margin-top:40rpx;
}
.report-list .r-list .r-name{
width:525rpx
}
.r-left .r-l-left{
width:280rpx;
}
.r-left .r-l-right{
}
.no-data{
padding:200rpx 0 200rpx;
}
.no-data .no-pic{
display: block;
width:440rpx;
height:210rpx;
margin:40rpx auto;
}
:deep(.mescroll-upwarp){
display: none !important;
}
.btn-submit{
width:400rpx;
margin:0 auto;
}
.btn-submit uni-button[type='primary']{
background-color: #05A3F4;
border-radius: 40rpx;
line-height: 2.2;
}
.btn-submit uni-button:after{
border-color:#05A3F4;
}
</style>