增加巡检内容

This commit is contained in:
xuli
2025-11-20 17:45:41 +08:00
parent a6cc5ff885
commit 003d2d0797
42 changed files with 5957 additions and 1405 deletions

0
.tgitconfig Normal file
View File

2648
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,8 @@
"@dcloudio/uni-ui": "^1.5.11",
"crypto-js": "^4.2.0",
"mescroll-uni": "^1.3.7",
"minio": "^8.0.6",
"minio-js": "^1.0.7",
"pinia": "2.0.20",
"uuid": "^11.1.0",
"vue": "3.4.21",

View File

@@ -0,0 +1,235 @@
<template>
<view class="month-calendar">
<view class="header">
<view class="arrow" @click="prevYear"></view>
<view class="year">
<picker @change="changeYear" :value="yearIndex" :range="yearList">
<view class="uni-input">{{yearList[yearIndex]}}</view>
</picker>
</view>
<view class="arrow" @click="nextYear"></view>
</view>
<view class="month-grid">
<view
v-for="month in months"
:key="month.number"
class="month-item"
:class="{ active: isActive(month.number) }"
@click="selectMonth(month.number)"
>
<view class="month-number">{{ month.number }}</view>
<view class="month-english">{{ month.english }}</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from "vue";
const props = defineProps({
year: {//默认当前年
default: new Date().getFullYear()
},
month:{//默认1月
default:1
}
})
// 年份列表
const yearIndex = ref(100);
const yearList = computed(() => {
let date = new Date();
let y = date.getFullYear();
let arr = [];
// 往前100年 往后61年
let j = 0;
for (let i = 0; i < 100; i++) {
let a = (y-100)+i;
arr.push(a);
j++;
}
for (let i = 0; i < 62; i++) {
let a = y+i
arr.push(a);
}
yearIndex.value = j;
// console.log(arr,yearIndex.value)
return arr;
});
const currentYear = ref(props.year);
const selectedMonth = ref(props.month);
// 月份英文处理
const months = ref([
{ number: 1, english: "Jan" },
{ number: 2, english: "Feb" },
{ number: 3, english: "Mar" },
{ number: 4, english: "Apr" },
{ number: 5, english: "May" },
{ number: 6, english: "Jun" },
{ number: 7, english: "Jul" },
{ number: 8, english: "Aug" },
{ number: 9, english: "Sep" },
{ number: 10, english: "Oct" },
{ number: 11, english: "Nov" },
{ number: 12, english: "Dec" },
]);
const isActive = (monthNumber) => {
return selectedMonth.value === monthNumber;
}
// 调用父组件方法
const emit = defineEmits(["change"]);
// 月份切换
const selectMonth = (monthNumber) => {
selectedMonth.value = monthNumber;
// console.log(`选择: ${currentYear.value}年${monthNumber}月`);
let m = monthNumber<10?'0'+monthNumber:monthNumber
// { year: currentYear.value, month: m }
// 触发事件
emit('change', {year:currentYear.value,month:monthNumber,ymStr:currentYear.value+'-'+m});
}
// 上一年
const prevYear = () => {
currentYear.value--;
yearIndex.value--;
getCurrentMonth();
}
// 下一年
const nextYear = () => {
currentYear.value++;
yearIndex.value++;
getCurrentMonth();
}
// 年份切换
const changeYear=(e)=>{
// console.log("年份切换=>",e)
yearIndex.value = e.detail.value;
currentYear.value = yearList.value[yearIndex.value];
getCurrentMonth();
}
// 获取当前月份
const getCurrentMonth = ()=>{
let date = new Date();
let year = date.getFullYear();
selectedMonth.value = 1;
if(year == currentYear.value){
selectedMonth.value = date.getMonth()+1;
}
}
</script>
<style scoped>
.month-calendar {
padding:0 20rpx;
background: #fff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding:0rpx 0 16rpx;
/* #ifdef APP-PLUS */
padding:20rpx 0;
/* #endif */
margin-bottom: 30rpx;
border-bottom:1px solid #E7E7E7;
}
.year {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.arrow {
font-size: 60rpx;
color: #CACACA;
padding: 0;
cursor: pointer;
}
.arrow:active {
color: #4A95FF;
}
.month-grid {
/* display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx; */
display: flex;
flex-flow:row wrap;
}
.month-item {
border-radius: 16rpx;
/* transition: all 0.3s ease; */
border: 1px solid #05A3F4;
background-color:#EAF8FF;
padding:0rpx 0 0;
/* #ifdef APP-PLUS */
padding:20rpx 0 0;
/* #endif */
width: calc(100% / 4 - 12px); /* 减去一些间隙以避免溢出 */
margin: 10rpx; /* 可选,用于添加间距 */
}
.month-item.active {
background: #FFF4EA;
border-color: #FF9638;
/* transform: scale(1.05); */
}
.month-item:active {
/* transform: scale(0.95); */
}
.month-number{
color:#05A3F4;
font-size:60rpx;
font-weight: bold;
margin-bottom: 10rpx;
text-align: center;
position: relative;
}
.month-number::after{
position: absolute;
content: "月";
right:5rpx;
bottom:15rpx;
color:#05A3F4;
font-size:22rpx;
font-weight: normal;
}
.month-english{
font-size: 26rpx;
color: #fff;
font-weight: bold;
background-color: #05A3F4;
border-radius: 0 0 14rpx 14rpx;
text-align: center;
}
.month-item.active .month-number,
.month-item.active .month-number::after{
color:#FF9638;
}
.month-item.active .month-english{
background-color: #FF9638;
}
</style>

View File

@@ -0,0 +1,161 @@
<template>
<view class="month-calendar">
<view class="header">
<view class="arrow" @click="prevYearMonth"></view>
<view class="year">
<picker mode="multiSelector" :value="dateIndex"
:range="dateRange" @change="handleChange"
@columnchange="onColumnChange"
>
<view class="uni-input">{{selectedYear}}-{{ selectedMonth.toString().padStart(2, '0') }}</view>
</picker>
</view>
<view class="arrow" @click="nextYearMonth"></view>
</view>
</view>
</template>
<script setup>
import { ref, computed,onMounted,reactive } from "vue";
const props = defineProps({
year: {//默认当前年
default: new Date().getFullYear()
},
month:{//默认1月
default:1
}
})
// 年份列表
// const yearIndex = ref(100);
const selectedYear = ref(props.year);
const selectedMonth = ref(props.month);
const dateIndex = ref([0, 0])
const dateRange = ref([[],[]])
// 初始化数据
onMounted(() => {
let date = new Date();
let y = date.getFullYear();
// 往前100年 往后61年
let j = 0;
for (let i = 0; i < 100; i++) {
let a = (y-100)+i;
dateRange.value[0].push(a);
j++;
}
for (let i = 0; i < 62; i++) {
let a = y+i
dateRange.value[0].push(a);
}
// 生成月份
for (let i = 1; i <= 12; i++) {
dateRange.value[1].push(i)// + '月'
}
// 设置默认选中
// const yearIndex = dateRange.years.findIndex(item => item === selectedYear.value)
const monthIndex = dateRange.value[1].findIndex(item => item === selectedMonth.value)
dateIndex.value = [j, monthIndex]
// console.log(dateRange.value[0],dateRange.value[1],dateIndex.value)
})
// 调用父组件方法
const emit = defineEmits(["change"]);
// 计算月份
const getYearMonth=(type)=>{
let y= selectedYear.value;
let m = selectedMonth.value;
let current_date = new Date(y+'-'+m)
let month=type==1? current_date.getMonth()-1:current_date.getMonth()+1;//上个月or下个月
current_date.setMonth(month);
selectedYear.value = current_date.getFullYear();
selectedMonth.value = current_date.getMonth()+1;
const yearIndex = dateRange.value[0].findIndex(item => item === selectedYear.value)
const monthIndex = dateRange.value[1].findIndex(item => item === selectedMonth.value)
dateIndex.value = [yearIndex, monthIndex]
let m2 = selectedMonth.value.toString().padStart(2, '0')
emit('change', {year:selectedYear.value,month:selectedMonth.value,ymStr:selectedYear.value+'-'+m2});
}
// 上个月
const prevYearMonth = () => {
getYearMonth(1)
}
// 下个月
const nextYearMonth = () => {
getYearMonth(2)
}
// 列选择
const onColumnChange = (e) => {
const { column, value } = e.detail;
// console.log(column,value)
if (column === 0) { // 年份列
dateIndex.value[0] = column
selectedYear.value = dateRange.value[column][value]
} else if (column === 1) { // 月份列
dateIndex.value[1] = column
selectedMonth.value = dateRange.value[column][value]
}
}
// 年月选择
const handleChange=(e)=>{
const values = e.detail.value
selectedYear.value = parseInt(dateRange.value[0][values[0]])
selectedMonth.value = parseInt(dateRange.value[1][values[1]])
// console.log('选择的日期:', `${selectedYear.value}-${selectedMonth.value.toString().padStart(2, '0')}`)
let m = selectedMonth.value.toString().padStart(2, '0')
emit('change', {year:selectedYear.value,month:selectedMonth.value,ymStr:selectedYear.value+'-'+m});
}
// 获取
const getNextMonth = (current_date)=>{
current_date.setMonth(current_date.getMonth() + 1);
}
</script>
<style scoped>
.month-calendar {
padding:0 20rpx;
background: #fff;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding:0rpx 0 16rpx;
/* #ifdef APP-PLUS */
padding:20rpx 0;
/* #endif */
}
.year {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.arrow {
font-size: 60rpx;
color: #CACACA;
padding: 0;
cursor: pointer;
}
.arrow:active {
color: #4A95FF;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<view :style="{
width: size + 'rpx',
height: size + 'rpx',
'--offset': offset
}"
v-html="svgHtml"
ref="svgRef"
>
</view>
<!-- <svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke="#7C7C7C" stroke-width="7" fill="none" />
<circle cx="50" cy="50" r="40" stroke="#10F6FC" stroke-width="7" fill="none" stroke-dasharray="251.2" stroke-dashoffset="251.2" class="progress-bar"/>
</svg> -->
</template>
<script setup>
import { ref, computed, watch } from "vue";
const props = defineProps({
size: {
type: Number,
default: 280,
},
color: {
type: String,
default: "#333333",
},
progress:{}
});
const svgRef = ref(null)
const offset = ref(251.2);
const svgHtml = ref(`<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke="#7C7C7C" stroke-width="7" fill="none" />
<circle cx="50" cy="50" r="40" stroke="#10F6FC" stroke-width="7" fill="none" stroke-dasharray="251.2" stroke-dashoffset="${offset.value}"/>
</svg>`);
watch(() => props.progress, (newVal, oldVal) => {
updateProgress(newVal)
},{deep:true});
// 更新进度函数
const updateProgress=(newProgress)=>{
offset.value = 251.2 - (251.2 * newProgress) / 100;
svgHtml.value = `<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke="#7C7C7C" stroke-width="7" fill="none" />
<circle cx="50" cy="50" r="40" stroke="#10F6FC" stroke-width="7" fill="none" stroke-dasharray="251.2" stroke-dashoffset="${offset.value}"/>
</svg>`
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,238 @@
<template>
<view class="model" v-if="visibleF">
<view class="model-con">
<view class="model-top" v-if="title">{{title}}</view>
<view class="img-title" :class="{'m-height':!title}" v-if="!isGreen">
<img :src="'static/images/polling/icon-Alert.png'"/>
</view>
<view class="img-title" :class="{'m-height':!title}" v-else>
<img :src="'static/images/polling/icon-success.png'"/>
</view>
<view class="model-middle">
<view :class="{'font-green':true,'font-orange':!isGreen}">{{contents}}</view>
<view>{{subContents}}</view>
<view v-if="subTimes">{{parseTime(subTimes,'{y}{m}{d} 星期{a} {h}:{i}')}}</view>
</view>
<view class="model-bottom">
<button type="default" class="btn-green" @click="handleConfirm" v-if="btnFlags" :loading="loading" :disabled="loading">{{btnTxts}}</button>
<button type="primary" class="btn-cancel" @click="handleCancel" v-if="btnFlags2">{{btnCancelTxts}}</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref,watch } from 'vue'
import { parseTime } from '@/utils/datetime.js';
const props = defineProps({
visible:{
type:Boolean
},
title:{//标题
type: String,
},
content: {//显示的内容
type: String
},
subContent:{//确认内容
type:String,
default:'是否确认提交?'
},
subTime:{//提交的时间
},
btnFlag:{//提交按钮是否显示
type:Boolean,
default:true
},
btnFlag2:{//暂不提交按钮是否显示
type:Boolean,
default:true
},
btnTxt:{//提交按钮文字
type:String,
default:'提交'
},
btnCancelTxt:{//暂不提交按钮文字
type:String,
default:'暂不提交'
},
isGreen:{//是否显示绿色
type:Boolean,
default:false
}
})
let visibleF = ref(props.visible)
let titles = ref(props.title)
let contents = ref(props.content);
let subContents = ref(props.subContent);
let subTimes = ref(props.subTime);
let btnFlags = ref(props.btnFlag);
let btnFlags2 = ref(props.btnFlag2)
let btnTxts = ref(props.btnTxt);
let btnCancelTxts = ref(props.btnCancelTxt);
let isGreens = ref(props.isGreen);
let loading = ref(false);
// 显示隐藏
watch(() => props.visible, (newVal, oldVal) => {
loading.value = false;
visibleF = newVal
},{
deep:true, // 深度监听
immediate:true // 立即执行
});
// 标题
watch(() => props.title, (newVal, oldVal) => {
titles.value = newVal
},{
deep:true,
immediate:true
});
// 内容
watch(() => props.content, (newVal, oldVal) => {
contents.value = newVal
},{
deep:true,
immediate:true
});
watch(() => props.subContent, (newVal, oldVal) => {
subContents.value = newVal
},{
deep:true,
immediate:true
});
watch(() => props.subTime, (newVal, oldVal) => {
subTimes.value = newVal
},{
deep:true,
immediate:true
});
// 按钮
watch(() => props.btnFlag, (newVal, oldVal) => {
btnFlags.value = newVal
},{
deep:true,
immediate:true
});
// 按钮
watch(() => props.btnFlag2, (newVal, oldVal) => {
btnFlags2.value = newVal
},{
deep:true,
immediate:true
});
watch(() => props.btnTxt, (newVal, oldVal) => {
btnTxts.value = newVal
},{
deep:true,
immediate:true
});
watch(() => props.btnCancelTxt, (newVal, oldVal) => {
btnCancelTxts.value = newVal
},{
deep:true,
immediate:true
});
// 调用父组件的方法
const emit = defineEmits(['close','confirm'])
const handleCancel = ()=>{
emit('close');
}
const handleConfirm = ()=>{
loading.value=true;
emit('confirm')
}
</script>
<style lang="scss" scoped>
.model {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 9999;
.model-con{
background: #fff;
position: fixed;
top:40%;
left:50%;
// width:620rpx;
width:580rpx;
min-height: 278rpx;
margin-left:-310rpx;
margin-top:-139rpx;
border-radius: 24rpx;
padding:20rpx 20rpx 30rpx;
}
.model-top{
text-align: center;
padding:20rpx 0;
font-size:36rpx;
}
.img-title{
text-align: center;
img{
width:120rpx;
height: 120rpx;
}
}
.model-middle{
text-align: center;
font-size:30rpx;
color:#333333;
margin-bottom:10rpx;
.font-green{
color:#00BF5A;
font-size: 36rpx;
font-weight: bold;
padding:20rpx 0;
}
.font-orange{
color:#FF9638;
font-size: 36rpx;
font-weight: bold;
padding:0rpx 0 20rpx;
}
}
.m-height{
padding:40rpx 0 20rpx;
}
.model-bottom{
width:380rpx;
margin-top:40rpx;
margin:40rpx auto;
.btn-green,.btn-cancel{
background-color: #fff;
color:#05A3F4;
border-radius: 48rpx;
font-size:36rpx;
text-align: center;
line-height: 2.2;
margin:10rpx 0 0 0 !important;
&::after{
border:none;
border-radius: 37rpx;
}
}
.btn-green{
border: 1px solid #05A3F4;
background-color:#05A3F4;
color:#fff;
margin:0rpx 0 0 20rpx !important;
}
}
}
</style>

View File

@@ -0,0 +1,235 @@
<template>
<view class="date-con" v-if="isShow">
<view class="date-bg">
<view class="date-title">
<uni-icons type="closeempty" size="20" @click="handleClose"></uni-icons>
<view class="choose-title">选择时间</view>
<view class="date-confirm" @click="handleConfirm">确定</view>
</view>
<!-- 最近多少天 '3days' | '7days' | '1month' | '3months'-->
<view class="range-list">
<view class="range-item" :class="{active:lastType=='3days'}" @click="handleChoose('3days')">近3天</view>
<view class="range-item" :class="{active:lastType=='7days'}" @click="handleChoose('7days')">近7天</view>
<view class="range-item" :class="{active:lastType=='1month'}" @click="handleChoose('1month')">近1月</view>
<view class="range-item" :class="{active:lastType=='3months'}" @click="handleChoose('3months')">近3月</view>
</view>
<!-- 时间段 -->
<view class="range-to">
<view class="to-bg" :class="{blue:dateType==1}" @click="handleChooseDate(1,beginDate)">{{beginDate}}</view>
<view class="to"></view>
<view class="to-bg" :class="{blue:dateType==2}" @click="handleChooseDate(2,endDate)">{{endDate}}</view>
</view>
<!-- 时间选择 -->
<picker-view class="picker-view" v-if="pickerVisible" :indicator-style="indicatorStyle"
:value="dateValueArr" @change="bindChange"
>
<picker-view-column>
<view class="item" v-for="(item,index) in years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item,index) in months" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item,index) in days" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</view>
</template>
<script setup>
import { ref,onMounted } from 'vue'
import { getDateRange } from '@/utils/datetime.js';
const props = defineProps({
isShow:false,
})
let years = ref([]);
let months= ref([]);
let days = ref([]);
let pickerVisible =ref(false)
let indicatorStyle=ref(`height: 88rpx`);
let dateValueArr = ref([])
// 默认最近3天
let lastType = ref('3days');
let dateType = ref('');
let beginDate = ref('');
let endDate = ref('');
// 初始化日期
const initDate=(date)=>{
for (let i = 1990; i <= date.getFullYear() + 61; i++) {
years.value.push(i)
}
for (let i = 1; i <= 12; i++) {
months.value.push(i.toString().padStart(2, '0'))
}
for (let i = 1; i <= 31; i++) {
days.value.push(i.toString().padStart(2, '0'))
}
}
// 调用父类方法
const emit = defineEmits(['close','confirm'])
const handleClose = ()=>{
emit('close');
}
const handleConfirm = ()=>{
emit('confirm',{startDate:beginDate.value,endDate:endDate.value});
}
// 选择最近
const handleChoose=(type)=>{
lastType.value = type;
let dateObj = getDateRange(type);
beginDate.value = dateObj.startDate;
endDate.value = dateObj.endDate;
pickerVisible.value = false;
dateType.value=""
}
// 选择开始或结束时间
const handleChooseDate=(type,dateStr)=>{
dateType.value = type;
pickerVisible.value = true;
lastType.value='';
// 回显picker
let strs = dateStr.split("-");
const yearIndex = years.value.findIndex(item => item == strs[0])
const monthIndex = months.value.findIndex(item => item == strs[1])
const dayIndex = days.value.findIndex(item => item == strs[2])
dateValueArr.value = [yearIndex, monthIndex, dayIndex];
// console.log(strs,dateValueArr.value)
}
// 切换日期
const bindChange= (e)=>{
const val = e.detail.value
let year = years.value[val[0]]
let month = months.value[val[1]]
let day = days.value[val[2]]
// console.log(year,month,day);
let dateStr = `${year}-${month}-${day}`;
if(dateType.value==1){//开始时间
beginDate.value = dateStr
}else{
endDate.value = dateStr
}
}
onMounted(() => {
let date = new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
initDate(date);
const yearIndex = years.value.findIndex(item => item == year)
const monthIndex = months.value.findIndex(item => item == month)
const dayIndex = days.value.findIndex(item => item == day)
dateValueArr.value = [yearIndex, monthIndex, dayIndex];
// 默认近3天
let dateObj = getDateRange('3days');
beginDate.value = dateObj.startDate;
endDate.value = dateObj.endDate
})
</script>
<style lang="scss" scoped>
.date-con {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 9999;
.date-bg{
width:670rpx;
border-radius: 18rpx 18rpx 0 0;
padding:20rpx 40rpx;
position: fixed;
bottom:0;
background-color: #fff;
min-height: 650rpx;
.date-title{
display: flex;
align-items: center;
.choose-title{
width:550rpx;
margin:0 auto;
text-align: center;
color:#333;
font-weight: bold;
font-size: 32rpx;
}
.date-confirm{
margin-left:auto;
color:#05A3F4;
font-size:28rpx;
}
}
.range-list{
display: flex;
align-items: center;
justify-content: center;
margin-top:30rpx;
.range-item{
width:150rpx;
border-radius: 10rpx;
border:1px solid #E8E8E8;
text-align: center;
color:#333333;
font-size:28rpx;
padding:10rpx 0;
margin-right:15rpx;
}
.range-item.active{
border:1px solid #05A3F4;
color:#05A3F4;
}
}
.range-to{
display: flex;
align-items: center;
justify-content: center;
margin-top:30rpx;
.to-bg{
border:1px solid #F5F5F5;
background-color: #F5F5F5;
width:275rpx;
border-radius: 28rpx;
text-align: center;
color:#919191;
font-size:28rpx;
padding:8rpx 0;
}
.to-bg.blue{
border:1px solid #05A3F4;
color:#05A3F4;
background-color:#fff;
}
.to{
color:#333333;
font-size:28rpx;
margin:0 30rpx;
}
}
.picker-view {
width: 670rpx;
height: 385rpx;
margin-top: 20rpx;
:deep(.item){
text-align: center;
line-height: 78rpx;
}
}
}
}
</style>

View File

@@ -22,7 +22,9 @@
},
/* */
"modules" : {
"Push" : {}
"Push" : {},
"Camera" : {},
"Barcode" : {}
},
/* */
"distribute" : {
@@ -57,10 +59,11 @@
"<uses-permission android:name=\"android.permission.BROADCAST_PACKAGE_INSTALL\" />",
"<uses-permission android:name=\"android.permission.BROADCAST_PACKAGE_REPLACED\" />",
"<uses-permission android:name=\"android.permission.RESTART_PACKAGES\" />",
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />"
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />",
"<uses-permission android:name=\"android.permission.NFC\"/>"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
"minSdkVersion" : 26,
"minSdkVersion" : 23,
"targetSdkVersion" : 35,
"icons" : {
"android" : {

View File

@@ -622,7 +622,25 @@
}
},
{
"path": "pages/business/polling/issueTracking",
"path": "pages/business/polling/taskDetail",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/optionDetail",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemDetail",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemList",
"style": {
"navigationBarTitleText": ""
}
@@ -632,6 +650,18 @@
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemReport",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemLog",
"style": {
"navigationBarTitleText": ""
}
}
],
"globalStyle": {

View File

@@ -22,7 +22,7 @@
<view class="white-bg">
<view class="blue-title">日常巡检</view>
<block v-if="list1.length>0">
<view class="report-list" v-for="(item, index) in list1" :key="index" @click="handleDetail(item)">
<view class="report-list" v-for="(item, index) in list1" :key="index" @click="handleDetail(item,1)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.taskName }}</view>
<view class="r-right">
@@ -70,9 +70,9 @@
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
<view class="green-title" style="margin-top:80rpx">临时巡检</view>
<view class="green-title" style="margin-top:60rpx">临时巡检</view>
<block v-if="list2.length>0">
<view class="report-list" v-for="(item, index) in list2" :key="index" @click="handleDetail(item)">
<view class="report-list" v-for="(item, index) in list2" :key="index" @click="handleDetail(item,1)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.taskName }}</view>
<view class="r-right">
@@ -124,7 +124,7 @@
<view class="white-bg white-bg2">
<view class="red-title">问题跟踪</view>
<block v-if="list3.length>0">
<view class="report-list" v-for="(item, index) in list3" :key="index" @click="handleDetail(item)">
<view class="report-list" v-for="(item, index) in list3" :key="index" @click="handleDetail(item,2)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.problemDesc }}</view>
<view class="r-right">
@@ -359,9 +359,17 @@ const getList = (pageIndex, pageSize) => {
}
// 查看详情
const handleDetail = (item) =>{
// 查看详情 type 1-任务详情 2-问题详情
const handleDetail = (item,type) =>{
let url=''
if(type==1){
url = '/pages/business/polling/taskDetail?id='+item.taskId;
}else{
url = '/pages/business/polling/problemDetail?id='+item.taskId;
}
uni.navigateTo({
url
});
}
// 跳转webview

View File

@@ -1,13 +0,0 @@
<template>
<view>
</view>
</template>
<script setup>
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,603 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="巡检详情"
:leftFlag="true" :rightFlag="true"
>
<template #right>
<view 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>
<!-- 1 单选 2多选 3判断 4 问答 5 读卡 6 扫码 7 拍照 8 视频 9 定位暂时去掉了 -->
<view class="white-bg">
<view class="blue-title">{{optionObj.optionName}}</view>
<view class="blue-sub-title" style="margin-bottom:20rpx;">
巡检日期<text>{{parseTime(optionObj.planTime,'{y}-{m}-{d} 星期{a}')}}</text>
</view>
<view class="report-list" v-for="(item, index) in optionObj.list" :key="index">
<!-- 单选 or 判断 -->
<block v-if="item.pointType==1||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" style="transform:scale(0.9)">
<radio value="1" color="#02C74C" style="margin-right:30rpx;" ></radio>
<radio value="2" color="#02C74C"></radio>
</radio-group>
</view>
</view>
</block>
<!-- 多选 -->
<block v-else-if="item.pointType==2">
<view class="r-left">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<multipleSelect :multiple="true" :value="chooseList" downInner
:options="checkList" @change="changeCheck"
:slabel="'text'"
>
</multipleSelect>
</block>
<!-- 问答 -->
<block v-else-if="item.pointType==4">
<view class="r-left">
<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.result" 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" @click="initNFC">
<img :src="'static/images/polling/icon-NFCcode.png'" class="img-nfc" /> 开始识别
</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="scanStr" @click="handleScan">
<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">
<img :src="'static/images/polling/icon-QRcode.png'" class="img-nfc" /> 开始扫码
</view>
<!-- #endif -->
</view>
</block>
<!-- 拍照 -->
<block v-else-if="item.pointType==7">
<view class="r-left">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<view class="img-flex">
<view class="img-show" v-for="(item,index) in imgArr" :key="index">
<img :src="item" />
</view>
<view class="img-con" @click="chooseImage">
<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">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<view class="img-flex">
<!-- v-for="(item,index) in videoArr" :key="index" -->
<view class="img-show" v-for="(item,index) in videoArr" :key="index">
<!-- @error="videoErrorCallback"
:danmu-list="danmuList" -->
<!-- <video id="myVideo" :src="videoArr"
enable-danmu danmu-btn controls
></video> -->
<video :src="item" controls></video>
</view>
<view class="img-con" @click="chooseVideo">
<img :src="'static/images/polling/icon-AddVideo.png'" class="img-pic" />
<view>添加视频</view>
</view>
</view>
</block>
<view class="report-border" :style="{borderColor:index<optionObj.list.length-1?'#E7E7E7':'#fff'}"></view>
</view>
<view class="btn-submit">
<button type="primary" @click="handleSubmit">提交</button>
</view>
</view>
</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>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
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 { parseTime } from '@/utils/datetime.js';
import { formatTaskStatus } from '@/utils/status.js';
import { noticeList } from '@/api/notice.js'
import {uploadFileMinio} from '@/utils/minio.js'
let workId = ref(undefined);
let checkList=[
{ value: 0, text: "答案1" },
{ value: 1, text: "答案2" },
{ value: 2, text: "答案3" },
{ value: 3, text: "答案4" },
{ value: 4, text: "答案5" },
{ value: 4, text: "答案6" }
];
onLoad(option => {
// console.log(option)
workId.value = option.id;
getList();
})
// 查询列表
let list = ref([]);
let optionObj = ref({})
// 获取数据列表
const getList = () => {
// let res = await noticeList();
// 1 单选 2多选 3判断 4问答 5 读卡 6 扫码 7 拍照 8 视频 9 定位
let res = {
"code": 200,
"msg": "操作成功",
"data": {
optionName:'配电箱箱体和内部线路检查',
planTime:new Date().getTime(),
list:[
{ pointName:'技术中心机房总电源', optionId:202512297899, pointType:1, },
{ pointName:'监控室消防设备阀门正常开启闭合', optionId:202512297899, pointType:2, },
{ pointName:'监控室10组灭火器压力指针处于绿色区域', optionId:202512297899, pointType:3,},
{ pointName:'库房灭火器压力指针处于绿色区', optionId:202512297899,pointType:4,},
{ pointName:'技术中心机房总电源', optionId:202512297899,pointType:5,},
{ pointName:'监控室消防设备阀门正常监控室消防设备阀门正常监控室消防设备阀门正常', optionId:202512297899,pointType:6,},
{ pointName:'库房灭火器压力指针处于绿色区', optionId:202512297899,pointType:7,},
{ pointName:'技术中心机房总电源', optionId:202512297899,pointType:8,},
],
}
}
optionObj.value = res.data||{};
}
// 跳转问题上报页面
const handleQuestion=()=>{
uni.navigateTo({
url: '/pages/business/polling/problemReport?id='+workId.value
});
}
// radio 单选选择
const radioChange=(e)=>{
console.log(e)
}
// 多选处理
let chooseList = reactive([0]);
const changeCheck = (item, value) => {
chooseList = item;
}
// 照片及拍照处理
const imgArr = ref([]);
const imgArr2=ref([]);
const chooseImage = () => {
imgArr.value = [];
uni.chooseImage({
// count: 1, // 默认是9这里设置为1次只选1张
sizeType: ['compressed'], // 可以指定是原图还是压缩图,可选 'original' 或 'compressed'
sourceType: ['album', 'camera'], // 指定来源是相册还是相机,默认二者都有
success: async (res) => {
// 选择成功,返回本地文件路径列表
let files = res.tempFilePaths;
// console.log('图片临时路径:', files, res.tempFiles);
// 显示本地图片
imgArr.value.push(...res.tempFilePaths)
// 执行上传
uploadFileMinio(res.tempFiles,'/polling',files).then(res=>{
console.log("上传成功后图片路径====>",res)
imgArr2.value = res;
})
},
fail: (err) => {
console.log('选择图片失败', err);
}
});
};
// 视频处理
const videoSrc = ref('');
const videoArr = ref([]);
const chooseVideo = () => {
uni.chooseVideo({
sourceType: ['album', 'camera'], // 来源:相册和相机
maxDuration: 60, // 最大时长(秒)
camera: 'back', // 使用后置摄像头
compressed: true, // 压缩视频
success: (res) => {
videoSrc.value=res.tempFilePath;
videoArr.value.push(res.tempFilePath)
console.log('视频路径:', res.tempFilePath);
console.log('视频时长:', res.duration);
console.log('视频大小:', res.size);
},
fail: (err) => {
console.error('选择视频失败:', err);
}
});
};
// nfc 处理
let discoveryCallback = null
const initNFC = () => {
// 确保在 App 环境
if (typeof plus === 'undefined') {
showModel('此功能仅支持 App 端')
return
}
// 检查设备是否支持NFC
if (!plus.nfc) {
showModel('设备不支持 NFC')
return;
}
if (!plus.nfc.isSupportNFC()) {
showModel('设备不支持 NFC')
return;
}
// 检查NFC是否开启
plus.nfc.isNFCEnabled((enabled) => {
if (enabled) {
startDiscovery()
} else {
showModel('请先开启设备NFC功能')
}
},(error) => {
console.error('检查NFC状态失败:'+error.message);
showModel('检查NFC状态失败')
})
}
// 执行NFC扫描
const startDiscovery = () => {
// 开始监听NFC标签
plus.nfc.startDiscovery((res) => {
console.log('NFC监听启动成功')
// 监听NFC标签被发现的事件
discoveryCallback = (result) => {
try {
console.log("NFC监听返回=>",result)
// 尝试读取标签ID或NDEF数据
const tag = result.tag
if (tag && tag.id) {
// 将标签ID字节数组转换为十六进制字符串
const tagId = tag.id.map(b => b.toString(16).padStart(2, '0')).join(':')
console.log(`检测到标签ID: ${tagId}`)
// 此处可以处理tag.ndefMessage等数据
} else {
console.log( `检测到标签,但无法解析数据`)
}
} catch (error) {
console.log( '处理NFC数据时出错' + error.message)
}
}
plus.nfc.addEventListener('discovered', discoveryCallback, false)
},(err) => {
console.log( 'NFC监听启动失败' + err.message)
})
}
// 扫二维码
const scanStr = ref(undefined);
const handleScan = () => {
// 调用扫码API
uni.scanCode({
success: (res) => {
scanStr.value = res.result;
console.log('扫码结果:', res.result); // 二维码内容
console.log('码类型:', res.scanType); // 码类型,如 QR_CODE
// 处理扫码结果,例如跳转页面
// 如果是URL可以跳转
// if (res.result.startsWith('http')) {
// uni.navigateTo({
// url: '/pages/webview/webview?url=' + encodeURIComponent(res.result)
// });
// } else {
// // 其他内容,如文本,可以展示或处理
// uni.showModal({
// content: `扫描到内容:${res.result}`,
// showCancel: false
// });
// }
},
fail: (err) => {
console.error('扫码失败:', err);
}
});
}
// 提交
const handleSubmit=()=>{
showModel('有未完成的巡检项');
// showModel2('此巡检项已完成');
}
// 自定义弹窗
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)=>{
visible.value = true;
content.value = str;
subContent.value='提交时间';
btnTxt.value='确定';
btnFlag2.value=false;
isGreen.value = true;
subTime.value = new Date().getTime();
}
//关闭
const handleClose=()=>{
visible.value = false;
}
// 执行提交
const handleConfirm=()=>{
if(isGreen.value){
visible.value = false;
}
}
// 组件卸载时停止监听
onUnmounted(() => {
// #ifdef APP-PLUS
if (discoveryCallback) {
plus.nfc.removeEventListener('discovered', discoveryCallback)
}
plus.nfc.stopDiscovery?.()
// #endif
})
</script>
<style scoped>
.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 .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>

View File

@@ -0,0 +1,323 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="问题跟踪"
:leftFlag="true" :rightFlag="true"
>
<template #right>
<view class="head-right" @click="handleQuestion">
<view class="btn-yellow">
<uni-icons type="plus" size="20" color="#4C2D11"></uni-icons> 新增跟踪信息
</view>
</view>
</template>
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<view class="white-bg">
<view class="red-title">问题{{problemObj.groupName}}</view>
<view class="report-list">
<view class="report-pro">问题项</view>
<view class="report-right">
<view class="r-list" v-for="(item, index) in problemObj.list" :key="index">
<view class="r-left" :class="{'r-red':item.active}">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item }}</view>
</view>
<view class="r-right">
<view class="r-r-round" :class="{active:item.active}"></view>
</view>
</view>
</view>
</view>
<view class="report-border"></view>
<view class="report-list">
<view style="width:45%;display:flex;">
<view class="report-pro report-w" style="margin-right:20rpx;">提交人</view>
<view class="report-right report-w">{{problemObj.realname}}</view>
</view>
<view style="width:55%;display:flex;">
<view class="report-pro report-w" style="margin-right:20rpx;">提交时间</view>
<view class="report-right report-w">{{parseTime(problemObj.planTime,'{y}-{m}-{d} {h}:{i}')}}</view>
</view>
</view>
<view class="report-border"></view>
<view class="report-list">问题描述</view>
<view class="report-list report-gray">{{problemObj.problemDesc}}</view>
<view class="report-border"></view>
<view class="report-list">问题点位照片或视频</view>
<view class="report-list">
<block v-for="(item,index) in problemObj.listFile" :key="index">
<view class="img-flex">
<view class="img-show" v-if="getFileType(item)=='image'">
<img :src="item" />
</view>
<view class="img-show" v-else-if="getFileType(item)=='video'">
<video :src="item" controls></video>
</view>
</view>
</block>
</view>
<!-- 当天提的 增加按钮 修改和删除 -->
<view class="btn-con" v-if="isToday">
<button type="primary" @click="handleUpdate" size="mini" style="margin-right:50rpx;" class="btn-primary">修改</button>
<button type="primary" @click="handleDelete" size="mini">删除</button>
</view>
<!-- 追踪列表 -->
<view class="bg-border"></view>
<view v-for="(item,index) in problemObj.logList" :key="index" class="log-con">
<view class="blue-title">
{{parseTime(item.createTime,'{y}-{m}-{d} {h}:{i}')}}
<text style="margin-left:20rpx;">巡检人{{item.modifyUserName}}</text>
</view>
<view class="report-list">检查点跟踪情况描述</view>
<view class="report-list report-gray">{{item.logDesc}}</view>
<view class="report-border"></view>
<view class="report-list">检查点位照片或视频</view>
<view class="report-list">
<view class="img-flex">
<block v-for="(item,index) in item.listFile" :key="index">
<view class="img-show" v-if="getFileType(item)=='image'">
<img :src="item" />
</view>
<view class="img-show" v-else-if="getFileType(item)=='video'">
<video :src="item" controls></video>
</view>
</block>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import { parseTime } from '@/utils/datetime.js';
import {getFileType} from '@/utils/common.js'
let id=ref('');
let problemObj = ref({});
let isToday = ref(false)
onLoad(option => {
// console.log(option)
id.value = option.id;
getList();
})
// 获取数据列表
const getList = () => {
// let res = await noticeList();
let res = {
"code": 200,
"msg": "操作成功",
"data": {
groupName:'配电箱箱体和内部线路检查',
problemDesc:'电箱右侧箱体有锈蚀,需要及时修补;电箱前门关闭不严,缝隙过大;箱门密封条出现老化痕迹;显示屏出现若干像素坏点。',
planTime:new Date().getTime(),
realname:'张三',
pointName:'技术中心机房总电源,监控室消防设备阀门正常开启闭合,监控室10组灭火器压力指针处于绿色区域',
problemVedio:'http://192.168.236.196:9000/718ys-test/polling/rotate_2.jpg,http://192.168.236.196:9000/718ys-test/polling/mov_bbb.mp4',
logList:[
{
logId:1,logDesc:'配电箱箱体和内部线路检查',createTime:new Date().getTime(),modifyUserName:'张三',
logVedio:'http://192.168.236.196:9000/718ys-test/polling/mov_bbb.mp4,http://192.168.236.196:9000/718ys-test/polling/rotate_2.jpg,http://192.168.236.196:9000/718ys-test/polling/mov_bbb.mp4,http://192.168.236.196:9000/718ys-test/polling/rotate_2.jpg,http://192.168.236.196:9000/718ys-test/polling/mov_bbb.mp4,http://192.168.236.196:9000/718ys-test/polling/rotate_2.jpg',
}
]
}
}
let data = res.data || {};
data.list = data.pointName.split(",") || [];
data.listFile=data.problemVedio.split(",") || [];
data.logList.forEach(item => {
item.listFile = item.logVedio.split(",")||[]
});
problemObj.value = data;
}
// 跳转问题上报页面
const handleQuestion=()=>{
uni.navigateTo({
url: '/pages/business/polling/problemLog'
});
}
// 修改
const handleUpdate=()=>{
uni.navigateTo({
url: '/pages/business/polling/problemReport?id='+id.value
});
}
// 删除
const handleDelete=()=>{
}
</script>
<style lang="scss" scoped>
.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;
}
.white-bg{
width: 680rpx;
padding: 15rpx 30rpx 10rpx 40rpx;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
}
.white-bg .red-title{
margin-bottom:30rpx;
color:#FF2B44;
font-size:38rpx;
}
.report-border{
border-bottom:1px solid #E7E7E7;
width:710rpx;
height: 1px;
margin:30rpx 0;
}
.report-list{
display: flex;
font-size:28rpx;
}
.report-list.report-gray{
color:#919191;
padding:20rpx 0 0;
}
.report-list .r-title{
position: relative;
}
.report-list text{
color:#FF2B44;
font-size:28rpx;
}
.report-list .report-pro{
font-size:28rpx;
width:16%;
/* margin-right:30rpx; */
}
.report-list .report-right{
width:84%;
color:#919191;
font-size:28rpx;
}
.report-list .report-w{
width:auto;
}
.report-list .r-list{
padding: 10rpx 0;
}
.report-list .r-list:first-child{
padding-top:0;
}
.report-list .r-left{
display: flex;
/* align-items: center; */
font-size:28rpx;
color:#919191;
}
.report-list .r-left view:first-child{
margin-right: 10rpx;
}
.report-list .r-left.r-red{
color: #FF2B44;
font-size: 28rpx;
}
.report-list .r-right{
display: flex;
font-size:30rpx;
align-items: center;
justify-content: center;
}
.report-list .img-flex{
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
}
.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:76rpx;
height: 76rpx;
margin-top:30rpx;
}
.report-list .img-show,
.report-list .img-con{
width: calc(100% / 3 - 10px); /* 减去一些间隙以避免溢出 */
margin:20rpx 20rpx 0 0;
}
.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;
}
.btn-con{
text-align: center;
padding-top:50rpx;
}
uni-button[type='primary']{
background-color: #05A3F4;
border-radius: 40rpx;
line-height: 2.5;
padding:0 80rpx;
}
uni-button:after{
display: none;
}
.bg-border{
width:750rpx;
height:20rpx;
background-color: #F5F5F5;
margin-left:-40rpx;
margin-top:40rpx;
}
.log-con{
padding-top:10rpx;
}
.log-con .blue-title{
font-size:32rpx;
}
</style>

View File

@@ -0,0 +1,378 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" :title="!searchShow?'巡检问题跟踪':'搜索'"
:leftFlag="true"
:rightFlag="false"
@back="handleBack" :searchType="searchShow?1:undefined"
></customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 搜索处理 -->
<customSearch v-if="searchShow"
:searchKeywords="searchText"
:searchType="searchTypeObj"
:checkTypeObj="notictTypeCheck"
:searchTypeList="noticeTypeList"
@confirm="handleSearchConfirm"
></customSearch>
<view class="search" v-else @click="handleSearchFocus">
<view class="search-bg">
<view class="search-left">{{ notictTypeCheck.name?notictTypeCheck.name:'全部' }}</view>
<view class="search-right">
<uni-icons type="search" size="20" color="#ffffff"></uni-icons>
<input class="uni-input" v-model="searchText" placeholder="搜索" placeholder-class="search-color" />
</view>
</view>
</view>
<!-- 列表 -->
<mescroll-uni v-if="!searchShow" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"
:up="upOption" :down="downOption" :fixed="false" class="scroll-h" :class="{'loading-scroll':cssFlag}">
<view class="white-bg" :class="{'bg-height':list.length<8}">
<block v-if="list.length>0">
<view class="report-list" v-for="(item, index) in list" :key="index" @click="handleDetail(item,2)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.problemDesc }}</view>
<view class="r-right">
<!-- 问题状态 1=追踪2=关闭 -->
<view v-if="item.problemStatus==1" class="btn-red">进行中</view>
<view v-if="item.problemStatus==2" class="btn-green">已解决</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">跟踪次数<span class="r-gray">{{ item.count }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{parseTime(item.modifyTime,'{m}-{d} {h}:{i}') }}</span></view>
</view>
</view>
<view class="report-border" v-if="index<list.length-1"></view>
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
</view>
</mescroll-uni>
</view>
</view>
</template>
<script setup>
import { ref, onMounted,computed } from 'vue'
import { onLoad,onHide } from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue'
import customSearch from '@/components/customSearch.vue'
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import { getNavBarPaddingTop } from '@/utils/system.js'
import { noticeList } from '@/api/notice.js'
import { parseTime } from '@/utils/datetime.js'
onLoad(async(opt) => {
uni.setStorageSync('page_cache',true);
})
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
})
// 搜索处理
let searchShow = ref(false);
let searchText = ref(undefined);
let searchTypeObj = ref({typeId:3,typeName:'巡检类型'});
let noticeTypeList=ref([
{id:1,name:'日常巡检'},
{id:2,name:'临时巡检'},
]);
let notictTypeCheck = ref({});//选中类型
onHide(()=>{
searchShow.value=false;
})
// 搜索返回操作
const handleBack=()=>{
searchShow.value=false;
}
// 获取input 焦点跳转
const handleSearchFocus=()=>{
searchShow.value = true;
}
// 搜索完返回处理
const handleSearchConfirm = (param1,param2)=>{
// console.log(param1,param2)
notictTypeCheck.value=param1.value;
searchText.value=param2.value;;
searchShow.value=false;
}
// 清除未读
const handleRead = () => {
list.value.forEach(item => {
item.isRead = false;
})
}
// 查询通知列表
let list = ref([]);
const mescrollRef = ref(null);
const upOption = ref({
page: { num: 0, size: 10 },
noMoreSize: 5,
empty: {
tip: '~ 空空如也 ~',
icon: "../../../static/images/mescroll-empty.png"
},
textLoading: '加载中...',
textNoMore: '已经到底了'
});
const downOption = ref({
auto: false,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
let cssFlag=ref(false);//控制样式
const mescrollInit = (mescroll) => {
cssFlag.value = true;
mescrollRef.value = mescroll;
};
// 下拉刷新
const downCallback = async (mescroll) => {
try {
console.log("下拉刷新")
const res = await getNoticeList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {
setTimeout(async ()=>{
mescroll.endSuccess();
},500);
}
}
// 上拉加载更多
const upCallback = async (mescroll) => {
try {
console.log("上拉加载更多")
let res = await getNoticeList(mescroll.num, mescroll.size);
if (mescroll.num === 1) {
list.value = res.list;
} else {
list.value.push(...res.list);
}
mescroll.endBySize(res.list.length, res.total);
} catch (error) {
mescroll.endErr();
}
}
// 获取数据列表
const getNoticeList = (pageIndex, pageSize) => {
return new Promise(async (resolve) => {
let param = {
pageIndex,
pageSize,
searchValue:searchText.value?searchText.value:undefined,
taskType:notictTypeCheck.value.id
}
// let res = await noticeList(param);
let res={
"code": 200,
"msg": "操作成功",
"data": {
list:[
{
problemDesc:'西区地下车库入口防汛物资摆放',
problemId:202512297899,
problemStatus:1,
modifyTime:new Date().getTime(),
count:0,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
},
{
problemDesc:'西区地下车库入口防汛物资摆放',
problemId:202512297899,
problemStatus:1,
modifyTime:new Date().getTime(),
count:0,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
},{
problemDesc:'西区地下车库入口防汛物资摆放',
problemId:202512297899,
problemStatus:1,
modifyTime:new Date().getTime(),
count:0,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
}
],
recordCount:2
}
}
let list = res.data.list || [];
resolve({
list,
total: res.recordCount || 0
});
});
}
// 查看详情
const handleDetail = (item,type) =>{
let url= '/pages/business/polling/problemDetail?id='+item.taskId;
uni.navigateTo({
url
});
}
</script>
<style scoped>
.all-body{
position: absolute;
/* #ifdef APP-PLUS */
top:150rpx;
height: calc(100vh - 75px);
/* #endif */
/* #ifndef APP-PLUS */
top:120rpx;
height: calc(100vh - 64px);
/* #endif */
overflow: hidden;
}
:deep(.mescroll-downwarp .downwarp-progress){
border-color:#fff !important;
}
:deep(.mescroll-downwarp .downwarp-tip){
color:#fff;
}
.white-bg {
width: 670rpx;
padding: 30rpx 40rpx 40rpx;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
position: relative;
}
.white-bg.bg-height{
/* #ifdef APP-PLUS */
height: calc(100vh - 150px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 140px);
/* #endif */
}
.scroll-h{
/* #ifdef APP-PLUS */
height: calc(100vh - 130px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 105px);
/* #endif */
}
.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-list .r-left{
display: flex;
}
.report-list .r-list .r-gray{
margin-left:10rpx;
}
.report-list .r-list .r-blue{
margin-left:10rpx;
font-weight: bold;
}
.report-list .r-list .r-red{
font-weight: bold;
}
.report-list .r-list .r-name{
width:525rpx
}
.r-left .r-l-left{
width:280rpx;
}
.r-left .r-l-right{
}
.no-data .no-pic{
display: block;
width:440rpx;
height:210rpx;
margin:40rpx auto;
}
:deep(.mescroll-upwarp){
display: none !important;
}
</style>

View File

@@ -0,0 +1,280 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="新增跟踪信息"
:leftFlag="true" :rightFlag="false"
></customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<view class="white-bg">
<view class="red-title">问题{{optionObj.groupName}}</view>
<view class="report-list">
<view class="report-pro">问题项</view>
<view class="report-right">
<view class="r-list" v-for="(item, index) in optionObj.list" :key="index">
<view class="r-left">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item }}</view>
</view>
</view>
</view>
</view>
<view class="report-border"></view>
<view class="report-list">
<view class="report-pro">巡检人</view>
<view class="report-right">{{realname}}</view>
</view>
<view class="report-border"></view>
<view class="report-list" style="display:block;">
检查点跟踪情况描述
<textarea class="r-input textarea" v-model="desc" auto-height
placeholder="请输入跟踪情况描述" placeholder-class="place-input"
></textarea>
</view>
<view class="report-border"></view>
<view class="report-list" style="display:block;">
<view class="r-title">问题点位照片或视频 <text>*</text></view>
<view class="img-flex">
<view class="img-show" v-for="(item,index) in imgArr" :key="index">
<img :src="item" />
</view>
<view class="img-show" v-for="(item,index) in videoArr" :key="index">
<video :src="item" controls></video>
</view>
<view class="img-con" @click="chooseMedia">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
</view>
</view>
<view class="btn-submit">
<button type="primary" @click="handleSubmit">提交</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import { getUserInfo } from '@/api/auth.js'
let id = ref('');
let realname = ref('');
let desc = ref('');//描述
onLoad(async option => {
// console.log(option)
id.value = option.id;
let userinfo = await getUserInfo({});
realname.value = userinfo.realname
getList();
})
// 查询列表
let list = ref([]);
let optionObj = ref({})
// 获取数据列表
const getList = () => {
// let res = await noticeList();
// 1 单选 2多选 3判断 4问答 5 读卡 6 扫码 7 拍照 8 视频 9 定位
let res = {
"code": 200,
"msg": "操作成功",
"data": {
groupName:'配电箱箱体和内部线路检查',
planTime:new Date().getTime(),
pointName:'技术中心机房总电源,监控室消防设备阀门正常开启闭合,监控室10组灭火器压力指针处于绿色区域',
}
}
let data = res.data || {};
data.list = data.pointName.split(",") || [];
optionObj.value = data;
}
// 图片或视频
let imgArr = ref([]);//视频
let videoArr = ref([]);//视频
const imgArr2=ref([]);
const chooseMedia = () => {
imgArr.value=[];
videoArr.value=[];
uni.chooseMedia({
count: 9,
mediaType: ['image', 'video'], // 指定可选择图片和视频
sourceType: ['album', 'camera'],
maxDuration: 30, // 拍摄视频最长拍摄时间
camera: 'back',
success: (res) => {
console.log(res)
res.tempFiles.forEach(file => {
console.log(`文件类型: ${file.type}, 文件路径: ${file.tempFilePath}`);
if (file.type === 'image') {
// 显示本地图片
imgArr.value.push(file)
} else if (file.type === 'video') {
// 处理视频
videoArr.value.push(file)
}
});
}
});
}
// 提交
const handleSubmit=()=>{
// showModel('有未完成的巡检项');
// showModel2('此巡检项已完成');
}
</script>
<style scoped>
.white-bg{
width: 680rpx;
padding: 15rpx 30rpx 40rpx 40rpx;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
}
.white-bg .red-title{
margin-bottom:30rpx;
color:#FF2B44;
font-size:38rpx;
}
.report-border{
border-bottom:1px solid #E7E7E7;
width:710rpx;
height: 1px;
margin:20rpx 0;
}
.report-list{
display: flex;
font-size:28rpx;
}
.report-list .r-title{
position: relative;
}
.report-list text{
color:#FF2B44;
font-size:28rpx;
}
.report-list .report-pro{
font-size:28rpx;
width:16%;
/* margin-right:30rpx; */
}
.report-list .report-right{
width:84%;
color:#919191;
font-size:28rpx;
}
.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: 120rpx;
}
.report-list .r-list{
padding: 10rpx 0;
}
.report-list .r-list:first-child{
padding-top:0;
}
.report-list .r-left{
display: flex;
font-size:28rpx;
color:#919191;
}
.report-list .r-left view:first-child{
margin-right: 10rpx;
}
.report-list .r-left.r-red{
color: #FF2B44;
font-size: 28rpx;
}
.report-list .r-right{
display: flex;
font-size:30rpx;
align-items: center;
justify-content: center;
}
.report-list .r-r-round{
width:35rpx;
height:35rpx;
border:1px solid #BDBDBD;
border-radius: 50%;
}
.report-list .r-r-round.active{
border:1px solid #FF2B44;
background-color: #FF2B44;
position: relative;
}
.report-list .r-r-round.active::after{
content:"!";
position: absolute;
top:0;
left:0;
color:#fff;
text-align: center;
width:35rpx;
height:35rpx;
/* #ifndef APP-PLUS */
line-height: 30rpx;
/* #endif */
/* #ifdef APP-PLUS */
height:30rpx;
padding-top:5rpx;
/* #endif */
}
.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:76rpx;
height: 76rpx;
margin-top:30rpx;
}
.report-list .img-show,
.report-list .img-con{
width: calc(100% / 3 - 10px); /* 减去一些间隙以避免溢出 */
margin:20rpx 10rpx 0;
}
.report-list .img-show:first-child,
.report-list .img-con:first-child{
margin-left:0;
}
.btn-submit{
width:400rpx;
margin:90rpx 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>

View File

@@ -0,0 +1,292 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="问题上报"
:leftFlag="true" :rightFlag="false"
></customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<view class="white-bg">
<view class="red-title">问题{{optionObj.optionName}}</view>
<view class="report-list">
<view class="report-pro">问题项</view>
<view class="report-right">
<view class="r-list" v-for="(item, index) in optionObj.list" :key="index">
<view class="r-left" :class="{'r-red':item.active}">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
<view class="r-right">
<view class="r-r-round" :class="{active:item.active}"></view>
</view>
</view>
</view>
</view>
<view class="report-border"></view>
<view class="report-list">
<view class="report-pro">提交人</view>
<view class="report-right">{{realname}}</view>
</view>
<view class="report-border"></view>
<view class="report-list" style="display:block;">
问题描述
<textarea class="r-input textarea" v-model="desc" auto-height
placeholder="请输入问题项描述" placeholder-class="place-input"
></textarea>
</view>
<view class="report-border"></view>
<view class="report-list" style="display:block;">
<view class="r-title">问题点位照片或视频 <text>*</text></view>
<view class="img-flex">
<view class="img-show" v-for="(item,index) in imgArr" :key="index">
<img :src="item" />
</view>
<view class="img-show" v-for="(item,index) in videoArr" :key="index">
<video :src="item" controls></video>
</view>
<view class="img-con" @click="chooseMedia">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
</view>
</view>
<view class="btn-submit">
<button type="primary" @click="handleSubmit">{{id?'修改问题跟踪表':'生成问题跟踪表'}}</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import { getUserInfo } from '@/api/auth.js'
let id = ref('');
let realname = ref('');
let desc = ref('');//描述
onLoad(async option => {
// console.log(option)
id.value = option.id;
let userinfo = await getUserInfo({});
realname.value = userinfo.realname
getList();
})
// 查询列表
let list = ref([]);
let optionObj = ref({})
// 获取数据列表
const getList = () => {
// let res = await noticeList();
// 1 单选 2多选 3判断 4问答 5 读卡 6 扫码 7 拍照 8 视频 9 定位
let res = {
"code": 200,
"msg": "操作成功",
"data": {
optionName:'配电箱箱体和内部线路检查',
planTime:new Date().getTime(),
list:[
{ pointName:'技术中心机房总电源', optionId:202512297899, pointType:1},
{ pointName:'监控室消防设备阀门正常开启闭合', optionId:202512297899, pointType:2},
{ pointName:'监控室10组灭火器压力指针处于绿色区域', optionId:202512297899, pointType:3,active:true},
{ pointName:'库房灭火器压力指针处于绿色区', optionId:202512297899,pointType:4,active:true},
{ pointName:'技术中心机房总电源', optionId:202512297899,pointType:5},
{ pointName:'监控室消防设备阀门正常监控室消防设备阀门正常监控室消防设备阀门正常', optionId:202512297899,pointType:6},
{ pointName:'库房灭火器压力指针处于绿色区', optionId:202512297899,pointType:7},
{ pointName:'技术中心机房总电源', optionId:202512297899,pointType:8},
],
}
}
optionObj.value = res.data||{};
}
// 图片或视频
let imgArr = ref([]);//视频
let videoArr = ref([]);//视频
const imgArr2=ref([]);
const chooseMedia = () => {
imgArr.value=[];
videoArr.value=[];
uni.chooseMedia({
count: 9,
mediaType: ['image', 'video'], // 指定可选择图片和视频
sourceType: ['album', 'camera'],
maxDuration: 30, // 拍摄视频最长拍摄时间
camera: 'back',
success: (res) => {
console.log(res)
res.tempFiles.forEach(file => {
console.log(`文件类型: ${file.type}, 文件路径: ${file.tempFilePath}`);
if (file.type === 'image') {
// 显示本地图片
imgArr.value.push(file)
} else if (file.type === 'video') {
// 处理视频
videoArr.value.push(file)
}
});
}
});
}
// 提交
const handleSubmit=()=>{
// showModel('有未完成的巡检项');
// showModel2('此巡检项已完成');
}
</script>
<style scoped>
.white-bg{
width: 680rpx;
padding: 15rpx 30rpx 40rpx 40rpx;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
}
.white-bg .red-title{
margin-bottom:30rpx;
color:#FF2B44;
font-size:38rpx;
}
.report-border{
border-bottom:1px solid #E7E7E7;
width:710rpx;
height: 1px;
margin:20rpx 0;
}
.report-list{
display: flex;
font-size:28rpx;
}
.report-list .r-title{
position: relative;
}
.report-list text{
color:#FF2B44;
font-size:28rpx;
}
.report-list .report-pro{
font-size:28rpx;
width:16%;
/* margin-right:30rpx; */
}
.report-list .report-right{
width:84%;
color:#919191;
font-size:28rpx;
}
.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: 120rpx;
}
.report-list .r-list{
padding: 10rpx 0;
}
.report-list .r-list:first-child{
padding-top:0;
}
.report-list .r-left{
display: flex;
/* align-items: center; */
width:70%;
font-size:28rpx;
color:#919191;
}
.report-list .r-left view:first-child{
margin-right: 10rpx;
}
.report-list .r-left.r-red{
color: #FF2B44;
font-size: 28rpx;
}
.report-list .r-right{
display: flex;
font-size:30rpx;
align-items: center;
justify-content: center;
}
.report-list .r-r-round{
width:35rpx;
height:35rpx;
border:1px solid #BDBDBD;
border-radius: 50%;
}
.report-list .r-r-round.active{
border:1px solid #FF2B44;
background-color: #FF2B44;
position: relative;
}
.report-list .r-r-round.active::after{
content:"!";
position: absolute;
top:0;
left:0;
color:#fff;
text-align: center;
width:35rpx;
height:35rpx;
/* #ifndef APP-PLUS */
line-height: 30rpx;
/* #endif */
/* #ifdef APP-PLUS */
height:30rpx;
padding-top:5rpx;
/* #endif */
}
.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:76rpx;
height: 76rpx;
margin-top:30rpx;
}
.report-list .img-show,
.report-list .img-con{
width: calc(100% / 3 - 10px); /* 减去一些间隙以避免溢出 */
margin:20rpx 10rpx 0;
}
.report-list .img-show:first-child,
.report-list .img-con:first-child{
margin-left:0;
}
.btn-submit{
width:400rpx;
margin:90rpx 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>

View File

@@ -15,143 +15,189 @@
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 列表 -->
<mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"
:up="upOption" :down="downOption" :fixed="false" class="scroll-h" :class="{'loading-scroll':cssFlag}">
<!-- 列表 @down="downCallback" @up="upCallback"
:up="upOption" :down="downOption" :fixed="false" -->
<mescroll-uni ref="mescrollRef" @init="mescrollInit" :fixed="false"
class="scroll-h" :class="{'loading-scroll':cssFlag}"
@down="downCallback" :down="downOption"
@up="upCallback" :up="upOption"
@scroll="onScroll"
>
<view class="white-bg">
<!-- 日历 -->
<view class="calender-con">
<uni-calendar class="uni-calendar--hook" :selected="calendarInfo.selected"
:showMonth="false" @change="change"
@monthSwitch="monthSwitch"
/>
<view class="white-top" v-if="isMonth">
<view class="calender-con">
<uni-calendar class="uni-calendar--hook" v-if="activeTab==0"
:selected="calendarInfo.selected"
:showMonth="false" @change="calendarChange"
@monthSwitch="monthSwitch"
/>
<calenderMonth :year="year" :month="month" v-else-if="activeTab==1"
@change="calendarChange2"
></calenderMonth>
</view>
<view class="calender-list">
<view class="calender-item">
<view class="item-blue">{{count1}}</view>
<view class="font28">待巡检</view>
</view>
<view class="calender-item">
<view class="item-blue">{{count2}}</view>
<view class="font28">已巡检</view>
</view>
<view class="calender-item">
<view class="item-red">{{count3}}</view>
<view class="font28">未完成</view>
</view>
</view>
</view>
<view class="blue-title">日常巡检</view>
<block v-if="list1.length>0">
<view class="report-list" v-for="(item, index) in list1" :key="index" @click="handleDetail(item)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.taskName }}</view>
<view class="r-right">
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.count==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
</block>
<img v-else-if="item.taskStatus==4" :src="'static/images/polling/icon-complete.png'" class="img-complete" />
<img v-else-if="item.taskStatus==5" :src="'static/images/polling/icon-Expired.png'" class="img-w" />
</view>
</view>
<view class="r-list">
<view class="r-left">巡检单号<span class="r-gray">{{ item.taskId }}</span></view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ item.planTime }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.count<item.total}">{{item.count}}</span>/{{item.total}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{item.percentage}}</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view>任务状态
<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.count==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
</view>
</view>
<view class="report-border" v-if="index<list1.length-1"></view>
<!-- 当是月的时候日历滚动没了的时候 显示 -->
<view v-else class="calender-blue">
<view class="calender-simple">
<calenderMonthSimple :year="year" :month="month"
@change="calendarChange2"
></calenderMonthSimple>
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
<view class="green-title" style="margin-top:80rpx">临时巡检</view>
<block v-if="list2.length>0">
<view class="report-list" v-for="(item, index) in list2" :key="index" @click="handleDetail(item)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.taskName }}</view>
<view class="r-right">
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.count==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
</block>
<img v-else-if="item.taskStatus==4" :src="'static/images/polling/icon-complete.png'" class="img-complete" />
<img v-else-if="item.taskStatus==5" :src="'static/images/polling/icon-Expired.png'" class="img-w" />
</view>
</view>
<view class="r-list">
<view class="r-left">巡检单号<span class="r-gray">{{ item.taskId }}</span></view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ item.planTime }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.count<item.total}">{{item.count}}</span>/{{item.total}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{item.percentage}}</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view>任务状态
<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.count==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
<view v-if="!isMonth" style="height:100rpx;"></view>
<block v-for="(row,index) in list" :key="index">
<!-- 日期展示 -->
<view class="date-title">{{parseTime(row.date,'{y}-{m}-{d} 星期{a}')}}</view>
<!-- 日常巡检 -->
<view class="blue-title">日常巡检</view>
<block v-if="row.list1.length>0">
<view class="report-list" v-for="(item, index) in row.list1" :key="index" @click="handleDetail(item,1)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.taskName }}</view>
<view class="r-right">
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.count==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
</block>
<img v-else-if="item.taskStatus==4" :src="'static/images/polling/icon-complete.png'" class="img-complete" />
<img v-else-if="item.taskStatus==5" :src="'static/images/polling/icon-Expired.png'" class="img-w" />
</view>
</view>
</view>
<view class="report-border" v-if="index<list2.length-1"></view>
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
</view>
<view class="white-bg white-bg2">
<view class="red-title">问题跟踪</view>
<block v-if="list3.length>0">
<view class="report-list" v-for="(item, index) in list3" :key="index" @click="handleDetail(item)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.problemDesc }}</view>
<view class="r-right">
<!-- 问题状态 1=追踪2=关闭 -->
<view v-if="item.problemStatus==1" class="btn-red">进行中</view>
<view v-if="item.problemStatus==2" class="btn-green">已解决</view>
<view class="r-list">
<view class="r-left">巡检单号<span class="r-gray">{{ item.taskId }}</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">跟踪次数<span class="r-gray">{{ item.count }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{ item.planTime }}</span></view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ item.planTime }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.count<item.total}">{{item.count}}</span>/{{item.total}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{item.percentage}}</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view>任务状态
<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.count==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
</view>
</view>
<view class="report-border" v-if="index<row.list1.length-1"></view>
</view>
<view class="report-border" v-if="index<list3.length-1"></view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
<!-- 临时巡检 -->
<view class="green-title" style="margin-top:60rpx">临时巡检</view>
<block v-if="row.list2.length>0">
<view class="report-list" v-for="(item, index) in row.list2" :key="index" @click="handleDetail(item,1)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.taskName }}</view>
<view class="r-right">
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.count==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
</block>
<img v-else-if="item.taskStatus==4" :src="'static/images/polling/icon-complete.png'" class="img-complete" />
<img v-else-if="item.taskStatus==5" :src="'static/images/polling/icon-Expired.png'" class="img-w" />
</view>
</view>
<view class="r-list">
<view class="r-left">巡检单号<span class="r-gray">{{ item.taskId }}</span></view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ item.planTime }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.count<item.total}">{{item.count}}</span>/{{item.total}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{item.percentage}}</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view>任务状态
<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.count==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
</view>
</view>
<view class="report-border" v-if="index<row.list2.length-1"></view>
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
<!-- 问题跟踪 -->
<view class="bg-border"></view>
<view class="red-title">问题跟踪</view>
<block v-if="row.list3.length>0">
<view class="report-list" v-for="(item, index) in row.list3" :key="index" @click="handleDetail(item,2)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.problemDesc }}</view>
<view class="r-right">
<!-- 问题状态 1=追踪2=关闭 -->
<view v-if="item.problemStatus==1" class="btn-red">进行中</view>
<view v-if="item.problemStatus==2" class="btn-green">已解决</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">跟踪次数<span class="r-gray">{{ item.count }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{ item.planTime }}</span></view>
</view>
</view>
<view class="report-border" v-if="index<row.list3.length-1"></view>
</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>
@@ -162,35 +208,52 @@ import { ref,onMounted } from 'vue'
import { onLoad,onHide } from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import calenderMonth from '@/components/calenderMonth.vue'
import calenderMonthSimple from '@/components/calenderMonthSimple.vue'
import customTabs from '@/components/customTabs.vue';
import { parseTime } from '@/utils/datetime.js';
import { formatTaskStatus } from '@/utils/status.js';
import { noticeList } from '@/api/notice.js'
const form = ref({});//查询条件
const year=ref('');//默认月份
const month=ref('');//默认月份
const activeTab = ref(0);//默认按天查看
const tabList = ['按天查看', '按月查看'];
// '2025-12-29 星期五'
let dateStr = ref('');
let count1=ref(12);//待巡检
let count2=ref(9);//已巡检
let count3=ref(3);//已完成
onLoad(option => {
let date = new Date();
dateStr.value = parseTime(date,'{y}-{m}-{d} 星期{a}');
year.value = date.getFullYear();
month.value = date.getMonth()+1;
})
// tab切换
// tab切换 按日 or 按月 0-按日 1-按月
const handleTab = (index)=>{
activeTab.value = index
activeTab.value = index;
isMonth.value=true;
// scrollToPosition();
}
// 日历
// 日历初始化
const calendarInfo = ref({
range: true,
insert: false,//插入模式可选值ture插入模式false弹窗模式默认为插入模式
selected: [],
showMonth:true,
});
const change=(e)=> {
console.log('change 返回:', e)
// 按日选择 选月处理
const monthSwitch=(e)=>{
console.log('monthSwitchs 返回:', e)
}
// 按日选择 日历change
const calendarChange=(e)=> {
console.log('按日选择 返回:', e.fulldate)
form.value.selectDate = e.fulldate
// 模拟动态打卡
// if (calendarInfo.value.selected.length > 5) return
// calendarInfo.value.selected.push({
@@ -199,16 +262,21 @@ const change=(e)=> {
// })
}
const monthSwitch=(e)=>{
console.log('monthSwitchs 返回:', e)
// 按月选择 日历change
const calendarChange2=(value)=> {
console.log("按月选择 选择具体月份=>",value)
form.value.selectDate = value.ymStr
year.value = value.year;
month.value = value.month;
}
// 查询列表
let list1 = ref([]);
let list2 = ref([]);
let list3 = ref([]);
let list = ref([])
const mescrollRef = ref(null);
const isMonth = ref(true);
const upOption = ref({
onScroll:true,
page: { num: 0, size: 10 },
noMoreSize: 5,
empty: {
@@ -227,20 +295,29 @@ const downOption = ref({
});
let cssFlag=ref(false);//控制样式
const mescrollInit = (mescroll) => {
const mescrollInit = async(mescroll) => {
cssFlag.value = true;
mescrollRef.value = mescroll;
};
// 滚动到指定位置
const scrollToPosition = () => {
console.log(mescrollRef)
if (mescrollRef) {
// 滚动到指定Y坐标位置单位px
mescrollRef.value.scrollTo(0, 300) // 滚动到Y=500的位置动画时长300ms
// mescrollRef.value.scrollTop=0;
}
}
// 下拉刷新
const downCallback = async (mescroll) => {
try {
console.log("下拉刷新")
isMonth.value=true;
const res = await getList(1, upOption.value.page.size);
cssFlag.value = false;
list1.value = res.list1;
list2.value = res.list2;
list3.value = res.list3;
list.value = res.list;
mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
@@ -256,9 +333,7 @@ const upCallback = async (mescroll) => {
console.log("上拉加载更多")
let res = await getList(mescroll.num, mescroll.size);
if (mescroll.num === 1) {
list1.value = res.list1;
list2.value = res.list2;
list3.value = res.list3;
list.value = res.list;
} else {
// list.value.push(...res.list);
}
@@ -281,105 +356,110 @@ const getList = (pageIndex, pageSize) => {
"code": 200,
"msg": "操作成功",
"data": {
list1:[
list:[
{
taskName:'日常巡检任务AAA日常巡检任务111',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
taskName:'日常巡检任务AAA日常巡检任务222',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
taskName:'日常巡检任务BBB任务名称过长时可折行 行距35px',
taskId:202512297899,
taskStatus:4,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
taskName:'日常巡检任务ccc任务名称',
taskId:202512297899,
taskStatus:5,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
],
list2:[
{
taskName:'日常巡检任务AAA日常巡检任务111',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
taskName:'日常巡检任务AAA日常巡检任务222',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
taskName:'日常巡检任务BBB任务名称过长时可折行 行距35px',
taskId:202512297899,
taskStatus:4,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
taskName:'日常巡检任务ccc任务名称',
taskId:202512297899,
taskStatus:5,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
],
list3:[
{
problemDesc:'西区地下车库入口防汛物资摆放',
problemId:202512297899,
problemStatus:1,
modifyTime:new Date().getTime(),
count:0,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
date:new Date(),
list1:[
{
taskName:'日常巡检任务AAA日常巡检任务111',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
taskName:'日常巡检任务AAA日常巡检任务222',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
taskName:'日常巡检任务BBB任务名称过长时可折行 行距35px',
taskId:202512297899,
taskStatus:4,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
taskName:'日常巡检任务ccc任务名称',
taskId:202512297899,
taskStatus:5,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
],
list2:[
{
taskName:'日常巡检任务AAA日常巡检任务111',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
taskName:'日常巡检任务AAA日常巡检任务222',
taskId:202512297899,
taskStatus:3,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
taskName:'日常巡检任务BBB任务名称过长时可折行 行距35px',
taskId:202512297899,
taskStatus:4,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
taskName:'日常巡检任务ccc任务名称',
taskId:202512297899,
taskStatus:5,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
],
list3:[
{
problemDesc:'西区地下车库入口防汛物资摆放',
problemId:202512297899,
problemStatus:1,
modifyTime:new Date().getTime(),
count:0,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
}
]
}
]
}
@@ -393,11 +473,35 @@ const getList = (pageIndex, pageSize) => {
}
// 查看详情
const handleDetail = (item) =>{
// 滚动位置
const onScroll = (mescrollInstance)=>{
const y = mescrollInstance.getScrollTop()
// const maxScroll = mescrollInstance.getScrollHeight() - mescrollInstance.getClientHeight()
// console.log(y)
if(activeTab.value==1){
if(y==0){
isMonth.value=true;
}
if(y>=200){
isMonth.value=false;
}
}
}
// 查看详情 type 1-任务详情 2-问题详情
const handleDetail = (item,type) =>{
let url=''
if(type==1){
url = '/pages/business/polling/taskDetail?id='+item.taskId;
}else{
url = '/pages/business/polling/problemDetail?id='+item.taskId;
}
uni.navigateTo({
url
});
}
// 跳转webview
const handleJump = (item)=>{
// uni.navigateTo({
@@ -409,6 +513,50 @@ const handleJump = (item)=>{
</script>
<style scoped>
.white-top{
position: relative;
padding-bottom:30rpx;
margin-bottom:26rpx;
}
.calender-list{
display: flex;
align-items: center;
justify-content: center;
padding:20rpx 0;
}
.calender-list .calender-item{
/* flex-grow: 1; */
width:33.3%;
text-align: center;
position: relative;
}
.calender-list .calender-item::after{
position: absolute;
content: " ";
width:1px;
height:80%;
background-color: #EAEAEA;
right:0;
top:25rpx;
}
.calender-list .calender-item:last-child::after{
display:none;
}
.calender-list .calender-item .item-blue,
.calender-list .calender-item .item-red{
color:#579FF9;
font-size:60rpx;
font-weight: bold;
}
.calender-list .calender-item .item-red{
color: #FF687A;
}
.calender-list .calender-item .font28{
font-size:28rpx;
}
.tab-list{
display: flex;
justify-content: center;
@@ -449,13 +597,21 @@ const handleJump = (item)=>{
}
/* 日历 */
.date-title{
background-color: #F5F5F5;
padding:40rpx 40rpx 30rpx;
width:670rpx;
margin-left:-40rpx;
color:#919191;
font-size:28rpx;
}
.scroll-h{
/* #ifdef APP-PLUS */
height: calc(100vh - 108px);
height: calc(100vh - 77px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 93px);
height: calc(100vh - 58px);
/* #endif */
}
.head-right{
@@ -477,10 +633,35 @@ const handleJump = (item)=>{
padding: 30rpx 40rpx 40rpx;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
position: relative;
}
.white-bg2{
border-radius: 0;
margin-top:20rpx;
.white-bg .blue-title,
.white-bg .red-title{
margin-top:30rpx
}
.white-bg .calender-blue{
position: fixed;
width:750rpx;
left:0rpx;
top:115rpx;
/* #ifdef APP-PLUS */
top:155rpx;
/* #endif */
z-index: 9999;
background-color: #307AF5;
}
.white-bg .calender-simple{
padding-top:30rpx;
background-color: #fff;
border-radius: 8px 8px 0 0;
}
.bg-border{
width:750rpx;
height:20rpx;
background-color: #F5F5F5;
margin-left:-40rpx;
margin-top:40rpx;
}
.report-list{
position: relative;
@@ -533,4 +714,7 @@ const handleJump = (item)=>{
height:210rpx;
margin:40rpx auto;
}
:deep(.mescroll-upwarp){
display: none !important;
}
</style>

View File

@@ -0,0 +1,484 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="巡检详情"
:leftFlag="true" :rightFlag="false"
></customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<view class="week">{{dateStr}}</view>
<view class="bg-progress">
<view class="progress-container">
<circleTemplate :progress="progress"></circleTemplate>
<span>{{ progress }}%</span>
</view>
<view class="polling">
<view class="p-title p-border">巡检单号<text>20251229789</text></view>
<view class="p-title">完成进度<text class="p-blue">3</text>/9</view>
<view class="btn-primary">
<button type="primary" size="mini">任务提交</button>
</view>
</view>
</view>
<!-- 列表 -->
<view class="white-bg2">
<mescroll-uni ref="mescrollRef" @init="mescrollInit"
@down="downCallback" :down="downOption"
@up="upCallback" :up="upOption"
:fixed="false" class="scroll-h"
:class="{'loading-scroll':cssFlag}"
>
<view class="white-bg">
<view class="blue-title">日常巡检任务AAA</view>
<block v-if="list.length>0">
<view class="report-list" v-for="(item, index) in list" :key="index" @click="handleDetail(item,1)">
<view class="r-list">
<view class="r-left">{{ item.workName }}</view>
<view class="r-right">
<block v-if="item.count<item.total">
<text class="r-orange">{{item.count}}</text>/{{item.total}}
</block>
<block v-else>{{item.count}}/{{item.total}}</block>
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</view>
<view class="report-border" :style="{borderColor:index<list.length-1?'#E7E7E7':'#fff'}"></view>
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
<!-- 问题跟踪 -->
<view class="bg-border"></view>
<view class="red-title">问题跟踪</view>
<block v-if="questList.length>0">
<view class="report-list" v-for="(item, index) in questList" :key="index" @click="handleDetail(item,2)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.problemDesc }}</view>
<view class="r-right">
<!-- 问题状态 1=追踪2=关闭 -->
<view v-if="item.problemStatus==1" class="btn-red">进行中</view>
<view v-if="item.problemStatus==2" class="btn-green">已解决</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">跟踪次数<span class="r-gray">{{ item.count }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{ parseTime(item.modifyTime,'{m}-{d} {h}:{i}') }}</span></view>
</view>
</view>
<view class="report-border" v-if="index<questList.length-1"></view>
</view>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
</view>
</mescroll-uni>
</view>
</view>
</view>
</template>
<script setup>
import { ref,onMounted,nextTick,computed } from 'vue'
import { onLoad,onHide } from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import circleTemplate from '@/components/circleTemplate.vue';
import { parseTime } from '@/utils/datetime.js';
import { formatTaskStatus } from '@/utils/status.js';
import { noticeList } from '@/api/notice.js'
// '2025-12-29 星期五'
let taskId = ref(undefined);
let dateStr = ref('');
const progress = ref(0); // 初始进度为0
onLoad(option => {
// console.log(option)
taskId.value = option.id;
let date = new Date();
let dstr = parseTime(date,'{y}-{m}-{d} 星期{a}');
dateStr.value = dstr + " 14:00开始 时长6小时";
setTimeout(() => {
progress.value=62
}, 1000);
})
// 查询列表
let list = ref([]);
let questList = ref([]);//问题列表
const mescrollRef = ref(null);
const upOption = ref({
// use: false,
page: { num: 0, size: 10 },
noMoreSize: 5,
empty: {
tip: '~ 空空如也 ~',
icon: "../../../static/images/mescroll-empty.png"
},
textLoading: '加载中...',
textNoMore: '已经到底了'
});
const downOption = ref({
auto: false,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
let cssFlag=ref(false);//控制样式
const mescrollInit = (mescroll) => {
cssFlag.value = true;
mescrollRef.value = mescroll;
};
// 下拉刷新
const downCallback = async (mescroll) => {
try {
console.log("下拉刷新")
const res = await getList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
questList.value=res.questList
mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {
setTimeout(async ()=>{
mescroll.endSuccess();
},500);
}
}
// 上拉加载更多
const upCallback = async (mescroll) => {
try {
console.log("上拉加载更多")
let res = await getList(mescroll.num, mescroll.size);
if (mescroll.num === 1) {
list.value = res.list;
questList.value=res.questList
} else {
// list.value.push(...res.list);
}
mescroll.endBySize(res.list.length, res.total);
} catch (error) {
mescroll.endErr();
}
}
// 获取数据列表
const getList = (pageIndex, pageSize) => {
return new Promise(async (resolve) => {
let param = {
pageIndex,
pageSize,
}
// let res = await noticeList(param);
let res = {
"code": 200,
"msg": "操作成功",
"data": {
list:[
{
workName:'技术中心机房总电源',
pointId:202512297899,
workStatus:3,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
workName:'监控室消防设备阀门正常开启闭合',
workId:202512297899,
workStatus:3,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
workName:'监控室10组灭火器压力指针处于绿色区域',
workId:202512297899,
workStatus:4,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
workName:'库房灭火器压力指针处于绿色区',
workId:202512297899,
workStatus:5,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
workName:'技术中心机房总电源',
pointId:202512297899,
workStatus:3,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
workName:'监控室消防设备阀门正常开启闭合',
workId:202512297899,
workStatus:3,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
workName:'监控室10组灭火器压力指针处于绿色区域',
workId:202512297899,
workStatus:4,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
workName:'库房灭火器压力指针处于绿色区',
workId:202512297899,
workStatus:5,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
workName:'技术中心机房总电源',
pointId:202512297899,
workStatus:3,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
workName:'监控室消防设备阀门正常开启闭合',
workId:202512297899,
workStatus:3,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
workName:'监控室10组灭火器压力指针处于绿色区域',
workId:202512297899,
workStatus:4,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
workName:'库房灭火器压力指针处于绿色区',
workId:202512297899,
workStatus:5,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
],
questList:[
{
problemDesc:'西区地下车库入口防汛物资摆放',
problemId:202512297899,
problemStatus:1,
modifyTime:new Date().getTime(),
count:0,
},
{
problemDesc:'监控室消防设备阀门确保正常开启闭合',
problemId:202512297899,
problemStatus:2,
modifyTime:new Date().getTime(),
count:20,
}
]
}
}
let data = res.data||{};
resolve({
...data,
// total: res.recordCount || 0
});
});
}
// 查看详情 type 1-详情 2-问题详情
const handleDetail = (item,type) =>{
let url=''
if(type==1){
url = '/pages/business/polling/optionDetail?id='+item.pointId;
}else{
url = '/pages/business/polling/problemDetail?id='+item.taskId;
}
uni.navigateTo({
url
});
}
</script>
<style scoped>
.week{
padding:0 40rpx;
color: #fff;
font-size:32rpx;
margin-bottom:20rpx;
}
.bg-progress{
background: url('@/static/images/polling/bg-XunJian.png') no-repeat;
background-size:750rpx 352rpx;
/* width:750rpx;
height: 352rpx; */
width:730rpx;
height: 312rpx;
padding:20rpx 0rpx 20rpx 20rpx;
display: flex;
}
.bg-progress .progress-container{
width:280rpx;
height: 280rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.bg-progress .progress-container span{
position: absolute;
font-size: 72rpx;
color:#10F6FC;
font-weight: bold;
}
.bg-progress .polling{
padding:20rpx 0rpx 50rpx 50rpx;
}
.polling .p-title{
font-size:26rpx;
color:#fff;
padding:10rpx 30rpx 20rpx 0;
}
.polling .p-title.p-border{
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
}
.polling .p-title text{
font-size:32rpx;
font-weight: bold;
margin-left:10rpx;
}
.polling .p-title text.p-blue{
color:#10F6FC
}
.polling .btn-primary{
width:220rpx;
margin-top:10rpx;
}
.polling .btn-primary uni-button[type='primary']{
background-color: #05A3F4;
}
.scroll-h{
/* #ifdef APP-PLUS */
height: calc(100vh - 258px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 253px);
/* #endif */
}
.white-bg{
width: 680rpx;
padding: 30rpx 30rpx 30rpx 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-list .r-left{
display: flex;
}
.report-list .r-list .r-gray{
margin-left:10rpx;
}
.report-list .r-list .r-right{
display: flex;
}
.report-list .r-list .r-right .r-orange{
color:#FF9638
}
.report-list .r-list .r-right .uniui-right{
margin-left:20rpx;
}
.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 .no-pic{
display: block;
width:440rpx;
height:210rpx;
margin:40rpx auto;
}
:deep(.mescroll-upwarp){
display: none !important;
}
</style>

View File

@@ -1,13 +1,422 @@
<template>
<view>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="巡检任务查询"
:leftFlag="true" :rightFlag="false"
></customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 查询条件 -->
<view class="search">
<view class="search-bg s-b-left" @click="chooseDate">
<img :src="'static/images/polling/icon-search-clock.png'" class="img-icon" />
近3天
<uni-icons type="down" size="16" color="#C8DCFF" style="margin-left:auto;"></uni-icons>
</view>
<view class="search-bg s-b-right">
<img :src="'static/images/polling/icon-type.png'" class="img-type" />
<picker @change="changeTaskType" :value="taskTypeIndex" :range="taskTypeArr">
<view class="uni-input">{{taskTypeArr[taskTypeIndex]}}</view>
</picker>
<uni-icons type="down" size="16" color="#C8DCFF" style="margin-left:auto;"></uni-icons>
</view>
<view class="search-btn">查询</view>
</view>
<view class="week">{{dateStr}}</view>
<!-- 列表 -->
<mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"
:up="upOption" :down="downOption" :fixed="false" class="scroll-h" :class="{'loading-scroll':cssFlag}">
<view class="white-bg">
<block v-if="list.length>0">
<block v-for="(obj, index) in list" :key="index">
<view class="r-title">{{ parseTime(obj.dateStr,'{y}-{m}-{d} 星期{a}')}}</view>
<view class="report-list" v-for="(item, index) in obj.list" :key="index" @click="handleDetail(item)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.taskName }}</view>
<view class="r-right">
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.count==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
</block>
<img v-else-if="item.taskStatus==4" :src="'static/images/polling/icon-complete.png'" class="img-complete" />
<img v-else-if="item.taskStatus==5" :src="'static/images/polling/icon-Expired.png'" class="img-w" />
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">巡检单号<span class="r-gray">{{ item.taskId }}</span></view>
<view class="r-l-right">类型<span class="r-gray">{{ formatTaskType(item.taskType) }}</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ item.planTime }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.count<item.total}">{{item.count}}</span>/{{item.total}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{item.percentage}}</span></view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view>任务状态
<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.count==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
</view>
</view>
<view class="report-border" v-if="index<obj.list.length-1"></view>
</view>
</block>
</block>
<view v-else class="no-data">
<img :src="'static/images/polling/pic-NoResult.png'" class="no-pic" />
</view>
</view>
</mescroll-uni>
</view>
<!-- 选择日期 -->
<searchDate :isShow="isShow" @close="dateClose" @confirm="dateConfirm"></searchDate>
</view>
</template>
<script setup>
import { ref,onMounted,getCurrentInstance } from 'vue'
import { onLoad,onHide } from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import searchDate from '@/components/searchDate.vue';
import { parseTime,getDateRange } from '@/utils/datetime.js';
import { taskTypeOptions,formatTaskType,formatTaskStatus } from '@/utils/status.js';
import { noticeList } from '@/api/notice.js'
const { proxy } = getCurrentInstance();
// 底部日期选择
let isShow = ref(false);
const chooseDate = ()=>{
isShow.value = true;
}
const dateClose=()=>{
isShow.value = false;
}
const dateConfirm=(dateObj)=>{
console.log(dateObj)
isShow.value = false;
dateStr.value = dateObj.startDate + ' 至 '+dateObj.endDate
}
// 类型索引
let taskTypeIndex = ref(0);
let taskTypeArr = ref([]);
let dateStr = ref('');
onLoad(option => {
taskTypeOptions.forEach(item => {
taskTypeArr.value.push(item.label)
});
let dateObj = getDateRange('3days');
dateStr.value = dateObj.startDate + ' 至 '+dateObj.endDate
})
// 选择类型筛选
const changeTaskType = (e)=>{
taskTypeIndex.value = e.detail.value
let obj = taskTypeOptions[taskTypeIndex.value];
console.log('obj=>', obj)
}
// 查询列表
let list = ref([]);
let form = ref({
beginTime:''
})
const mescrollRef = ref(null);
const upOption = ref({
// use: false,
page: { num: 0, size: 10 },
noMoreSize: 5,
empty: {
tip: '~ 空空如也 ~',
icon: "../../../static/images/mescroll-empty.png"
},
textLoading: '加载中...',
textNoMore: '已经到底了'
});
const downOption = ref({
auto: false,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
let cssFlag=ref(false);//控制样式
const mescrollInit = (mescroll) => {
cssFlag.value = true;
mescrollRef.value = mescroll;
};
// 下拉刷新
const downCallback = async (mescroll) => {
try {
console.log("下拉刷新")
const res = await getList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {
setTimeout(async ()=>{
mescroll.endSuccess();
},500);
}
}
// 上拉加载更多
const upCallback = async (mescroll) => {
try {
console.log("上拉加载更多")
let res = await getList(mescroll.num, mescroll.size);
if (mescroll.num === 1) {
list.value = res.list;
} else {
list.value.push(...res.list);
}
mescroll.endBySize(res.list.length, res.total);
} catch (error) {
mescroll.endErr();
}
}
// 获取数据列表
const getList = (pageIndex, pageSize) => {
return new Promise(async (resolve) => {
let param = {
pageIndex,
pageSize,
}
// let res = await noticeList(param);
let res = {
"code": 200,
"msg": "操作成功",
"data": {
list:[
{
dateStr:new Date().getTime(),
list:[
{
taskName:'日常巡检任务AAA日常巡检任务111',
taskId:202512297899,
taskStatus:3,
taskType:1,
planTime:'10:25',
workHour:1,
count:0,
total:70,
percentage:'30%'
},
{
taskName:'日常巡检任务AAA日常巡检任务222',
taskId:202512297899,
taskStatus:3,
taskType:2,
planTime:'10:25',
workHour:1,
count:20,
total:70,
percentage:'40%'
},
{
taskName:'日常巡检任务BBB任务名称过长时可折行 行距35px',
taskId:202512297899,
taskStatus:4,
taskType:1,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
{
taskName:'日常巡检任务ccc任务名称',
taskId:202512297899,
taskStatus:5,
taskType:2,
planTime:'10:25',
workHour:1,
count:70,
total:70,
percentage:'100%'
},
],
},
],
recordCount:10,
}
}
let data = res.data||{};
resolve({
...data,
total: res.recordCount || 0
});
});
}
// 查看详情
const handleDetail = (item,type) =>{
let url='/pages/business/polling/taskDetail?id='+item.taskId;
uni.navigateTo({
url
});
}
</script>
<style lang="scss" scoped>
<style scoped>
.scroll-h{
/* #ifdef APP-PLUS */
height: calc(100vh - 108px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 130px);
/* #endif */
}
.search{
display: flex;
align-items: center;
padding:0 30rpx;
}
.search .search-bg{
margin:20rpx 0;
line-height:56rpx;
}
.search .s-b-left{
width:230rpx;
display: flex;
align-items: center;
}
.search .s-b-right{
width:270rpx;
margin-left:20rpx;
margin-right:20rpx;
display: flex;
align-items: center;
}
.search .search-bg .img-icon,
.search .search-bg .img-type{
width:28rpx;
height: 28rpx;
margin-right:20rpx;
}
.search .search-bg .img-icon{
width:30rpx;
height:30rpx;
}
.search .search-btn{
width:150rpx;
color:#fff;
font-size:28rpx;
}
.week{
padding:0 40rpx;
color: #fff;
font-size:28rpx;
margin-bottom:10rpx;
}
.white-bg{
width: 670rpx;
padding: 30rpx 40rpx 40rpx;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
}
.white-bg2{
border-radius: 0;
margin-top:20rpx;
}
.r-title{
width:670rpx;
color:#919191;
font-size:28rpx;
background-color: #F5F5F5;
margin-left:-40rpx;
padding:40rpx 40rpx 30rpx;
margin-bottom:15rpx;
}
.report-list{
position: relative;
}
.img-w{
width:50rpx;
height:50rpx;
}
.img-complete{
position: absolute;
right:-40rpx;
top:60rpx;
width:133rpx;
height:150rpx;
}
.report-border{
border-bottom:1px solid #E7E7E7;
width:710rpx;
height: 1px;
margin:20rpx 0;
}
.report-list .r-list{
padding: 5rpx 0;
}
.report-list .r-list .r-left{
display: flex;
}
.report-list .r-list .r-gray{
margin-left:10rpx;
}
.report-list .r-list .r-blue{
margin-left:10rpx;
font-weight: bold;
}
.report-list .r-list .r-red{
font-weight: bold;
}
.report-list .r-list .r-name{
width:525rpx
}
.r-left .r-l-left{
width:350rpx;
}
.r-left .r-l-right{
}
.no-data .no-pic{
display: block;
width:440rpx;
height:210rpx;
margin:40rpx auto;
}
</style>

View File

@@ -266,7 +266,7 @@ const submitForm = () => {
//h5测试用 内网-sn123456
//公司外网 'f3fca83f-bf56-47f4-a98b-a602ed8bddee' 529a5543-6957-401e-b090-13df6dee5429
//友晟外网 'b97527c8-2ad4-493c-a01c-5f9d0aabaff2'
param.uniqCode = '4d8489b7-78cd-4db2-9227-d7c2ab2093d8';
param.uniqCode ='1af78c0a-b878-425f-9dc5-bee42146860a' //'4d8489b7-78cd-4db2-9227-d7c2ab2093d8';
let res = await login(param);
userStore.login(res);
uni.switchTab({ url: '/pages/home/home' })

View File

@@ -292,16 +292,28 @@ page {
color:#3384DF;
font-size:38rpx;
font-weight: bold;
margin:10rpx 0;
}
.white-bg .blue-sub-title{
color:#3384DF;
font-size:28rpx;
font-weight: bold;
margin:10rpx 0;
}
.white-bg .blue-sub-title text{
font-weight:normal;
}
.white-bg .green-title{
color:#12BC36;
font-size:38rpx;
font-weight: bold;
margin:10rpx 0;
}
.white-bg .red-title{
color:#FF687A;
font-size:38rpx;
font-weight: bold;
margin:10rpx 0;
}
.white-bg .w-b-title {
@@ -693,7 +705,13 @@ uni-button[type='primary'][plain] {
/* 日历修改 begin */
.calender-con{
box-shadow: 0px 0px 15px 0px rgba(4, 0, 0, 0.15);
padding-bottom:30rpx;
padding-bottom:40rpx;
}
.calender-con .uni-calendar__content{
padding:0 20rpx !important;
}
.calender-con .uni-calendar__backtoday{
right:-20rpx !important;
}
.calender-con .uni-calendar__header-text{
color:#333333 !important;
@@ -709,35 +727,55 @@ uni-button[type='primary'][plain] {
font-size: 34rpx !important;
}
.calender-con .uni-calendar__weeks{
padding:0 20rpx !important;
padding:0 0rpx !important;
}
.calender-con .uni-calendar-item__weeks-box-item{
width:auto !important;
height:80rpx !important;
}
.calender-con .uni-calendar-item--checked{
background-color:#fff !important;
opacity:1 !important;
color:#333 !important;
}
.calender-con .uni-calendar-item--isDay{
background-color: transparent !important;
background-color: #fff !important;
opacity: 1 !important;
}
.calender-con .uni-calendar-item__weeks-box-text{
background-color: #EEEEEE !important;
width:60rpx !important;
height:60rpx !important;
width:62rpx !important;
height:62rpx !important;
line-height: 55rpx !important;
/* #ifdef APP-PLUS */
line-height: 65rpx !important;
/* #endif */
border-radius: 50% !important;
text-align: center !important;
font-size:34rpx !important;
color:#333333 !important;
}
.calender-con .uni-calendar-item--checked .uni-calendar-item__weeks-box-text{
background-color:#FF9638 !important;
color:#fff !important;
}
.calender-con .uni-calendar-item--disable{
background-color: transparent !important;
color:#E3E3E3 !important;
}
.calender-con .uni-calendar-item--isDay-text{
.calender-con .uni-calendar-item--isDay.uni-calendar-item--isDay-text{
background-color: #FF9638 !important;
color:#fff !important;
width:65rpx !important;/*72rpx*/
height: 65rpx !important;
line-height: 60rpx !important;
/* #ifdef APP-PLUS */
line-height: 65rpx !important;
/* #endif */
}
.calender-con .uni-calendar-item__weeks-lunar-text.uni-calendar-item--isDay-text span{
.calender-con .uni-calendar-item__weeks-lunar-text.uni-calendar-item--isDay-text{
display: none !important;
}
/* 日历修改 end */

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

View File

@@ -205,3 +205,26 @@ const jumpAuthPermission2 = () => {
}
}
// 判断文件是图片还是视频
export const getFileType = (filePathOrName) => {
// 从路径或文件名中提取扩展名
let suffix = '';
try {
suffix = filePathOrName.split('.').pop().toLowerCase();
} catch (err) {
return 'unknown';
}
// 定义常见的图片和视频扩展名
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
const videoExtensions = ['mp4', 'mov', 'avi', 'm4v', '3gp', 'mpeg', 'mkv', 'wmv'];
if (imageExtensions.includes(suffix)) {
return 'image';
} else if (videoExtensions.includes(suffix)) {
return 'video';
} else {
return 'other';
}
}

View File

@@ -128,3 +128,37 @@ export function parseTime(time, pattern) {
})
return time_str
}
/**
* 获取常用时间范围
* @param {string} rangeType - 范围类型: '3days' | '7days' | '1month' | '3months'
* @returns {Object} { startDate, endDate } 返回开始和结束日期,格式为 'YYYY-MM-DD'
*/
export function getDateRange(rangeType) {
const endDate = new Date(); // 结束日期默认为今天
// 方案一使用原生JavaScript
const startDate = new Date();
switch (rangeType) {
case '3days':
startDate.setDate(endDate.getDate() - 2); // 近3天含今天
break;
case '7days':
startDate.setDate(endDate.getDate() - 6); // 近7天含今天
break;
case '1month':
startDate.setMonth(endDate.getMonth() - 1); // 近1个月
break;
case '3months':
startDate.setMonth(endDate.getMonth() - 3); // 近3个月
break;
default:
throw new Error('不支持的rangeType');
}
// 格式化日期为 'YYYY-MM-DD'
const format = (date) => date.toISOString().split('T')[0];
return {
startDate: format(startDate),
endDate: format(endDate)
};
}

121
src/utils/minio.js Normal file
View File

@@ -0,0 +1,121 @@
import { initMinio, putObject } from 'minio-js'
import { v4 as uuidv4 } from 'uuid'
// import Minio from 'minio';
// const Minio = require("minio");
// import * as Minio from 'minio'
/**
* minio 上传文件
* @param {*} files 文件
* @param {*} fileDir 上传后存放的路径
* @returns
*/
export const uploadFileMinio = async (files,fileDir,tempFilePaths) => {
try {
if (!files.length){
uni.showToast({ title: '请选择要上传的文件', icon: 'none' })
return;
}
let bucketName="718ys-test";//'你的存储桶名称'
// 初始化 Minio 客户端
// #ifndef APP-PLUS
initMinio({
endPoint: '192.168.236.196', // 例如 'play.min.io'
port: 9000, // 端口,使用 SSL 通常是 443非 SSL 是 9000
useSSL: false, // 根据你的 Minio 服务器配置决定 将此值设置为“true”为HTTPS访问false为普通http访问
accessKey: 'cyLu4uv7mFErTWkx',//'你的AccessKey',
secretKey: 'XE9PYr3wt6MHveHn8kA008rebxtj2c7S' //'你的SecretKey'
});
// const minioClient = new Minio.Client({
// endPoint: '192.168.236.196', // 例如 'play.min.io'
// port: 9000, // 端口,使用 SSL 通常是 443非 SSL 是 9000
// useSSL: false, // 根据你的 Minio 服务器配置决定 将此值设置为“true”为HTTPS访问false为普通http访问
// accessKey: 'cyLu4uv7mFErTWkx',//'你的AccessKey',
// secretKey: 'XE9PYr3wt6MHveHn8kA008rebxtj2c7S' //'你的SecretKey'
// })
// #endif
let retrunFileArr=[];
// 批量上传
const uploadPromises = files.map((file,index) => {
return new Promise(async(resolve, reject) => {
// 1.图片路径
let fileName = uuidv4()+ "." + file.name.split(".").pop();
let imgDir = fileDir?(fileDir+'/'+fileName):'/'+fileName;
retrunFileArr.push(imgDir);
console.log(imgDir)
// #ifdef APP-PLUS
uni.uploadFile({
url: 'http://192.168.236.196', //仅为示例,非真实的接口地址
filePath: tempFilePaths[index],
name: 'file',
formData: {
'user': 'test'
},
success: (uploadFileRes) => {
console.log(uploadFileRes);
},
fail: (err)=> {
console.log("err=>",err)
}
});
// #endif
// #ifndef APP-PLUS
// 使用putObject方法上传文件[citation:3]
// minioClient.putObject(bucketName,imgDir, file, (err, etag) => {
// if (err) {
// console.error('上传失败:', err);
// reject(err);
// } else {
// console.log('上传成功Etag:', etag);
// resolve(etag);
// }
// })
// 2. 读取文件
const arrayBuffer = await readFileAsArrayBuffer(file);
// 3. 执行上传
putObject(bucketName, arrayBuffer, imgDir, function (err, etag) {
console.log("执行上传=>",err,etag)
if (err) {
console.error('上传失败:', err);
// 处理错误
reject(err)
} else {
console.log('上传成功Etag:', etag);
// 上传成功后的处理
resolve(etag)
}
});
// #endif
})
})
await Promise.all(uploadPromises)
uni.showToast({ title: '所有文件上传成功', icon: 'success' })
return retrunFileArr;
} catch (error) {
console.error('上传过程中出错:', error);
uni.showToast({ title: '上传失败', icon: 'none' })
return [];
}
};
// 将 File 对象读取为 ArrayBuffer 的辅助函数
const readFileAsArrayBuffer = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsArrayBuffer(file);
});
};

View File

@@ -19,6 +19,13 @@ export function formatLevel(type){
}
// 任务(巡检)状态 1=未发布 2=已发布 3 进行中or执行中 4 已完成 5 已过期
export const taskStatusOptions =[
{value:1,label:'未发布'},
{value:2,label:'已发布'},
{value:3,label:'执行中'},
{value:4,label:'已完成'},
{value:5,label:'已过期'},
]
export function formatTaskStatus(status){
const result = {
1:'未发布',
@@ -29,3 +36,17 @@ export function formatTaskStatus(status){
}
return result[status];
}
// 巡检类型 1 日常任务 2 临时任务
export const taskTypeOptions =[
{value:0,label:'全部类型'},
{value:1,label:'日常巡检'},
{value:2,label:'临时巡检'},
]
export function formatTaskType(status){
const result = {
1:'日常巡检',
2:'临时巡检',
}
return result[status];
}