34 Commits

Author SHA1 Message Date
xuli
32c78e0a82 nfc验证修改 2026-01-28 11:40:06 +08:00
xuli
a1835abdc8 修改样式 2026-01-22 09:35:46 +08:00
xuli
239039f712 修改样式 2026-01-21 18:05:18 +08:00
xuli
04356796fa 增加问题主动上报内容 2026-01-21 17:50:38 +08:00
xuli
5aa7498225 巡检增加主动上报页面 2026-01-16 15:57:57 +08:00
PC-202311141343\Administrator
d4d37ee13b 补充提交 2026-01-13 10:54:57 +08:00
xuli
954c1baa96 增加网络 异常提示 2026-01-13 10:49:01 +08:00
jiangyanshan
4355121fff 任务计划添加 2026-01-09 11:39:49 +08:00
jiangyanshan
340c5cf6ef 任务计划添加 2026-01-09 11:37:46 +08:00
xuli
0715c338f4 巡检注释打开 2026-01-04 13:14:21 +08:00
jiangyanshan
a97fabbef8 请假修改 2025-12-29 14:31:38 +08:00
PC-202311141343\Administrator
9f12490fb5 提交CRM模块中任务功能初始化 2025-12-24 10:29:39 +08:00
xuli
05df7bff69 增加新 选择视频和图片的插件 2025-12-23 18:11:40 +08:00
xuli
485a9511a1 Merge branch 'develop' of http://123.57.20.168:4000/admin/ys-app into develop 2025-12-23 18:11:12 +08:00
xuli
eddd2a4bf7 增加新 选择视频和图片的插件 2025-12-23 18:10:37 +08:00
jiangyanshan
22326d4ff8 修改签到打卡接口 2025-12-23 08:39:22 +08:00
xuli
03b2f64389 修改巡检内容 2025-12-22 18:11:07 +08:00
xuli
eecfa777a8 修改成718的id 2025-12-19 17:00:14 +08:00
xuli
a1f18ade8f Merge branch 'develop' of http://123.57.20.168:4000/admin/ys-app into develop 2025-12-19 16:58:42 +08:00
xuli
3da8fe4027 修改巡检图片视频内容 2025-12-19 16:54:02 +08:00
jiangyanshan
052e616e45 Merge branch 'develop' of http://123.57.20.168:4000/admin/ys-app into develop 2025-12-19 08:29:49 +08:00
jiangyanshan
1f2bb96ef3 修改请假休假接口路径 2025-12-19 08:29:39 +08:00
xuli
d32152912a 修改 下拉刷新 2025-12-18 17:59:09 +08:00
xuli
a55cdb8121 修改巡检样式 2025-12-18 17:58:21 +08:00
xuli
26430aea3d Merge branch 'develop' of http://123.57.20.168:4000/admin/ys-app into develop 2025-12-18 17:57:16 +08:00
xuli
bfbb1a1cf4 修改巡检内容 2025-12-18 17:57:09 +08:00
jiangyanshan
dc9183664c 修改签到打卡 2025-12-18 11:16:44 +08:00
jiangyanshan
c36aa01f0a 修改签到打卡 2025-12-18 10:59:40 +08:00
jiangyanshan
a0f783fc5a Merge branch 'develop' of http://123.57.20.168:4000/admin/ys-app into develop 2025-12-17 14:18:55 +08:00
jiangyanshan
7b0a2472a3 修改签到打卡 2025-12-17 14:17:10 +08:00
xuli
78275cdc07 巡检注释 2025-12-17 09:41:22 +08:00
xuli
afb6d36c57 注释掉相关巡检 2025-12-17 09:23:31 +08:00
xuli
47618273ea 增加主动调用通知 2025-12-15 14:09:48 +08:00
xuli
b84d395b43 修改样式 2025-12-12 18:05:37 +08:00
56 changed files with 5320 additions and 502 deletions

9
package-lock.json generated
View File

@@ -30,6 +30,7 @@
"minio": "^8.0.6",
"minio-js": "^1.0.7",
"pinia": "2.0.20",
"uniapp-video-player": "^1.3.0",
"uuid": "^11.1.0",
"vue": "3.4.21",
"vue-i18n": "9.14.5"
@@ -12288,6 +12289,14 @@
"license": "MIT",
"peer": true
},
"node_modules/uniapp-video-player": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/uniapp-video-player/-/uniapp-video-player-1.3.0.tgz",
"integrity": "sha512-WZa/U836ou053pzqijL2EzDrN1VX2e7Dj4R/xSMKzhcotaTqTLTc9xL832xkgwPDFmL5MEecGuyPsw2sE4hutA==",
"peerDependencies": {
"vue": "^2.6.0 || ^3.0.0"
}
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",

View File

@@ -62,6 +62,7 @@
"minio": "^8.0.6",
"minio-js": "^1.0.7",
"pinia": "2.0.20",
"uniapp-video-player": "^1.3.0",
"uuid": "^11.1.0",
"vue": "3.4.21",
"vue-i18n": "9.14.5"

View File

@@ -216,7 +216,7 @@ export function addMapForReport(data) {
//新增请假申请
export function addLeaveApply(data) {
return request.post({
url: "/crm/jys/app/appLeaveApply/add",
url: "/crm/app/appLeaveApply/add",
data,
},{
isTransformResponse:false
@@ -226,7 +226,7 @@ export function addLeaveApply(data) {
//我的表单
export function getLeaveApplyList(data) {
return request.get({
url: "/crm/jys/app/appLeaveApply/list",
url: "/crm/app/appLeaveApply/list",
data
},{
isTransformResponse:false
@@ -236,7 +236,7 @@ export function getLeaveApplyList(data) {
//可销假表单
export function getBackList(data) {
return request.get({
url: "/crm/jys/app/appLeaveApply/getBackList",
url: "/crm/app/appLeaveApply/getBackList",
data
},{
isTransformResponse:false
@@ -246,7 +246,7 @@ export function getBackList(data) {
//我的审批
export function getMyReviewList(data) {
return request.get({
url: "/crm/jys/app/appLeaveApply/myReview",
url: "/crm/app/appLeaveApply/myReview",
data
},{
isTransformResponse:false
@@ -256,7 +256,7 @@ export function getMyReviewList(data) {
//可销假表单
export function getBackLeaveList(data) {
return request.get({
url: "/crm/jys/app/appLeaveApply/myBackLeave",
url: "/crm/app/appLeaveApply/myBackLeave",
data
},{
isTransformResponse:false
@@ -266,7 +266,7 @@ export function getBackLeaveList(data) {
//提交审批
export function addReviewLeave(data) {
return request.post({
url: "/crm/jys/app/appLeaveApply/addReview",
url: "/crm/app/appLeaveApply/addReview",
data,
},{
isTransformResponse:false
@@ -276,7 +276,7 @@ export function addReviewLeave(data) {
//新增销假申请
export function addBackApply(data) {
return request.post({
url: "/crm/jys/app/appLeaveApply/addBackApply",
url: "/crm/app/appLeaveApply/addBackApply",
data,
},{
isTransformResponse:false
@@ -286,7 +286,7 @@ export function addBackApply(data) {
//可销假表单
export function getDelLeaveApply(data) {
return request.get({
url: "/crm/jys/app/appLeaveApply/delLeaveApply",
url: "/crm/app/appLeaveApply/delLeaveApply",
data
},{
isTransformResponse:false

View File

@@ -173,3 +173,56 @@ export function crmMarketInformationApprovalUnSuccess(data) {
isTransformResponse:false
})
}
//任务清单
export function SalesManTaskList(data) {
return request.get({
url: '/crm/app/schedulerTask/SalesManTaskList',
data
},{
isTransformResponse:false
})
}
//主线任务列表
export function SalesManTaskDetailList(data) {
return request.get({
url: '/crm/app/schedulerTask/SalesManTaskDetailList',
data
},{
isTransformResponse:false
})
}
//任务计划新增
export function taskPlanAdd(data) {
return request.post({
url: '/crm/app/schedulerTask/add',
data
},{
isTransformResponse:false
})
}
//任务计划查看
export function TaskPlanList(data) {
return request.get({
url: '/crm/app/schedulerTask/TaskPlanList',
data
},{
isTransformResponse:false
})
}
//我的审批
export function getMyReviewList(data) {
return request.get({
url: "/crm/app/schedulerTask/myReview",
data
},{
isTransformResponse:false
});
}

View File

@@ -125,4 +125,57 @@ export function querytodaytasknum(data) {
}
/**主动上报 begin */
// 详情
export function patrolBugDetail(data) {
return request.post({
url: '/patrol/patrolBug/detail',
data
})
}
// 查询异常上报表列表
export function patrolBugList(data) {
return request.post({
url: '/patrol/patrolBug/list',
data
})
}
// 添加问题上报
export function patrolBugAdd(data) {
return request.post({
url: '/patrol/patrolBug/addBug',
data
})
}
// 编辑问题
export function patrolBugEdit(data) {
return request.post({
url: '/patrol/patrolBug/editBug',
data
})
}
// 删除问题
export function patrolBugDel(data) {
return request.post({
url: '/patrol/patrolBug/delBug',
data
})
}
// 添加追踪记录
export function patrolBugAddLog(data) {
return request.post({
url: '/patrol/patrolBug/addBuglog',
data
})
}
/**主动上报 end */
// 验证NFC扫描是否正确
export function patroltaskValidateNfc(data) {
return request.post({
url: '/patrol/patroltask/validateNfc',
data
})
}

View File

@@ -4,7 +4,7 @@
<view class="nfc-title">
<uni-icons type="closeempty" size="20" class="nfc-close" @click="handleClose"></uni-icons>NFC识别
</view>
<block v-if="formData.deviceId==''">
<block v-if="!deviceId">
<view class="nfc-pic">
<img class="nfc-pic-animal" :src="'static/images/polling/nfc-logo.png'" />
</view>
@@ -40,16 +40,20 @@
</template>
<script setup>
import { ref, onBeforeUnmount,onMounted } from "vue";
import { ref, onBeforeUnmount,onMounted,getCurrentInstance } from "vue";
import { parseTime } from '@/utils/datetime.js';
import { patroltaskValidateNfc } from '@/api/polling.js'
const { proxy } = getCurrentInstance();
const props = defineProps({
nfcId:{},
})
// 定义组件 emits
const emit = defineEmits(['close',"changeNfc"]);
// 状态管理
const formData = ref({
deviceId: "", //录入的id
});
const deviceId = ref(undefined);
const readStatus=ref(false);//读取状态 true 成功
// 变量初始化
@@ -81,7 +85,8 @@ function open() {
});
return;
}
deviceId.value=undefined;
readStatus.value=false;
initNFC();
} catch (e) {
console.error("打开NFC失败:", e);
@@ -177,7 +182,7 @@ function handleNewIntent() {
}
// 读取NFC数据
function readNFCData() {
async function readNFCData() {
try {
const main = plus.android.runtimeMainActivity();
const _intent = main.getIntent();
@@ -193,9 +198,23 @@ function readNFCData() {
function bytesToString(result) {
return String.fromCharCode(...result);
}
formData.value.deviceId = bytesToString(result).substring(3);
emit("changeNfc", formData.value.deviceId);
readStatus.value=true;
let nfcContent = bytesToString(result).substring(3);
console.log("nfcContent=>",nfcContent)
deviceId.value=nfcContent;
// 调用后台接口验证NFC内容的正确性
let param = {
nfcId:props.nfcId,
nfcContent
}
// console.log("param=>",param)
let res = await patroltaskValidateNfc(param);
// console.log("patroltaskValidateNfc=>",res)
if(!res){
emit("changeNfc", nfcContent);
readStatus.value=true;
}
} catch (e) {
console.log("读取NDEF数据错误:", e);
uni.showToast({ title: "读取数据失败,请重新读取", icon: "none" });

View File

@@ -184,7 +184,7 @@ const getCurrentMonth = ()=>{
padding:20rpx 0 0;
/* #endif */
width: calc(100% / 4 - 12px); /* 减去一些间隙以避免溢出 */
margin: 10rpx; /* 可选,用于添加间距 */
margin: 9rpx; /* 可选,用于添加间距 */
}
.month-item.active {

View File

@@ -0,0 +1,221 @@
<template>
<!-- 自定义底部弹窗选择器 -->
<view class="picker-model" v-if="showPicker">
<view class="picker-content">
<view class="picker-item" @click="takePhoto">
<text>拍照</text>
</view>
<!-- <view class="picker-item" @click="choosePhoto">
<text>选择照片</text>
</view> -->
<view class="picker-item" @click="takeVideo">
<text>拍摄视频</text>
</view>
<!-- <view class="picker-item" @click="chooseVideoFromAlbum">
<text>选择视频</text>
</view> -->
<view class="picker-item" @click="closePicker">
<text>取消</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, watch } from "vue";
import {compressImageUni} from '@/utils/common.js'
import { minioUpload } from '@/api/polling.js'
let showPicker = ref(false);
const openPicker = () => {
showPicker.value = true;
}
const closePicker = () => {
showPicker.value = false;
emit("closeMedia")
}
const takePhoto = () => {
captureMedia("image", "camera");
}
// const choosePhoto = () => {
// captureMedia("image", "album");
// }
const takeVideo = () => {
captureMedia("video", "camera");
}
// const chooseVideoFromAlbum = () => {
// captureMedia("video", "album");
// }
const captureMedia = async (type, source) => {
showPicker.value = false;
try {
if (type === "image") {
const res = await uni.chooseImage({
count: 1,
sourceType: [source],
});
processMedia(res.tempFilePaths, "image", source);
} else {
const res = await uni.chooseVideo({
sourceType: [source],
// sourceType: ['album', 'camera'], // 来源:相册和相机
maxDuration: 60, // 最大时长(秒)
camera: 'back', // 使用后置摄像头
compressed: true, // 压缩视频
});
processMedia([res.tempFilePath], "video", source);
}
} catch (error) {
emit("closeMedia")
}
}
// 调用父组件的方法
const emit = defineEmits(['getMediaArr','closeMedia']);
// 执行压缩并上传
const processMedia = async(files, type, source) => {
let returnArr = [];
let res = files.map(async file=>{
return new Promise(async(resolve, reject) => {
try {
const info = {
path: file,
type: type,
source: source,
timestamp: Date.now(),
}
// #ifdef APP-PLUS
// 进行压缩图片
if(type=="image"){
info.path = await compressImageUni(file);
}
// #endif
// 保存选择记录
let data = await saveSelectionRecord(info);
returnArr.push({
fileName:data.fileName,
fileType:type
})
resolve(returnArr)
} catch (error) {
reject(error)
}
});
})
await Promise.all(res);
// console.log("returnArr=>",returnArr)
showPicker.value = false;
emit('getMediaArr',returnArr);
// 根据来源做不同处理
// if (source === "camera") {
// handleCameraMedia(info);
// } else {
// handleAlbumMedia(info);
// }
}
// 保存到云
const saveSelectionRecord = (info) => {
return new Promise(async (resolve, reject) => {
try {
// 执行上传
let param = {
filePath: info.path,
name: 'file',
formData: {
directory:'polling'
},
}
let res = await minioUpload(param);
resolve(res.data);
} catch (error) {
resolve(error);
}
// 保存到本地存储
// const records = uni.getStorageSync("mediaRecords") || [];
// records.unshift(info);
// uni.setStorageSync("mediaRecords", records.slice(0, 10)); // 只保留最近10条
})
}
const handleCameraMedia = (info) => {
// 从相机拍摄的媒体文件处理
if (info.type === "image") {
// 相机拍摄的照片可能有地理位置信息
extractImageMetadata(info.path);
} else {
// 相机拍摄的视频
uni.showToast({
title: "已保存拍摄的视频",
icon: "success",
});
}
}
const handleAlbumMedia = (info) => {
// 从相册选择的媒体文件处理
console.log("从相册选择:", info);
}
const extractImageMetadata = (filePath) => {
// 获取图片元数据(部分平台支持)
uni.getImageInfo({
src: filePath,
success: (res) => {
console.log("图片元数据:", res);
},
});
}
// 暴露方法给父组件
defineExpose({
openPicker
});
</script>
<style lang="scss" scoped>
.picker-model{
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 9999;
.picker-content{
width:100%;
position: absolute;
bottom:0;
left:0;
background-color: #fff;
border-radius: 10rpx 10rpx 0 0;
.picker-item{
border-bottom:1px solid #E7E7E7;
text-align: center;
height:90rpx;
line-height: 90rpx;
// #ifdef APP-PLUS
height:120rpx;
line-height: 120rpx;
// #endif
&:last-child{
border-bottom: none;
}
text{
text-align: center;
}
}
}
}
</style>

View File

@@ -68,7 +68,7 @@ const props = defineProps({
searchKeywords:{//搜索文本
type:String
},
searchType:{//哪种类型显示对象 typeId:1-首页2-业务首页3-消息 之后可以自动添加
searchType:{//哪种类型显示对象 typeId:1-首页2-业务首页3-消息, 4-问题,5-上报问题 之后可以自动添加
type:Object
},
searchTypeList:{//类型列表

View File

@@ -1,6 +1,9 @@
<template>
<!-- 放大后的遮罩层 -->
<view v-if="isEnlarged" class="image-preview-overlay" @click="handleClose">
<view v-if="isEnlarged" class="image-preview-overlay">
<view class="image-preview-close" @click="handleClose">
<uni-icons type="closeempty" size="18" color="#fff"></uni-icons>
</view>
<block v-if="getFileType(mediaUrl)=='image'">
<image
:src="mediaUrl"
@@ -10,12 +13,24 @@
</block>
<view :class="videoClass" v-else-if="getFileType(mediaUrl)=='video'">
<!-- object-fit="cover" -->
<video :src="mediaUrl" controls @loadedmetadata="onVideoLoaded"></video>
<!-- <video :src="mediaUrl" controls @loadedmetadata="onVideoLoaded"></video> -->
<!--
src: '' // 视频地址
autoplay: false // 是否自动播放
loop: false // 是否循环播放
controls: false // 是否显示控制栏
muted: false // 是否静音
isLoading: false // Android系统加载时显示loading(为了遮挡安卓的黑色按钮)
objectFit: 'contain' // 视频尺寸与video区域的适应模式
poster: '' // 视频封面
-->
<DomVideoPlayer :src="mediaUrl" controls autoplay />
</view>
</view>
</template>
<script setup>
import DomVideoPlayer from 'uniapp-video-player'
import { ref,watch } from 'vue';
import {getFileType} from '@/utils/common.js';
const props = defineProps({
@@ -54,7 +69,7 @@ const onVideoLoaded = (e) => {
let w = e.detail.width;
let h = e.detail.height;
// 你也可以在这里进行后续操作比如根据宽高比调整UI
console.log(w,h)
// console.log(w,h)
if(h>w){
videoClass.value="enlarged-image"
}else{
@@ -77,6 +92,21 @@ const onVideoLoaded = (e) => {
align-items: center;
z-index: 10000;
}
.image-preview-overlay .image-preview-close{
position: absolute;
width:50rpx;
height:50rpx;
line-height: 50rpx;
text-align: center;
border-radius: 50%;
right:20rpx;
top:20rpx;
/* #ifdef APP-PLUS */
top:44rpx;
/* #endif */
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
}
:deep(uni-video){
width:100%;

View File

@@ -18,4 +18,9 @@ export const RequestCodeEnum = {
export const RequestErrMsgEnum = {
ABORT: 'request:fail abort',
TIMEOUT: 'request:fail timeout'
};
};
// 特殊接口不弹默认窗口处理
export const RequestUrlAlertEum=[
'/patrol/patroltask/validateNfc'
]

View File

@@ -3,7 +3,7 @@
"name" : "718友晟",
"appid" : "__UNI__0B682E1",
"description" : "",
"versionName" : "1.0.2",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
@@ -62,7 +62,9 @@
"<uses-permission android:name=\"android.permission.RESTART_PACKAGES\" />",
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />",
"<uses-permission android:name=\"android.permission.NFC\"/>",
"<uses-feature android:name=\"android.hardware.nfc\" android:required=\"true\"/>"
"<uses-feature android:name=\"android.hardware.nfc\" android:required=\"true\"/>",
"<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\"/>"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
"minSdkVersion" : 23,
@@ -86,22 +88,22 @@
"unipush" : {
"version" : "2",
"offline" : true,
"icons" : {
"push" : {
"ldpi" : "/static/images/icon.png",
"mdpi" : "/static/images/icon.png",
"hdpi" : "/static/images/icon.png",
"xhdpi" : "/static/images/icon.png",
"xxhdpi" : "/static/images/icon.png"
},
"small" : {
"ldpi" : "/static/images/icon_small.png",
"mdpi" : "/static/images/icon_small.png",
"hdpi" : "/static/images/icon_small.png",
"xhdpi" : "/static/images/icon_small.png",
"xxhdpi" : "/static/images/icon_small.png"
}
}
"icons" : {
"push" : {
"ldpi" : "/static/images/icon.png",
"mdpi" : "/static/images/icon.png",
"hdpi" : "/static/images/icon.png",
"xhdpi" : "/static/images/icon.png",
"xxhdpi" : "/static/images/icon.png"
},
"small" : {
"ldpi" : "/static/images/icon_small.png",
"mdpi" : "/static/images/icon_small.png",
"hdpi" : "/static/images/icon_small.png",
"xhdpi" : "/static/images/icon_small.png",
"xxhdpi" : "/static/images/icon_small.png"
}
}
}
}
}
@@ -154,3 +156,5 @@
"template" : "index.html"
}
}
/* ios */

View File

@@ -663,6 +663,30 @@
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemInitiativeList",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemInitiative",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemInitiativeDetail",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/problemInitiativeLog",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/polling/nfcTest/index",
"style": {
@@ -730,6 +754,49 @@
{
"path": "pages/business/CRM/leave/reviewDetail",
"style": {
"navigationBarTitleText": ""
}
},
//************************任务板块页面
{
"path": "pages/business/CRM/scheduler/index",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/CRM/scheduler/taskPlanAdded",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/CRM/scheduler/taskListViewing",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/CRM/scheduler/taskViewingDetail",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/CRM/scheduler/taskPlanList",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/business/CRM/scheduler/taskPlanReview",
"style": {
"navigationBarTitleText": ""
}

View File

@@ -111,23 +111,17 @@ onMounted(() => {
onShow(() => {
uni.$on('isRefresh', function () {
isRefresh.value = true
getApplyList();
getlist();
})
})
function getApplyList() {
getlist({
applyUserName: searchValue.value
})
}
let isRefresh = ref(false)
let searchValue = ref(null)
// 查询搜索跳转
let handleSearch = () => {
isRefresh.value = true
getApplyList();
getlist();
}
const pageSize = ref(4);
@@ -162,10 +156,12 @@ let passList = ref([])
//已审批
let reviewList = ref([])
const getlist = async (param) => {
const getlist = async () => {
loading.value = true
let res = await getMyReviewList(param);
let res = await getMyReviewList({
applyUserName: searchValue.value
});
detailLists.value = res.rows;
reviewListA.value = res.rows.filter(t => (t.status == '待审批' || t.status == '审批中') && t
.applyType == '请假' && t.reviewerIdR == null)

View File

@@ -8,12 +8,19 @@
<view class="top-height" :style="{ paddingTop: navBarPaddingTop + 'px' }"></view>
<view class="inner-box">
<view class="cu-form-group">
<textarea v-model="form.remark" placeholder="请输入签到备注内容" name="input"></textarea>
</view>
<view class="btn-box">
<view class="cu-form-group" style="height: 200px;padding:10px;">
<uni-forms ref="formRef" :model="form" label-width="100px">
<uni-forms-item label="签到备注内容" name="understandTheWay"
class="uni-forms-item is-direction-top is-top">
<uni-easyinput type="textarea" autoHeight v-model="form.remark" placeholder="请输入签到备注内容"
class="form-texarea" />
</uni-forms-item>
</uni-forms>
<view class="btn-box" style="padding-top: 50px;">
<button type="primary" @click="addInsertMapClockIn">签到</button>
</view>
</view>
</view>
</view>
</view>
@@ -27,20 +34,20 @@ import { parseTime } from '@/utils/datetime.js';
import { onLoad } from '@dcloudio/uni-app';
import { getNavBarPaddingTop } from '@/utils/system.js'
let form = reactive({
addressForStart: null,
addressForEnd: null,
createId: null,
staffName: null,
visistCode: null,
visistId: null,
mapId: null,
remark: null
})
addressForStart: null,
addressForEnd: null,
createId: null,
staffName: null,
visistCode: null,
visistId: null,
mapId: null,
remark: null
})
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
})
onLoad(option => {

View File

@@ -28,7 +28,7 @@
:content="form.addressForStart"
closeTip="轻触空白处关闭"
cancelText="出差"
confirmText="打卡"
confirmText="未出差"
contentAlign="center"
@confirm="handleConfirm"
@cancel="handleCancel"/>

View File

@@ -0,0 +1,118 @@
<template>
<view class="con-body">
<view class="con-bg">
<customHeader ref="customHeaderRef" :title="'任务版块'" :leftFlag="true">
</customHeader>
<!-- #ifdef H5 -->
<view style="height:50rpx"></view>
<!-- #endif -->
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<view class="white-bg">
<navigator url="/pages/business/CRM/scheduler/taskListViewing">
<view class="list-item item-padding">
<img class="l-icon" :src="'static/images/business/icon-zfsp.png'"/>
<text>任务清单查看</text>
<view class="list-right">
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</view>
</navigator>
<view class="item-border"></view>
<navigator url="/pages/business/CRM/scheduler/taskPlanList">
<view class="list-item item-padding">
<img class="l-icon" :src="'static/images/business/icon-jhck.png'"/>
<text>任务计划查看</text>
<view class="list-right">
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</view>
</navigator>
<view class="item-border"></view>
<navigator url="/pages/business/CRM/scheduler/taskPlanReview">
<view class="list-item item-padding">
<img class="l-icon" :src="'static/images/business/icon-khryss.png'"/>
<text>任务计划审批</text>
<view class="list-right">
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</view>
</navigator>
</view>
</view>
</view>
</template>
<script setup>
import {ref, onMounted} from 'vue'
import customHeader from '@/components/customHeader.vue'
import {getNavBarPaddingTop} from '@/utils/system.js'
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
})
</script>
<style scoped>
.head-pic .head-right img {
display: block;
width: 28rpx;
height: 25rpx;
margin-left: auto;
margin-top: 2px;
}
.backlog-b-item img {
width: 90rpx;
height: 90rpx;
margin: 0 auto 10rpx;
display: block;
}
.white-bg {
width: 590rpx; /*690*/
padding: 30rpx 50rpx;
}
.list-item {
display: flex;
align-items: center;
font-size: 32rpx;
border-radius: 8rpx;
}
.list-item.item-padding {
padding: 45rpx 0;
}
.list-item.item-padding:first-child {
padding-top: 20rpx;
}
.list-item.item-padding:last-child {
padding-bottom: 20rpx;
}
.item-border {
height: 1px;
background-color: #E7E7E7;
width: 640rpx;
}
.list-item .l-icon {
width: 90rpx;
height: 90rpx;
margin-right: 20rpx;
}
.list-item .list-right {
margin-left: auto;
display: flex;
align-items: center;
}
</style>

View File

@@ -0,0 +1,412 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" :title="!searchShow ? '任务清单查看' : '搜索'"
:leftFlag="true" :rightFlag="true" @back="handleBack"
:searchType="searchShow ? 1 : undefined">
<!-- <template #right v-if="!searchShow">
<view class="head-right" @click="handleRead">
<img :src="'static/images/icon-clean@2x.png'" />清除未读
</view>
</template> -->
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 搜索处理 -->
<customSearch v-if="searchShow" :searchKeywords="searchText" :searchType="searchTypeObj"
:checkTypeObj="notictTypeCheck" @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">
<input class="uni-input" v-model="inputval" 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" textColor="#ffffff" bgColor="#ffffff" class="scroll-h"
:class="{ 'loading-scroll': cssFlag }">
<view class="white-bg margin-bottom20" v-for="(item, index) in list" :key="index"
@click="showDetail(item)">
<view>
<view class="report-list">
<view class="title" style="font-size: 30rpx">
任务名称{{ item.taskName }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
主线任务类型{{ item.mainTaskName }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
计划开始时间{{ formatDateStr(item.mainTaskStartTime) }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
计划结束时间{{ formatDateStr(item.mainTaskEndTime) }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
是否超期{{ item.mainTaskIsOverdue }}
</view>
</view>
</view>
</view>
</mescroll-uni>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
watch
} from 'vue'
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 {
SalesManTaskList
} from '@/api/crm/api_ys.js'
import {
getDate
} from '@/utils/datetime.js'
import {
onLoad,
onShow,
onUnload,
onHide
} from '@dcloudio/uni-app'
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
uni.$on('updateStatus', markVisited)
})
// 搜索处理
let searchShow = ref(false);
let searchText = ref(undefined);
let searchTypeObj = ref({
typeId: 3,
typeName: '消息类型'
});
let notictTypeCheck = ref({}); //选中类型
// 查询列表
let list = ref([]);
// 新增状态变量存储搜索条件
const inputval = ref(''); // 搜索内容
let searchValue = ref(null)
//监视查询的内容的变化
watch(searchValue, (newValue, oldValue) => {
//变化了之后,重新查询内容
var data = {
pageNum: 1,
pageSize: 10,
};
SalesManTaskList(data).then(res => {
if (res.code == 200) {
//设置列表数据
list.value = res.rows;
}
})
})
onHide(() => {
searchShow.value = false;
})
// 搜索返回操作
const handleBack = () => {
searchShow.value = false;
}
// 获取input 焦点跳转
const handleSearchFocus = () => {
searchShow.value = true;
}
const formatDateStr = (times) => {
const date = new Date(times);
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
month = month > 9 ? month : '0' + month;;
day = day > 9 ? day : '0' + day;
return `${year}-${month}-${day}`;
}
// 搜索完返回处理
const handleSearchConfirm = (param1, param2) => {
// console.log(param1,param2)
notictTypeCheck.value = param1.value;
inputval.value = param2.value || '';
searchValue.value = param2.value;
// 重置mescroll触发刷新
if (mescrollRef.value) {
mescrollRef.value.resetUpScroll();
}
searchShow.value = false;
}
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: true,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
let cssFlag = ref(false); //控制样式
const mescrollInit = (mescroll) => {
cssFlag.value = true;
mescrollRef.value = mescroll;
};
// 下拉刷新
const downCallback = async (mescroll) => {
try {
setTimeout(async () => {
const res = await getSalesManTaskList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
}, 500);
} catch (error) {
mescroll.endErr();
} finally {
setTimeout(async () => {
mescroll.endSuccess();
}, 500);
}
}
// 上拉加载更多
const upCallback = async (mescroll) => {
try {
setTimeout(async () => {
const res = await getSalesManTaskList(mescroll.num, mescroll.size,
inputval.value);
if (mescroll.num === 1) {
list.value = res.list;
} else {
list.value.push(...res.list);
}
mescroll.endBySize(res.list.length, res.total);
}, 500);
} catch (error) {
mescroll.endErr();
}
}
// 获取数据列表
const getSalesManTaskList = (pageNum, pageSize) => {
return new Promise(async (resolve) => {
let param = {
pageNum,
pageSize,
// 添加搜索条件参数
taskName: inputval.value
}
let res = await SalesManTaskList(param);
resolve({
list: res.rows,
total: res.total
});
});
}
function showDetail(item) {
uni.navigateTo({
url: "/pages/business/CRM/scheduler/taskViewingDetail?data=" + encodeURIComponent(
JSON
.stringify(item))
})
}
onUnload(() => {
uni.$off('updateStatus')
})
const markVisited = (informationId) => {
const newList = [...list.value].map(item => {
if (item.informationId == informationId) {
return {
...item,
myselfBrowsing: 1
};
}
return item;
});
list.value = newList;
};
</script>
<style scoped>
.all-body {
/* #ifdef APP-PLUS */
top: 150rpx;
height: calc(100vh - 75px);
/* #endif */
/* #ifndef APP-PLUS */
top: 120rpx;
height: calc(100vh);
/* #endif */
}
.search {
display: flex;
}
.search .btn-search {
border: none;
background: none;
line-height: normal;
color: #fff;
line-height: 56rpx !important;
padding: 10rpx 0 0;
text-align: left;
cursor: pointer;
}
.search .btn-search::after {
display: none;
}
.search .custom-search {
width: 80%;
}
.search .custom-search.uni-searchbar {
padding-right: 0 !important;
}
.scroll-h {
/* #ifdef APP-PLUS */
height: calc(100vh - 120px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 110px);
/* #endif */
}
.white-bg {
padding-bottom: 10rpx;
}
.search_center {
display: flex;
align-items: center;
border: none;
flex: 1;
height: 70rpx;
margin-left: 20rpx;
padding-left: 20rpx;
border-radius: 32.5rpx;
background-color: #f5f5f5;
.search_icon {
width: 30rpx;
height: 30rpx;
margin-right: 22rpx;
}
.category {
display: flex;
align-items: center;
margin-right: 16rpx;
padding-right: 16rpx;
border-right: 2rpx solid #D4D4D4;
position: relative;
text {
font-size: 13px;
margin-left: 10rpx;
margin-right: 10rpx;
}
image {
margin-top: 4rpx;
width: 20rpx;
height: 11rpx;
}
.pop {
position: absolute;
top: 50rpx;
background: #FFFFFF;
box-shadow: 0px 0px 40rpx 0px rgba(59, 59, 59, 0.2);
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
z-index: 100;
max-height: 566rpx;
overflow-y: auto;
text {
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 500;
color: #333333;
white-space: nowrap;
margin: 10rpx 0;
line-height: 56rpx;
}
}
.pop_arrow {
position: absolute;
top: 48rpx;
z-index: 999;
&::after {
position: absolute;
content: "";
left: 68rpx;
top: -26rpx;
border: 14rpx solid #fff;
border-color: transparent transparent #fff transparent;
}
}
}
}
</style>

View File

@@ -0,0 +1,363 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" :title="'任务计划新增'" :leftFlag="true"
:rightFlag="true">
<template #right>
<view class="head-right" @click="submitForm">
<uni-icons custom-prefix="iconfont" type="icon-phonebaocun" size="22"
color="#B7D2FF"></uni-icons>保存
</view>
</template>
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 正文内容 -->
<view class="white-bg">
<view class="form-con">
<uni-forms ref="formRef" :model="formData" :rules="rules" label-width="100px">
<uni-forms-item label="主线任务" name="taskName" class="f-c-right">
<view class="form-item-container">
<text class="name">{{ formData.taskName}}</text>
</view>
</uni-forms-item>
<uni-forms-item label="计划内容" name="planContent" class="f-c-right">
<picker @change="onPlanContentChange" :value="planContentIndex"
:range="array" :range-key="'name'">
<view class="picker">
{{ array[planContentIndex]?.name || '请选择计划内容' }}
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</picker>
</uni-forms-item>
<uni-forms-item label="计划开始时间" name="plannedStartTime" class="f-c-right">
<picker mode="date" :value="formData.plannedStartTime"
@change="onStartTimeChange">
<view class="picker">
{{ formData.plannedStartTime || '请选择计划开始时间' }}
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</picker>
</uni-forms-item>
<uni-forms-item label="计划完成时间" name="plannedCompTime" class="f-c-right">
<picker mode="date" :value="formData.plannedCompTime"
@change="onCompTimeChange">
<view class="picker">
{{ formData.plannedCompTime || '请选择计划完成时间' }}
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</picker>
</uni-forms-item>
<uni-forms-item label="次数" name="num">
<uni-number-box v-model="formData.num"
style="float: right;margin-right: 10rpx;margin-top: 15rpx;"></uni-number-box>
</uni-forms-item>
<uni-forms-item label="频率" name="frequency" class="f-c-right">
<picker @change="onFrequencyChange" :value="frequencyIndex"
:range="arrayFrequency" :range-key="'name'">
<view class="picker">
{{ arrayFrequency[frequencyIndex]?.name || '请选择频率' }}
<uni-icons type="right" size="20" color="#A0A0A0"></uni-icons>
</view>
</picker>
</uni-forms-item>
<uni-forms-item label="内容描述" name="description"
class="uni-forms-item is-direction-top is-top">
<uni-easyinput type="textarea" autoHeight v-model="formData.description"
placeholder="请输入内容描述" class="form-texarea" />
</uni-forms-item>
</uni-forms>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
reactive,
onUnmounted,
computed
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
import customHeader from '@/components/customHeader.vue'
import {
isEmpty
} from '@/utils/validate.js'
import {
taskPlanAdd
} from '@/api/crm/api_ys.js'
// 表单数据
const formData = ref({
cusId: null,
taskName: null,
planContent: "", // 机会类型
plannedStartTime: "", // 计划开始时间
plannedCompTime: "", // 计划完成时间
description: "", // 内容描述
num: "", // 次数
})
onLoad((options) => {
if (options.data) {
let mainData = JSON.parse(decodeURIComponent(options.data))
formData.value.taskName = mainData.mainTaskName
formData.value.taskId = mainData.taskId
}
})
// 表单验证规则
const rules = {
planContent: {
rules: [{
required: true,
errorMessage: '请选择计划内容'
}]
},
plannedStartTime: {
rules: [{
required: true,
errorMessage: '请选择计划开始时间'
}]
},
plannedCompTime: {
rules: [{
required: true,
errorMessage: '请选择计划完成时间aaa'
},
{
validateFunction: function(rule, value, data, callback) {
if (value && data.plannedStartTime) {
if (new Date(value) < new Date(data.plannedStartTime)) {
callback('计划完成时间不能早于计划开始时间')
return
}
}
callback()
}
}
]
},
description: {
rules: [{
required: true,
errorMessage: '请输入内容描述'
}]
},
}
// picker 相关
const index = ref(0)
const array = ref([{
id: 0,
name: '日常走访'
},
{
id: 1,
name: '业务招待'
},
{
id: 2,
name: '技术交流'
},
{
id: 3,
name: '来厂参观'
},
{
id: 4,
name: '党建活动'
},
{
id: 5,
name: '文体活动'
},
{
id: 6,
name: '联谊活动'
},
{
id: 7,
name: '礼尚往来'
},
{
id: 8,
name: '高层交流'
},
{
id: 9,
name: '组合拳服务活动'
}
])
const arrayFrequency = ref([{
id: 0,
name: '',
value: 0
},
{
id: 1,
name: '3天1次',
value: 3
},
{
id: 2,
name: '7天1次',
value: 7
},
{
id: 3,
name: '15天1次',
value: 15
},
{
id: 4,
name: '30天1次',
value: 30
}
])
const planContentIndex = ref(0)
const frequencyIndex = ref(0)
// 表单引用 & 客户选择器引用
const formRef = ref(null)
const customHeaderRef = ref(null)
//监听时间
onMounted(() => {
// 设置机会类型的默认值
formData.value.planContent = array.value[0].name;
planContentIndex.value = 0;
frequencyIndex.value = 0;
})
// 提交表单
const submitForm = async () => {
try {
formData.value.planType = "0"
// 表单校验
await formRef.value.validate()
const res = await taskPlanAdd(formData.value);
console.log(res)
uni.showToast({
title: '提交成功',
icon: 'success'
})
uni.$emit('refreshMarketList');
setTimeout(() => {
uni.navigateBack(1);
}, 1500);
console.log('表单数据:', formData.value)
} catch (err) {
console.log('表单验证失败:', err)
}
}
const onPlanContentChange = (e) => {
planContentIndex.value = e.detail.value
formData.value.planContent = array.value[e.detail.value]?.name || ''
}
const onFrequencyChange = (e) => {
frequencyIndex.value = e.detail.value
formData.value.frequency = arrayFrequency.value[e.detail.value]?.value || null
}
// 计划开始时间变化事件
const onStartTimeChange = (e) => {
const value = e.detail.value
formData.value.plannedStartTime = value
// 如果已选择完成时间,重新校验完成时间
// Vue3 中需要手动触发验证更新
if (formRef.value) {
// 方法1使用 setValue
formRef.value.setValue('plannedStartTime', value)
if (formData.value.plannedCompTime) {
formRef.value.validateField('plannedCompTime')
}
}
}
// 计划完成时间变化事件
const onCompTimeChange = (e) => {
const value = e.detail.value
formData.value.plannedCompTime = value
// Vue3 中需要手动触发验证更新
if (formRef.value) {
// 方法1使用 setValue
formRef.value.setValue('plannedCompTime', value)
// 校验完成时间
formRef.value.validateField('plannedCompTime')
}
}
// 如果你原来在 onShow 中做了类似这样:
// let res = currPage.data.cusData; 判断是否传入了客户信息
// 那么在 Vue3 中通常是通过路由参数或者 Vuex/Pinia 等状态管理获取
// 暂时不做,如你后续需要可继续补充
</script>
<style scoped>
.white-bg {
width: 750rpx;
padding: 30rpx 0 0;
margin-bottom: 0;
border-radius: 8px 8px 0 0;
}
.form-con {
/* #ifdef APP-PLUS */
min-height: calc(100vh - 100px);
/* #endif */
/* #ifndef APP-PLUS */
min-height: calc(100vh - 80px);
/* #endif */
}
:deep(.uni-date-x) {
display: block;
}
:deep(.uni-date-x .icon-calendar) {
float: right;
margin-top: 15rpx;
margin-right: 20rpx;
background: url('../../../static/images/business/icon-date.png') no-repeat;
background-size: 32rpx 35rpx;
width: 32rpx;
height: 35rpx;
}
:deep(.uni-date-x .icon-calendar::before) {
display: none;
}
:deep(.uni-date-x .uni-date__x-input) {
padding-left: 20rpx;
color: #919191;
}
</style>

View File

@@ -0,0 +1,413 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" :title="!searchShow ? '任务计划查看' : '搜索'"
:leftFlag="true" :rightFlag="true" @back="handleBack"
:searchType="searchShow ? 1 : undefined">
<!-- <template #right v-if="!searchShow">
<view class="head-right" @click="handleRead">
<img :src="'static/images/icon-clean@2x.png'" />清除未读
</view>
</template> -->
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 搜索处理 -->
<customSearch v-if="searchShow" :searchKeywords="searchText" :searchType="searchTypeObj"
:checkTypeObj="notictTypeCheck" @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">
<input class="uni-input" v-model="inputval" 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" textColor="#ffffff" bgColor="#ffffff" class="scroll-h"
:class="{ 'loading-scroll': cssFlag }">
<view class="white-bg margin-bottom20" v-for="(item, index) in list" :key="index"
@click="showDetail(item)">
<view>
<view class="report-list">
<view class="title" style="font-size: 30rpx">
主线任务{{ item.taskName }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
计划内容{{ item.planContent }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
计划开始时间{{ item.plannedStartTime }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
计划结束时间{{ item.plannedCompTime }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
次数{{ item.num }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
频率提醒{{ item.reminderCount }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
完成状态{{ item.completionCount }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
是否超时{{ item.planStatus }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
审批状态
<label v-if="item.approvalStatus == '0'" style="color: #cf0000;">{{ item.approvalStatus }}</label>
<label v-else-if="item.approvalStatus == '1'" style="color: #00cf00;">{{ item.approvalStatus }}</label>
<label v-else style="color: #60a0ff;">{{ item.approvalStatus }}</label>
</view>
</view>
</view>
</view>
</mescroll-uni>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
watch
} from 'vue'
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 {
TaskPlanList
} from '@/api/crm/api_ys.js'
import {
onLoad,
onShow,
onUnload,
onHide
} from '@dcloudio/uni-app'
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
uni.$on('updateStatus', markVisited)
})
// 搜索处理
let searchShow = ref(false);
let searchText = ref(undefined);
let searchTypeObj = ref({
typeId: 3,
typeName: '消息类型'
});
let notictTypeCheck = ref({}); //选中类型
// 查询列表
let list = ref([]);
// 新增状态变量存储搜索条件
const inputval = ref(''); // 搜索内容
let searchValue = ref(null)
//监视查询的内容的变化
watch(searchValue, (newValue, oldValue) => {
//变化了之后,重新查询内容
var data = {
pageNum: 1,
pageSize: 10,
};
TaskPlanList(data).then(res => {
if (res.code == 200) {
//设置列表数据
list.value = res.rows;
}
})
})
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;
inputval.value = param2.value || '';
searchValue.value = param2.value;
// 重置mescroll触发刷新
if (mescrollRef.value) {
mescrollRef.value.resetUpScroll();
}
searchShow.value = false;
}
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: true,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
let cssFlag = ref(false); //控制样式
const mescrollInit = (mescroll) => {
cssFlag.value = true;
mescrollRef.value = mescroll;
};
// 下拉刷新
const downCallback = async (mescroll) => {
try {
setTimeout(async () => {
const res = await getTaskPlanList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
}, 500);
} catch (error) {
mescroll.endErr();
} finally {
setTimeout(async () => {
mescroll.endSuccess();
}, 500);
}
}
// 上拉加载更多
const upCallback = async (mescroll) => {
try {
setTimeout(async () => {
const res = await getTaskPlanList(mescroll.num, mescroll.size,
inputval.value);
if (mescroll.num === 1) {
list.value = res.list;
} else {
list.value.push(...res.list);
}
mescroll.endBySize(res.list.length, res.total);
}, 500);
} catch (error) {
mescroll.endErr();
}
}
// 获取数据列表
const getTaskPlanList = (pageNum, pageSize) => {
return new Promise(async (resolve) => {
let param = {
pageNum,
pageSize,
// 添加搜索条件参数
searchInput: inputval.value
}
let res = await TaskPlanList(param);
resolve({
list: res.rows,
total: res.total
});
});
}
function showDetail(item) {
uni.navigateTo({
url: "/pages/business/CRM/scheduler/taskViewingDetail?data=" + encodeURIComponent(
JSON
.stringify(item))
})
}
onUnload(() => {
uni.$off('updateStatus')
})
const markVisited = (informationId) => {
const newList = [...list.value].map(item => {
if (item.informationId == informationId) {
return {
...item,
myselfBrowsing: 1
};
}
return item;
});
list.value = newList;
};
</script>
<style scoped>
.all-body {
/* #ifdef APP-PLUS */
top: 150rpx;
height: calc(100vh - 75px);
/* #endif */
/* #ifndef APP-PLUS */
top: 120rpx;
height: calc(100vh);
/* #endif */
}
.search {
display: flex;
}
.search .btn-search {
border: none;
background: none;
line-height: normal;
color: #fff;
line-height: 56rpx !important;
padding: 10rpx 0 0;
text-align: left;
cursor: pointer;
}
.search .btn-search::after {
display: none;
}
.search .custom-search {
width: 80%;
}
.search .custom-search.uni-searchbar {
padding-right: 0 !important;
}
.scroll-h {
/* #ifdef APP-PLUS */
height: calc(100vh - 120px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 110px);
/* #endif */
}
.white-bg {
padding-bottom: 10rpx;
}
.search_center {
display: flex;
align-items: center;
border: none;
flex: 1;
height: 70rpx;
margin-left: 20rpx;
padding-left: 20rpx;
border-radius: 32.5rpx;
background-color: #f5f5f5;
.search_icon {
width: 30rpx;
height: 30rpx;
margin-right: 22rpx;
}
.category {
display: flex;
align-items: center;
margin-right: 16rpx;
padding-right: 16rpx;
border-right: 2rpx solid #D4D4D4;
position: relative;
text {
font-size: 13px;
margin-left: 10rpx;
margin-right: 10rpx;
}
image {
margin-top: 4rpx;
width: 20rpx;
height: 11rpx;
}
.pop {
position: absolute;
top: 50rpx;
background: #FFFFFF;
box-shadow: 0px 0px 40rpx 0px rgba(59, 59, 59, 0.2);
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
z-index: 100;
max-height: 566rpx;
overflow-y: auto;
text {
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 500;
color: #333333;
white-space: nowrap;
margin: 10rpx 0;
line-height: 56rpx;
}
}
.pop_arrow {
position: absolute;
top: 48rpx;
z-index: 999;
&::after {
position: absolute;
content: "";
left: 68rpx;
top: -26rpx;
border: 14rpx solid #fff;
border-color: transparent transparent #fff transparent;
}
}
}
}
</style>

View File

@@ -0,0 +1,309 @@
<template>
<view class="con-body">
<view class="con-bg">
<customHeader ref="customHeaderRef" :title="'任务计划审批'" :leftFlag="true">
</customHeader>
<!-- #ifdef H5 -->
<view style="height:50rpx"></view>
<!-- #endif -->
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<view class="white-bg">
<view class="tabs-container">
<view class="search">
<uni-search-bar class="custom-search" radius="28" placeholder="任务名称"
clearButton="auto" cancelButton="none" bgColor="#6FA2F8"
textColor="#ffffff" v-model="searchValue" />
<button type="default" @click="handleSearch" size="mini"
class="btn-search">查询</button>
</view>
<view class="tabs-scorll">
<view class="tabs-header">
<view v-for="(tab, index) in tabHeaderList" :key="index"
:class="['tab-item-main', { 'active': activeTabHeader === index }]"
@click="switchTabHeader(index)">
{{ tab }}
</view>
</view>
</view>
<view class="white-bg-2" v-for="item in detailList">
<view class="report-list" @click="showDetail(item)">
<view class="report-list">
<view class="title" style="font-size: 30rpx;margin-top: 20rpx;">
{{ item.typeval == '1' ? '全天' : item.typeval == '2' ? '前半个班次' : '后半个班次' }}
<label v-if="item.applyType == '请假'"
style="float: right;color: #03ca03;">
{{ item.applyType }}</label>
<label v-if="item.applyType == '销假'"
style="float: right;color: #eaa303;">
{{ item.applyType }}</label>
</view>
<view class="r-list">
<view class="r-left">姓名</view>
<view class="r-right">{{ item.applyUserName }} </view>
</view>
<view class="r-list">
<view class="r-left">{{ item.applyType }}日期</view>
<view class="r-right">{{ item.startDate }} {{ item.endDate }}
<label v-if="item.applyType == '请假'"
style="color: #03ca03;">
{{ item.days.replace('.0', '') }}</label>
<label v-if="item.applyType == '销假'"
style="color: #eaa303;">
{{ item.days.replace('.0', '') }}</label>
</view>
</view>
<view class="r-list">
<view class="r-left">详细说明</view>
<view v-if="item.reason && item.reason.length <= 18"
class="r-right">{{ item.reason
}}</view>
</view>
<view class="r-list" style="margin-top: -30rpx;"
v-if="item.reason && item.reason.length > 18">
<view class="r-right">{{ item.reason }}</view>
</view>
</view>
<view class="border-bottom" style="margin: 20rpx 0;"></view>
</view>
</view>
<view class="uni-pagination-box"><uni-pagination show-icon :page-size="pageSize"
:current="pageCurrent" :total="total" @change="change" /></view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
reactive
} from 'vue'
import {
onShow
} from '@dcloudio/uni-app'
import customHeader from '@/components/customHeader.vue'
import {
getNavBarPaddingTop
} from '@/utils/system.js'
import {
getMyReviewList
} from '@/api/crm/api_ys.js'
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
getlist();
})
onShow(() => {
uni.$on('isRefresh', function() {
isRefresh.value = true
getlist();
})
})
let isRefresh = ref(false)
let searchValue = ref(null)
// 查询搜索跳转
let handleSearch = () => {
isRefresh.value = true
getlist();
}
const pageSize = ref(4);
const pageCurrent = ref(1);
const loading = ref(false);
let tabHeaderList = ref(['待审核', '已审核'])
let total = ref(0);
const totalAll = ref(0);
const totalReject = ref(0);
const totalPass = ref(0);
let activeTab = ref(0);
let activeTabHeader = ref(0);
let detailList = ref([])
let detailLists = ref([])
//驳回
let rejectList = ref([])
//通过
let passList = ref([])
//待审批
let reviewList = ref([])
const getlist = async () => {
loading.value = true
let res = await getMyReviewList({
applyUserName: searchValue.value
});
detailLists.value = res.rows;
rejectList.value = res.rows.filter(t => t.status == '驳回' && t.reviewerIdR != null)
passList.value = res.rows.filter(t => t.status != '驳回' && t.reviewerIdR != null)
reviewList.value = res.rows.filter(t => t.reviewerIdR != null)
total.value = res.total
totalReject.value = rejectList.value.length
totalPass.value = passList.value.length
totalAll.value = reviewList.value.length
if (isRefresh.value == false) {
switchTabHeader(0)
}
loading.value = false
}
const switchTabHeader = (index) => {
isRefresh.value = false
activeTabHeader.value = index
change({
current: 1
})
}
// 分页触发
let change = (e) => {
pageCurrent.value = e.current
let end = pageSize.value * e.current
let start = end - pageSize.value
if (activeTabHeader.value == 0) {
detailList.value = passList.value.slice(start, end)
total.value = totalPass.value
} else if (activeTabHeader.value == 1) {
detailList.value = rejectList.value.slice(start, end)
total.value = totalReject.value
}
}
// 跳转
let showDetail = (item) => {
uni.navigateTo({
url: "/pages/business/CRM/leave/reviewDetail?data=" + encodeURIComponent(JSON
.stringify(item))
})
}
</script>
<style lang="scss" scoped>
.apply-row {
margin: 5px;
}
.border-bottom {
margin: 5px;
width: 100%;
}
.tabs-header {
display: flex;
height: 80rpx;
background: #fff;
border-bottom: 1rpx solid #eee;
}
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #666;
}
.tab-item.active {
color: #007AFF;
position: relative;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 200rpx;
height: 4rpx;
background-color: #007AFF;
}
.tab-item-main {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
color: #666;
}
.tab-item-main.active {
color: #007AFF;
position: relative;
}
.tab-item-main.active::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 300rpx;
height: 4rpx;
background-color: #007AFF;
}
:deep(.report-list .r-list) {
padding: 0.5rem 0;
}
.search {
display: flex;
}
.search .btn-search {
border: none;
background: none;
line-height: normal;
color: #666;
line-height: 56rpx !important;
padding: 10rpx 0 0;
text-align: left;
cursor: pointer;
}
.search .btn-search::after {
display: none;
}
.search .custom-search {
width: 80%;
}
.search .custom-search.uni-searchbar {
padding-right: 0 !important;
}
</style>

View File

@@ -0,0 +1,393 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" :title="!searchShow ? '主线任务列表' : '搜索'"
:leftFlag="true" :rightFlag="true" @back="handleBack"
:searchType="searchShow ? 1 : undefined">
<!-- <template #right v-if="!searchShow">
<view class="head-right" @click="handleRead">
<img :src="'static/images/icon-clean@2x.png'" />清除未读
</view>
</template> -->
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 正文内容 -->
<!-- 分页部分 -->
<mescroll-uni v-if="!searchShow" ref="mescrollRef" @init="mescrollInit"
@down="downCallback" @up="upCallback" :up="upOption" :down="downOption"
:fixed="false" textColor="#ffffff" bgColor="#ffffff" class="scroll-h"
:class="{ 'loading-scroll': cssFlag }">
<view class="white-bg margin-bottom20" v-for="(item, index) in list" :key="index">
<view>
<view class="report-list">
<view class="title" style="font-size: 30rpx">
主线任务内容{{ item.mainTaskName }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
计划开始时间{{ formatDateStr(item.mainTaskStartTime) }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
计划结束时间{{ formatDateStr(item.mainTaskEndTime) }}
</view>
<view style="display: flex;align-items: center;">
<view class="r-left"
style="width:80%;font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
完成状态{{ item.mainTaskStatus }}
</view>
<view>
<uni-icons type="plus-filled" size="30" color="#38A6FF"
@click="showDetail(item)"></uni-icons>
</view>
</view>
<view class="right"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
完成状态{{ item.mainTaskStatus }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
是否超期{{ item.mainTaskIsOverdue }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
绩效分{{ item.mainTaskScore_JX }}
</view>
<view class="r-left"
style="font-size: 25rpx;font-weight: 500;margin-top: 10rpx;">
能力分{{ item.mainTaskScore_NL }}
</view>
</view>
</view>
</view>
</mescroll-uni>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted,
watch
} from 'vue'
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 {
SalesManTaskDetailList
} from '@/api/crm/api_ys.js'
import {
onLoad,
onShow,
onUnload,
onHide
} from '@dcloudio/uni-app'
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
let mainTaskId = ref("");
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
uni.$on('updateStatus', markVisited)
})
let mainData = ref();
onLoad((options) => {
if (options.data) {
mainData = JSON.parse(decodeURIComponent(options.data))
mainTaskId.value = mainData.mainTaskId
}
})
// 搜索处理
let searchShow = ref(false);
// 查询列表
let list = ref([]);
// 新增状态变量存储搜索条件
const inputval = ref(''); // 搜索内容
onHide(() => {
searchShow.value = false;
})
// 搜索返回操作
const handleBack = () => {
searchShow.value = false;
}
// 获取input 焦点跳转
const handleSearchFocus = () => {
searchShow.value = true;
}
const formatDateStr = (times) => {
const date = new Date(times);
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
month = month > 9 ? month : '0' + month;;
day = day > 9 ? day : '0' + day;
return `${year}-${month}-${day}`;
}
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: true,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
let cssFlag = ref(false); //控制样式
const mescrollInit = (mescroll) => {
cssFlag.value = true;
mescrollRef.value = mescroll;
};
// 下拉刷新
const downCallback = async (mescroll) => {
try {
setTimeout(async () => {
const res = await getSalesManTaskDetailList(1, upOption.value.page
.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
}, 500);
} catch (error) {
mescroll.endErr();
} finally {
setTimeout(async () => {
mescroll.endSuccess();
}, 500);
}
}
// 上拉加载更多
const upCallback = async (mescroll) => {
try {
setTimeout(async () => {
const res = await getSalesManTaskDetailList(mescroll.num, mescroll
.size,
inputval.value);
if (mescroll.num === 1) {
list.value = res.list;
} else {
list.value.push(...res.list);
}
mescroll.endBySize(res.list.length, res.total);
}, 500);
} catch (error) {
mescroll.endErr();
}
}
// 获取数据列表
const getSalesManTaskDetailList = (pageNum, pageSize) => {
return new Promise(async (resolve) => {
let param = {
pageNum,
pageSize,
// 添加搜索条件参数
mainTaskId: mainTaskId.value
}
let res = await SalesManTaskDetailList(param);
resolve({
list: res.rows,
total: res.total
});
});
}
function showDetail(item) {
uni.navigateTo({
url: "/pages/business/CRM/scheduler/taskPlanAdded?data=" + encodeURIComponent(
JSON
.stringify(item))
})
}
onUnload(() => {
uni.$off('updateStatus')
})
const markVisited = (informationId) => {
const newList = [...list.value].map(item => {
if (item.informationId == informationId) {
return {
...item,
myselfBrowsing: 1
};
}
return item;
});
list.value = newList;
};
</script>
<style scoped>
.all-body {
/* #ifdef APP-PLUS */
top: 150rpx;
height: calc(100vh - 75px);
/* #endif */
/* #ifndef APP-PLUS */
top: 120rpx;
height: calc(100vh);
/* #endif */
}
.search {
display: flex;
}
.search .btn-search {
border: none;
background: none;
line-height: normal;
color: #fff;
line-height: 56rpx !important;
padding: 10rpx 0 0;
text-align: left;
cursor: pointer;
}
.search .btn-search::after {
display: none;
}
.search .custom-search {
width: 80%;
}
.search .custom-search.uni-searchbar {
padding-right: 0 !important;
}
.scroll-h {
/* #ifdef APP-PLUS */
height: calc(100vh - 120px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 110px);
/* #endif */
}
.white-bg {
padding-bottom: 10rpx;
}
.search_center {
display: flex;
align-items: center;
border: none;
flex: 1;
height: 70rpx;
margin-left: 20rpx;
padding-left: 20rpx;
border-radius: 32.5rpx;
background-color: #f5f5f5;
.search_icon {
width: 30rpx;
height: 30rpx;
margin-right: 22rpx;
}
.category {
display: flex;
align-items: center;
margin-right: 16rpx;
padding-right: 16rpx;
border-right: 2rpx solid #D4D4D4;
position: relative;
text {
font-size: 13px;
margin-left: 10rpx;
margin-right: 10rpx;
}
image {
margin-top: 4rpx;
width: 20rpx;
height: 11rpx;
}
.pop {
position: absolute;
top: 50rpx;
background: #FFFFFF;
box-shadow: 0px 0px 40rpx 0px rgba(59, 59, 59, 0.2);
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
z-index: 100;
max-height: 566rpx;
overflow-y: auto;
text {
font-size: 28rpx;
font-family: PingFang SC;
font-weight: 500;
color: #333333;
white-space: nowrap;
margin: 10rpx 0;
line-height: 56rpx;
}
}
.pop_arrow {
position: absolute;
top: 48rpx;
z-index: 999;
&::after {
position: absolute;
content: "";
left: 68rpx;
top: -26rpx;
border: 14rpx solid #fff;
border-color: transparent transparent #fff transparent;
}
}
}
}
</style>

View File

@@ -166,7 +166,7 @@ const downCallback = async (mescroll) => {
setTimeout(async ()=>{
getTaskCount();
getList();
mescroll.resetUpScroll();
// mescroll.resetUpScroll();
},500);
} catch (error) {
mescroll.endErr();

View File

@@ -31,8 +31,8 @@
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.groupFinishNum==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
<img v-if="item.beginTime" :src="'static/images/polling/icon-pending.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-start.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" />
@@ -43,7 +43,23 @@
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ parseTime(item.planTime,'{h}:{i}') }}</span></view>
<view v-if="item.beginTime">
执行时间<span class="r-gray">{{ parseTime(item.beginTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
<view v-else>
开始时间<span class="r-gray">{{ parseTime(item.planTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
任务状态<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.beginTime">执行中</block>
<block v-else>待执行</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
</view>
</view>
@@ -52,17 +68,8 @@
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.groupFinishNum<item.groupNum}">{{item.groupFinishNum}}</span>/{{item.groupNum}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</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 class="r-l-right">
完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</span>
</view>
</view>
</view>
@@ -93,8 +100,26 @@
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ parseTime(item.planTime,'{h}:{i}') }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
<view v-if="item.beginTime">
执行时间<span class="r-gray">{{ parseTime(item.beginTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
<view v-else>
开始时间<span class="r-gray">{{ parseTime(item.planTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
任务状态<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.beginTime">执行中</block>
<block v-else>待执行</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
<view class="r-l-right">
任务时长<span class="r-gray">{{ item.workHour }}小时</span>
</view>
</view>
</view>
<view class="r-list">
@@ -102,17 +127,8 @@
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.groupFinishNum<item.groupNum}">{{item.groupFinishNum}}</span>/{{item.groupNum}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</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.groupFinishNum==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
<view class="r-l-right">
完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</span>
</view>
</view>
</view>
@@ -138,7 +154,7 @@
<view class="r-list">
<view class="r-left">
<view class="r-l-left" style="width:220rpx">跟踪次数<span class="r-gray">{{ item.logNum }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{ parseTime(item.lastLogTime,'{m}-{d} {h}:{i}') }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{ parseTime(item.lastLogTime,'{y}-{m}-{d} {h}:{i}') }}</span></view>
</view>
</view>
<view class="report-border" v-if="index<list3.length-1"></view>
@@ -154,7 +170,7 @@
</template>
<script setup>
import { ref,onMounted } from 'vue'
import { onLoad,onHide } from '@dcloudio/uni-app';
import { onLoad,onShow,onHide } from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import { parseTime } from '@/utils/datetime.js';
@@ -172,6 +188,7 @@ onLoad(option => {
let list1 = ref([]);
let list2 = ref([]);
let list3 = ref([]);
let mescroll = null; // 用于存放mescroll实例
const mescrollRef = ref(null);
const upOption = ref({
use: false,
@@ -193,15 +210,23 @@ const downOption = ref({
});
let cssFlag=ref(false);//控制样式
const mescrollInit = (mescroll) => {
onShow(()=>{
if(mescroll)
mescroll.triggerDownScroll()
})
const mescrollInit = (mescrollInstance) => {
cssFlag.value = true;
mescroll = mescrollInstance;
// mescroll.setMescroll(mescrollRef.value); // 绑定mescroll实例
mescrollRef.value = mescroll;
};
// 下拉刷新
const downCallback = async (mescroll) => {
try {
console.log("下拉刷新")
// console.log("下拉刷新")
const res = await getList(1, upOption.value.page.size);
cssFlag.value = false;
list1.value = res.list1 || [];
@@ -337,6 +362,7 @@ const handleJump = (item)=>{
}
.report-list .r-list .r-left{
display: flex;
align-items: center;
}
.report-list .r-list .r-gray{
margin-left:10rpx;
@@ -352,7 +378,7 @@ const handleJump = (item)=>{
width:525rpx
}
.r-left .r-l-left{
width:280rpx;
width:345rpx;
}
.r-left .r-l-right{

View File

@@ -13,7 +13,7 @@
export default {
data() {
return {
inputText: 'testtesttesttesttestfdsf'
inputText: 'testtesttesttesttestfdsftttg'
}
},
onLoad() {

View File

@@ -56,7 +56,7 @@
</multipleSelect>
</block>
<!-- 判断 -->
<block v-if="item.pointType==3">
<block v-else-if="item.pointType==3">
<view class="r-list">
<view class="r-left">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
@@ -103,10 +103,13 @@
</view>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<view class="r-right r-active">
<view class="r-right r-active" v-if="item.resultContent" >
<img :src="'static/images/polling/icon-NFCcode-b.png'" class="img-nfc" />
<view>识别成功</view>
</view>
<view class="r-right" v-else>
<img :src="'static/images/polling/icon-NFCcode.png'" class="img-nfc" /> 开始识别
</view>
<!-- #endif -->
</view>
</block>
@@ -127,10 +130,13 @@
</view>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<view class="r-right r-active">
<view class="r-right r-active" v-if="item.resultContent">
<img :src="'static/images/polling/icon-QRcode-b.png'" class="img-nfc" />
<view>扫码成功</view>
</view>
<view class="r-right" v-else>
<img :src="'static/images/polling/icon-QRcode.png'" class="img-nfc" /> 开始扫码
</view>
<!-- #endif -->
</view>
</block>
@@ -141,13 +147,23 @@
<view>{{ item.pointName }}</view>
</view>
<view class="img-flex">
<view class="img-show" v-for="(item2,index) in imgArr2" :key="index" @click="showMediaPreview(item2)">
<img :src="item2.shortUrl" />
<view class="img-show" v-for="(item2,index2) in item.imgArr2" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(item,index,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<image :src="item2.shortUrl" mode="aspectFill" />
</view>
<view class="img-con" @click="chooseImage(item)">
<view class="img-con" @click="chooseImage(item,index)">
<img :src="'static/images/polling/icon-AddPic.png'" class="img-pic" />
<view>添加照片</view>
<!-- loading -->
<view class="upload-loading" v-if="item.loading">
<uni-icons type="refreshempty" size="30" color="#C9C9C9"></uni-icons>
<view>上传中....</view>
</view>
</view>
</view>
</block>
<!-- 视频 -->
@@ -157,15 +173,33 @@
<view>{{ item.pointName }}</view>
</view>
<view class="img-flex">
<view class="img-show" v-for="(item2,index) in videoArr2" :key="index" @click="showMediaPreview(item2)">
<video :src="item2.url" controls v-show="videoShow"></video>
<view class="img-show" v-for="(item2,index2) in item.videoArr2" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(item,index,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="item2.url" controls v-show="videoShow"></video> -->
<DomVideoPlayer :src="item2.url" objectFit="cover" />
</view>
<view class="img-con" @click="chooseVideo(item)">
<view class="img-con" @click="chooseVideo(item,index)">
<img :src="'static/images/polling/icon-AddVideo.png'" class="img-pic" />
<view>添加视频</view>
<!-- loading -->
<view class="upload-loading" v-if="item.loading">
<uni-icons type="refreshempty" size="30" color="#C9C9C9"></uni-icons>
<view>上传中....</view>
</view>
</view>
</view>
</block>
<block v-else>
<view class="r-left" style="width:100%">
<view>{{String(index+1).padStart(2, '0')+'.'}}</view>
<view>{{ item.pointName }}</view>
</view>
</block>
<view class="report-border" :style="{borderColor:index<optionObj.pointList.length-1?'#E7E7E7':'#fff'}"></view>
</view>
<!-- 状态是4-已完成 5-已过期的不能再修改了 -->
@@ -216,17 +250,16 @@
<!-- NFC 数据读取 -->
<NFCTemplate
v-if="nfcShow"
ref="nfcReaderRef"
@changeNfc="handleNfcData"
<NFCTemplate v-if="nfcShow" ref="nfcReaderRef"
:nfcId="nfcId"
@changeNfc="handleNfcData"
@close="nfcClose"
/>
></NFCTemplate>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive,getCurrentInstance } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import { onLoad,onShow,onHide} from '@dcloudio/uni-app';
import { MINIO_KEY } from '@/enums/cacheEnums';
import customHeader from '@/components/customHeader.vue';
@@ -236,14 +269,16 @@ import pollingShowModal from "@/components/pollingShowModal.vue";
import customShowModal from "@/components/customShowModal.vue"
import mediaPreview from "@/components/mediaPreview.vue"
import NFCTemplate from "@/components/NFCTemplate.vue"
import DomVideoPlayer from 'uniapp-video-player'
import { parseTime } from '@/utils/datetime.js';
import { formatTaskStatus } from '@/utils/status.js';
import { taskGroupDetail,submitResult,minioUpload } from '@/api/polling.js'
import {compressImageUni} from '@/utils/common.js'
import {compressImageUni,getVideoFirstFrame} from '@/utils/common.js'
// import {uploadFileMinio} from '@/utils/minio.js'
const { proxy } = getCurrentInstance();
let imgLoading = ref(false);
let taskId = ref(undefined);
let groupId = ref(undefined);
let minioObj = {};
@@ -254,7 +289,12 @@ onLoad(option => {
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
// console.log(minioObj)
getList();
})
onShow(()=>{
})
// 下拉刷新
@@ -263,6 +303,7 @@ const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll;
};
const downOption = ref({
use:false,
auto: true,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
@@ -293,34 +334,35 @@ const getList = async() => {
let res = await taskGroupDetail(param);
let data = res||{};
data.pointList.forEach(item=>{
item['imgArr']=[];
item['imgArr2']=[];
item['videoArr']=[];
item['videoArr2']=[];
item['chooseList']=[];
item.loading=false;
if(item.resultContent){
if(item.pointType==2 ||item.pointType==1){
item['chooseList'] = item.resultContent.split(",") //JSON.parse(item.resultContent)
item['chooseList'] = item.resultContent.split(",")||[]; //JSON.parse(item.resultContent)
}else if(item.pointType==7){
imgArr.value=[];
imgArr2.value=[];
let imgList = item.resultContent?.split(",")||[];
imgList.forEach(imgUrl=>{
imgArr.value.push(imgUrl);
imgArr2.value.push({
item.imgArr.push(imgUrl);
item.imgArr2.push({
shortUrl:minioObj.minioThumbUrl +"/"+imgUrl,
url:minioObj.minioUrl +"/"+imgUrl,
})
})
}else if(item.pointType==8){
videoArr.value=[];
videoArr2.value=[]
let videoList = item.resultContent?.split(",")||[];
videoList.forEach(videoUrl=>{
videoArr.value.push(videoUrl);
videoArr2.value.push({
item.videoArr.push(videoUrl);
item.videoArr2.push({
shortUrl:minioObj.minioThumbUrl +"/"+videoUrl,
url:minioObj.minioUrl +"/"+videoUrl,
})
})
}
}
})
// console.log(data.pointList)
optionObj.value = data
@@ -356,15 +398,14 @@ const changeCheck = (param,param2,item) => {
}
// 照片及拍照处理
const imgArr = ref([]);
const imgArr2=ref([]);
const chooseImage = (item) => {
console.log("item=>",item)
const chooseImage = (item,index) => {
// console.log("item=>",item)
try {
uni.chooseImage({
// count: 1, // 默认是9这里设置为1次只选1张
// sizeType: ['compressed'], // 可以指定是原图还是压缩图,可选 'original' 或 'compressed'
sourceType: ['album', 'camera'], // 指定来源是相册还是相机,默认二者都有
sourceType: ['camera'], // 指定来源是相册还是相机,默认二者都有//'album',
// quality:80,
// width:'1920px',
// height:'1920px',
@@ -387,23 +428,28 @@ const chooseImage = (item) => {
formData: {
directory:'polling'
},
}
}
optionObj.value.pointList[index].loading=true;
minioUpload(param).then(res=>{
let data = res.data;
// console.log("444图片上传成功=>",data)
// imgArr2.value.push(data.fileUrl)
imgArr2.value.push({
item.imgArr2.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
})
imgArr.value.push(data.fileName)//传给后台的路径
item.resultContent = imgArr.value.join(",")
})
item.imgArr.push(data.fileName)//传给后台的路径
optionObj.value.pointList[index].imgArr=[...item.imgArr]
optionObj.value.pointList[index].imgArr2=[...item.imgArr2]
optionObj.value.pointList[index].resultContent = item.imgArr.join(",");
// console.log("上传成功后=>",optionObj.value.pointList[index])
}).finally(()=>{
optionObj.value.pointList[index].loading=false;
})
})
},
fail: (err) => {
console.log('选择图片失败', err);
}
});
} catch (error) {
@@ -412,22 +458,20 @@ const chooseImage = (item) => {
};
// 视频处理
const videoSrc = ref('');
const videoArr = ref([]);
const videoArr2 = ref([]);
const chooseVideo = (item) => {
const chooseVideo = (item,index) => {
uni.chooseVideo({
sourceType: ['album', 'camera'], // 来源:相册和相机
sourceType: ['camera'], // 来源:相册和相机//'album',
maxDuration: 60, // 最大时长(秒)
camera: 'back', // 使用后置摄像头
compressed: true, // 压缩视频
success: (res) => {
console.log("res=>",res)
success: async (res) => {
// console.log("res=>",res)
// videoSrc.value=res.tempFilePath;
// videoArr.value.push(res.tempFilePath)
console.log('视频路径:', res.tempFilePath);
// console.log('视频路径:', res.tempFilePath);
// console.log('视频时长:', res.duration);
// console.log('视频大小:', res.size);
// 执行上传
let param = {
filePath: res.tempFilePath,
@@ -435,18 +479,24 @@ const chooseVideo = (item) => {
formData: {
directory:'polling'
},
}
minioUpload(param).then(uploadRes=>{
}
optionObj.value.pointList[index].loading=true;
minioUpload(param).then(uploadRes=>{
let data = uploadRes.data;
videoArr.value.push(data.fileName)//传给后台的路径
videoArr2.value.push(data.fileUrl)
item.resultContent = videoArr.value.join(",")
item.videoArr2.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:data.fileUrl
});
item.videoArr.push(data.fileName)//传给后台的路径
optionObj.value.pointList[index].videoArr=[...item.videoArr];
optionObj.value.pointList[index].videoArr2=[...item.videoArr2];
optionObj.value.pointList[index].resultContent = item.videoArr.join(",");
// console.log(optionObj.value.pointList[index])
}).finally(()=>{
optionObj.value.pointList[index].loading=false;
})
},
fail: (err) => {
},fail: (err) => {
console.error('选择视频失败:', err);
}
});
@@ -467,12 +517,35 @@ const handlePreviewClose=()=>{
videoShow.value = true;
}
// 视频或图片删除 根据数组下标删除数组里的某个图片或视频
const handleDelete=(item,index,index2)=>{
let imgArr2 = optionObj.value.pointList[index].imgArr2;
let videoArr2 = optionObj.value.pointList[index].videoArr2
if(imgArr2.length>0){
item.imgArr.splice(index2, 1);
item.imgArr2.splice(index2, 1);
optionObj.value.pointList[index].imgArr = [...item.imgArr]
optionObj.value.pointList[index].imgArr2 = [...item.imgArr2]
optionObj.value.pointList[index].resultContent = item.imgArr.join(",");
}else if(videoArr2.length>0){
item.videoArr.splice(index2, 1);
item.videoArr2.splice(index2, 1);
optionObj.value.pointList[index].videoArr = [...item.videoArr]
optionObj.value.pointList[index].videoArr2 = [...item.videoArr2]
optionObj.value.pointList[index].resultContent = item.videoArr.join(",");
}
// console.log("删除后=>",optionObj.value.pointList[index])
}
// nfc 处理
let nfcShow = ref(false);
const nfcReaderRef = ref(null);
// optionObj.pointList
let nfcIndex = ref(0);
let nfcId = ref()
const initNFC = async(item,index) => {
// console.log("item=>",item)
nfcId.value = item.refId;
nfcShow.value = true;
nfcIndex.value=index;
// uni.navigateTo({url:'/pages/business/polling/nfcTest/index'})
@@ -488,12 +561,9 @@ const nfcClose = async(item) => {
nfcShow.value = false;
}
const handleNfcData=(data)=>{
const handleNfcData= async(data)=>{
console.log("NFC数据:", data);
console.log("NFC数据1111:", optionObj.value.pointList,nfcIndex.value)
optionObj.value.pointList[nfcIndex.value].resultContent = data;
console.log("NFC数据1111:", optionObj.value.pointList[nfcIndex.value])
// nfcShow.value = false;
}
// 扫二维码
@@ -546,7 +616,7 @@ const handleSubmit=()=>{
groupId:groupId.value,
resultList:arr
}
console.log(flag)
// console.log("submitParam=>",submitParam)
// 都答好了直接提交
if(!flag){
@@ -633,10 +703,10 @@ onUnmounted(() => {
.scroll-h{
/* #ifdef APP-PLUS */
height:calc(100vh - 78px) !important;
height:calc(100vh - 76px) !important;
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 60px) !important;
height: calc(100vh - 58px) !important;
/* #endif */
}
.head-right{}
@@ -743,11 +813,15 @@ onUnmounted(() => {
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
/* gap:30rpx 20rpx ; */
width:100%;
}
.report-list .img-show img,
.report-list .img-show video{
.report-list .img-show video,
.report-list .img-show image,
.report-list .img-show .player-wrapper{
width:210rpx;
height: 140rpx;
height:140rpx;
}
.report-list .img-show :deep(.uni-video-cover-play-button){
width:64rpx;
@@ -775,6 +849,41 @@ onUnmounted(() => {
margin:20rpx 20rpx 0 0;
}
.report-list .img-show{
position: relative;
}
.report-list .img-show .img-delete{
position: absolute;
/* #ifndef APP-PLUS */
right:4rpx;
/* #endif */
/* #ifdef APP-PLUS */
right:4rpx;
/* #endif */
top:4rpx;
width:34rpx;
height:34rpx;
line-height:34rpx;
border-radius:3rpx;
background-color: rgba(0, 0, 0, 0.6);
text-align: center;
z-index:1;
}
.report-list .img-show .img-icon{
position: absolute;
top:50%;
left: 50%;
width: 64rpx;
height:64rpx;
margin-left:-32rpx;
margin-top:-32rpx;
z-index:1;
}
.report-list .img-show .img-icon img{
width: 64rpx;
height:64rpx;
}
.bg-border{
width:750rpx;
height:20rpx;

View File

@@ -54,16 +54,20 @@
<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'" @click="showMediaPreview(minioObj.minioUrl+'/'+item)">
<img :src="minioObj.minioThumbUrl+'/'+item" />
<view class="img-flex">
<block v-for="(item2,index2) in problemObj.listFile" :key="index2">
<view class="img-show" v-if="getFileType(item2)=='image'" @click="showMediaPreview(item2)">
<image :src="minioObj.minioThumbUrl+'/'+item2" mode="aspectFill" />
</view>
<view class="img-show" v-else-if="getFileType(item)=='video'" @click="showMediaPreview(minioObj.minioUrl+'/'+item)">
<video :src="minioObj.minioUrl+'/'+item" controls v-show="videoShow"></video>
<view class="img-show" v-else-if="getFileType(item2)=='video'" @click="showMediaPreview(item2)">
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="minioObj.minioUrl+'/'+item" controls v-show="videoShow"></video> -->
<DomVideoPlayer :src="minioObj.minioUrl+'/'+item2" objectFit="cover" v-show="videoShow" />
</view>
</view>
</block>
</block>
</view>
</view>
<!-- 当天提的 增加按钮 修改和删除 -->
@@ -87,12 +91,16 @@
<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'" @click="showMediaPreview(minioObj.minioUrl+'/'+item)">
<img :src="minioObj.minioThumbUrl+'/'+item" />
<block v-for="(item2,index2) in item.listFile" :key="index2">
<view class="img-show" v-if="getFileType(item2)=='image'" @click="showMediaPreview(item2)">
<image :src="minioObj.minioThumbUrl+'/'+item2" mode="aspectFill" />
</view>
<view class="img-show" v-else-if="getFileType(item)=='video'" @click="showMediaPreview(minioObj.minioUrl+'/'+item)">
<video :src="minioObj.minioUrl+'/'+item" controls v-show="videoShow"></video>
<view class="img-show" v-else-if="getFileType(item2)=='video'" @click="showMediaPreview(item2)">
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="minioObj.minioUrl+'/'+item" controls v-show="videoShow"></video> -->
<DomVideoPlayer :src="minioObj.minioUrl+'/'+item2" objectFit="cover" v-show="videoShow" />
</view>
</block>
</view>
@@ -114,6 +122,7 @@ import { onLoad,onShow,onHide} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import mediaPreview from "@/components/mediaPreview.vue"
import DomVideoPlayer from 'uniapp-video-player'
import { parseTime } from '@/utils/datetime.js';
import {getFileType} from '@/utils/common.js';
import { problemDetail,problemDel } from '@/api/polling.js'
@@ -180,9 +189,10 @@ let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(url)=>{
url = minioObj.minioUrl+'/'+url;
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = url
mediaUrl.value = url;
}
const handlePreviewClose=()=>{
isVisible.value = false;
@@ -323,6 +333,7 @@ const handleDelete=()=>{
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
width:100%;
}
.report-list .img-con{
background-color: #EEEEEE;
@@ -342,10 +353,28 @@ const handleDelete=()=>{
width: calc(100% / 3 - 10px); /* 减去一些间隙以避免溢出 */
margin:20rpx 20rpx 0 0;
}
.report-list .img-show{
position: relative;
}
.report-list .img-show .img-icon{
position: absolute;
top:50%;
left: 50%;
width: 64rpx;
height:64rpx;
margin-left:-32rpx;
margin-top:-32rpx;
z-index:1;
}
.report-list .img-show .img-icon img{
width: 64rpx;
height:64rpx;
}
.report-list .img-show img,
.report-list .img-show video{
.report-list .img-show video,
.report-list .img-show image,
.report-list .img-show .player-wrapper{
width:210rpx;
height: 140rpx;
}

View File

@@ -0,0 +1,501 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="新增问题上报"
:leftFlag="true" :rightFlag="false"
></customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 下拉刷新 -->
<mescroll-uni ref="mescrollRef" @init="mescrollInit"
:down="downOption" @down="downCallback"
:fixed="false" class="scroll-h"
>
<view class="white-bg">
<view class="red-title">
问题名称
<view class="report-list">
<input class="r-input uni-input" v-model="optionObj.bugName" placeholder="请输入问题" placeholder-class="place-input" />
</view>
</view>
<view class="report-border"></view>
<view class="report-list">
<view class="report-pro">提交人</view>
<view class="report-right">{{optionObj.createUserName?optionObj.createUserName:realname}}</view>
</view>
<view class="report-border"></view>
<view class="report-list" style="display:block;">
问题描述
<textarea class="r-input textarea" v-model="optionObj.bugDesc" 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="(item2,index2) in imgArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(imgArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<!-- <img :src="item2.shortUrl" /> -->
<image :src="item2.shortUrl" mode="aspectFill" />
</view>
<view class="img-show" v-for="(item2,index2) in videoArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(videoArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="item.url" controls></video> -->
<DomVideoPlayer :src="item2.url" objectFit="cover" />
</view>
<!-- loading -->
<view class="img-con" v-if="imgLoading">
<view class="upload-loading">
<uni-icons type="refreshempty" size="30" color="#C9C9C9"></uni-icons>
<view>上传中....</view>
</view>
</view>
<view class="img-con" @click="chooseMedia" v-else>
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
<!-- #ifdef APP-PLUS -->
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<!-- <view class="img-con">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view> -->
<!-- #endif -->
</view>
</view>
<view class="btn-submit">
<button type="primary" @click="handleSubmit">提交</button>
</view>
</view>
</mescroll-uni>
</view>
<!-- 图片放大 -->
<mediaPreview :visible="isVisible" :url="mediaUrl" @close="handlePreviewClose"></mediaPreview>
<!-- 选择图片或者视频 -->
<chooseMediaVue ref="chooseMediaRef" @getMediaArr="getMediaArr" @closeMedia="closeMedia"></chooseMediaVue>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive, watch,getCurrentInstance } from 'vue'
import { onLoad,onShow,onHide} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import chooseMediaVue from '@/components/chooseMedia.vue'
import mediaPreview from "@/components/mediaPreview.vue"
import DomVideoPlayer from 'uniapp-video-player'
import { getUserInfo } from '@/api/auth.js'
import { patrolBugDetail,patrolBugAdd,patrolBugEdit,minioUpload } from '@/api/polling.js'
import {compressImageUni,getFileType} from '@/utils/common.js'
import {showAlert,showLoading,hideLoading} from '@/utils/message.js'
import { MINIO_KEY } from '@/enums/cacheEnums';
const { proxy } = getCurrentInstance();
let bugId = ref('');//问题id
let realname = ref('');
let desc = ref('');//描述
let minioObj = {};
onLoad(async option => {
// console.log(option)
bugId.value = option.bugId;
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
let userinfo = await getUserInfo({});
realname.value = userinfo.realname;
if(bugId.value)
getList();
})
onShow(()=>{
imgLoading.value=false;
})
// 查询列表
let list = ref([]);
let optionObj = ref({})
// 下拉刷新
const mescrollRef = ref(null);
const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll;
};
const downOption = ref({
use:false,
auto: true,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
// 下拉刷新
const downCallback = async (mescroll) => {
try {
getList();
} catch (error) {
mescroll.endErr();
} finally {
mescroll.endSuccess();
}
}
// 获取数据列表
const getList = async () => {
let param = {
bugId:bugId.value
}
let res = await patrolBugDetail(param);
let data = res||{};
// 视频回显
imgArr.value = [];
mediaArr.value=[];
videoArr.value=[];
let fileList =data.bugVedio?.split(",") || [];
fileList.forEach(item=>{
mediaArr.value.push(item)
if(getFileType(item)=='image'){
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+item,
url:minioObj.minioUrl +"/"+item,
name:item
})
}else if(getFileType(item)=='video'){
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+item,
url:minioObj.minioUrl +"/"+item,
name:item
})
}
})
optionObj.value = data;
}
// 图片或视频
let mediaArr = ref([]);//传给后台的地址
let imgArr=ref([]);//图片 后台返回的
let videoArr = ref([]);//视频 后台返回的
let imgLoading = ref(false)
const chooseMedia = () => {
proxy.$refs["chooseMediaRef"].openPicker();
imgLoading.value=true;
}
const closeMedia = ()=>{
imgLoading.value=false;
}
// 插件回调
const getMediaArr=(arr)=>{
// console.log("插件回调=>",arr)
try {
arr.forEach(data=>{
mediaArr.value.push(data.fileName);
if(data.fileType=="image"){
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}else if(data.fileType == "video"){
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}
})
// console.log(imgArr.value,videoArr.value)
imgLoading.value=false;
} catch (error) {
console.error(error)
imgLoading.value=false;
}
}
// 视频或图片删除 根据数组下标删除数组里的某个图片或视频
const handleDelete=(arr,index2)=>{
let item = arr[index2];
arr.splice(index2, 1);
mediaArr.value = mediaArr.value.filter(item2=>item2!=item.name);
console.log("删除后=>",mediaArr.value)
}
// 放大视频或图片
let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(item)=>{
// console.log("调用放大视频==>",item)
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = item.url
}
const handlePreviewClose=()=>{
isVisible.value = false;
videoShow.value = true;
}
// 提交
const handleSubmit=()=>{
showLoading("加载中...")
if(bugId.value){
let param = {
bugId:bugId.value,
bugName:optionObj.value.bugName,
bugVedio:mediaArr.value.join(","),
bugDesc:optionObj.value.bugDesc
}
// console.log("patrolBugEdit=>",param)
patrolBugEdit(param).then(res=>{
// showAlert = (content, title = '提示',showCancel=false,succFun)
showAlert("修改问题上报成功!","提示",false,()=>{
uni.navigateBack()
})
}).finally(()=>{
hideLoading();
})
}else{
let param = {
bugName:optionObj.value.bugName,
bugVedio:mediaArr.value.join(","),
bugDesc:optionObj.value.bugDesc
}
// console.log("patrolBugAdd=>",param)
patrolBugAdd(param).then(res=>{
showAlert("新建问题上报成功!","提示",false,()=>{
bugId.value = res;
uni.navigateBack()
})
}).finally(()=>{
hideLoading();
})
}
}
</script>
<style scoped>
.scroll-h{
/* #ifdef APP-PLUS */
height:calc(100vh - 78px) !important;
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 58px) !important;
/* #endif */
}
:deep(.mescroll-upwarp){
display: none !important;
}
.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;
}
.white-bg .red-title .uni-input{
color:#333;
font-weight: normal;
}
.report-border{
border-bottom:1px solid #E7E7E7;
width:710rpx;
height: 1px;
margin:30rpx 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 0rpx;
}
.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:90%;
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:33rpx;
padding-top:2rpx;
/* #endif */
}
.report-list .img-flex{
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
width:100%;
}
.report-list .img-show img,
.report-list .img-show image,
.report-list .img-show video,
.report-list .img-show .player-wrapper{
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: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{
position: relative;
}
.report-list .img-show .img-delete{
position: absolute;
/* #ifndef APP-PLUS */
right:4rpx;
/* #endif */
/* #ifdef APP-PLUS */
right:4rpx;
/* #endif */
top:8rpx;
width:34rpx;
height:34rpx;
line-height:34rpx;
border-radius:3rpx;
background-color: rgba(0, 0, 0, 0.6);
text-align: center;
z-index:1;
}
.report-list .img-show .img-icon{
position: absolute;
top:50%;
left: 50%;
width: 64rpx;
height:64rpx;
margin-left:-32rpx;
margin-top:-32rpx;
z-index:1;
}
.report-list .img-show .img-icon img{
width: 64rpx;
height:64rpx;
}
.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,402 @@
<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" v-if="problemObj.bugStatus==1">
<view class="btn-yellow">
<uni-icons type="plus" size="20" color="#4C2D11"></uni-icons> 新增跟踪信息
</view>
</view>
</template>
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 下拉刷新 -->
<mescroll-uni ref="mescrollRef" @init="mescrollInit"
:down="downOption" @down="downCallback"
:fixed="false" class="scroll-h"
>
<view class="white-bg">
<view class="red-title">问题{{problemObj.bugName}}</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.createUserName}}</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.createTime,'{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.bugDesc}}</view>
<view class="report-border"></view>
<view class="report-list">问题点位照片或视频</view>
<view class="report-list">
<view class="img-flex">
<block v-for="(item2,index2) in problemObj.listFile" :key="index2">
<view class="img-show" v-if="getFileType(item2)=='image'" @click="showMediaPreview(item2)">
<image :src="minioObj.minioThumbUrl+'/'+item2" mode="aspectFill" />
</view>
<view class="img-show" v-else-if="getFileType(item2)=='video'" @click="showMediaPreview(item2)">
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="minioObj.minioUrl+'/'+item" controls v-show="videoShow"></video> -->
<DomVideoPlayer :src="minioObj.minioUrl+'/'+item2" objectFit="cover" v-show="videoShow" />
</view>
</block>
</view>
</view>
<!-- 当天提的 增加按钮 修改和删除 -->
<view class="btn-con" v-if="isToday && problemObj.bugStatus==1">
<button type="primary" @click="handleUpdate" size="mini" style="margin-right:50rpx;" class="btn-primary">修改</button>
<button type="primary" @click="handleDelete" size="mini">删除</button>
</view>
<!-- 追踪列表 -->
<block v-if="problemObj.logList">
<block v-if="problemObj.logList.length>0">
<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="(item2,index2) in item.listFile" :key="index2">
<view class="img-show" v-if="getFileType(item2)=='image'" @click="showMediaPreview(item2)">
<image :src="minioObj.minioThumbUrl+'/'+item2" mode="aspectFill" />
</view>
<view class="img-show" v-else-if="getFileType(item2)=='video'" @click="showMediaPreview(item2)">
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="minioObj.minioUrl+'/'+item" controls v-show="videoShow"></video> -->
<DomVideoPlayer :src="minioObj.minioUrl+'/'+item2" objectFit="cover" v-show="videoShow" />
</view>
</block>
</view>
</view>
</view>
</block>
</block>
</view>
</mescroll-uni>
</view>
<!-- 图片放大 -->
<mediaPreview :visible="isVisible" :url="mediaUrl" @close="handlePreviewClose"></mediaPreview>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive } from 'vue'
import { onLoad,onShow,onHide} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import mediaPreview from "@/components/mediaPreview.vue"
import DomVideoPlayer from 'uniapp-video-player'
import { parseTime } from '@/utils/datetime.js';
import {getFileType} from '@/utils/common.js';
import { patrolBugDetail,patrolBugDel } from '@/api/polling.js'
import { MINIO_KEY } from '@/enums/cacheEnums';
import {showAlert,showLoading,hideLoading} from '@/utils/message.js'
let bugId=ref('');
let problemObj = ref({});
let isToday = ref(false);
let minioObj = {};
onLoad(option => {
// console.log(option)
bugId.value = option.bugId;
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
})
onShow(()=>{
getList();
})
// 下拉刷新
const mescrollRef = ref(null);
const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll;
};
const downOption = ref({
auto: false,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
// 下拉刷新
const downCallback = async (mescroll) => {
try {
getList();
} catch (error) {
mescroll.endErr();
} finally {
mescroll.endSuccess();
}
}
// 获取数据列表
const getList = async() => {
isToday.value = false;
let data = await patrolBugDetail({bugId:bugId.value});
// data.list = data.pointName?.split(",") || [];
data.listFile=data.bugVedio?.split(",") || [];
data.logList.forEach(item => {
item.listFile = item.logVedio?.split(",")||[]
});
// 判断是否当天创建的,显示按钮
let createTime = parseTime(data.createTime,'{y}-{m}-{d}');
let dateNow = parseTime(new Date().getTime(),'{y}-{m}-{d}');
if(createTime == dateNow){
isToday.value = true;
}
problemObj.value = data;
}
// 放大视频或图片
let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(url)=>{
url = minioObj.minioUrl+'/'+url;
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = url;
}
const handlePreviewClose=()=>{
isVisible.value = false;
videoShow.value = true;
}
// 跳转问题上报页面
const handleQuestion=()=>{
uni.navigateTo({
url: '/pages/business/polling/problemInitiativeLog?bugId='+bugId.value
});
}
// 修改
const handleUpdate=()=>{
uni.navigateTo({
url: '/pages/business/polling/problemInitiative?bugId='+bugId.value
});
}
// 删除
const handleDelete=()=>{
showLoading("加载中...")
patrolBugDel({bugId:bugId.value}).then(res=>{
// content, title = '提示',showCancel=false,succFun
showAlert("删除成功!",'提示',false,()=>{
uni.navigateBack()
})
}).finally(()=>{
hideLoading();
})
}
</script>
<style lang="scss" scoped>
.scroll-h{
/* #ifdef APP-PLUS */
height:calc(100vh - 78px) !important;
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 58px) !important;
/* #endif */
}
:deep(.mescroll-upwarp){
display: none !important;
}
.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 50rpx 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;
width:100%;
}
.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{
position: relative;
}
.report-list .img-show .img-icon{
position: absolute;
top:50%;
left: 50%;
width: 64rpx;
height:64rpx;
margin-left:-32rpx;
margin-top:-32rpx;
z-index:1;
}
.report-list .img-show .img-icon img{
width: 64rpx;
height:64rpx;
}
.report-list .img-show img,
.report-list .img-show video,
.report-list .img-show image,
.report-list .img-show .player-wrapper{
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,334 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" :title="!searchShow?'巡检问题上报':'搜索'"
:leftFlag="true"
:rightFlag="true"
@back="handleBack" :searchType="searchShow?1:undefined"
>
<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>
<!-- 搜索处理 -->
<customSearch v-if="searchShow"
:searchKeywords="searchText"
:searchType="searchTypeObj"
@confirm="handleSearchConfirm"
></customSearch>
<view class="search" v-else>
<uni-search-bar class="custom-search" radius="28"
placeholder="请输入搜索条件"
clearButton="auto" cancelButton="none"
bgColor="#6FA2F8" textColor="#ffffff"
@focus="handleSearchFocus"
v-model="searchText"
/>
</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)">
<view class="r-list" style="padding-bottom:0">
<view class="r-name">{{ item.bugName }}</view>
<view class="r-right">
<!-- 问题状态 1=追踪2=关闭 -->
<view v-if="item.bugStatus==1" class="btn-red">进行中</view>
<view v-if="item.bugStatus==2" class="btn-green">已解决</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left" style="width:220rpx">跟踪次数<span class="r-gray">{{ item.logNum }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{parseTime(item.lastLogTime,'{y}-{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, onShow } 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 { parseTime } from '@/utils/datetime.js'
import { patrolBugList } from '@/api/polling.js'
onLoad(async(opt) => {
uni.setStorageSync('page_cache',true);
})
onShow(()=>{
triggerRefresh();
})
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
})
// 搜索处理
let searchShow = ref(false);
let searchText = ref(undefined);
let searchTypeObj = ref({typeId:5});
onHide(()=>{
searchShow.value=false;
})
// 搜索返回操作
const handleBack=()=>{
searchShow.value=false;
}
// 获取input 焦点跳转
const handleSearchFocus=()=>{
searchShow.value = true;
}
// 搜索完返回处理
const handleSearchConfirm = (param1,param2)=>{
// console.log(param1,param2)
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 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,
key:searchText.value?searchText.value:undefined,
}
let res = await patrolBugList(param);
let list = res.list || [];
resolve({
list,
// total: res.recordCount || 0
});
});
}
// 主动触发下拉刷新
const triggerRefresh = () => {
if (mescrollRef.value) {
mescrollRef.value.mescroll.triggerDownScroll()
}
}
// 跳转问题上报页面
const handleQuestion=()=>{
uni.navigateTo({
url: '/pages/business/polling/problemInitiative'
});
}
// 查看详情
const handleDetail = (item) =>{
let url= '/pages/business/polling/problemInitiativeDetail?bugId='+item.bugId;
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;
}
.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: 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 - 123px) !important;
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 105px) !important;
/* #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;
}
:deep(.mescroll-empty){
display: none !important;
}
</style>

View File

@@ -0,0 +1,498 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" title="新增跟踪信息"
:leftFlag="true" :rightFlag="false"
></customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height"></view>
<!-- 下拉刷新 -->
<mescroll-uni ref="mescrollRef" @init="mescrollInit"
:down="downOption" @down="downCallback"
:fixed="false" class="scroll-h"
>
<view class="white-bg">
<view class="red-title">问题{{optionObj.bugName}}</view>
<view class="report-border"></view>
<view class="report-list">
<view class="report-pro">提交人</view>
<view class="report-right">{{optionObj.createUserName}}</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="(item2,index2) in imgArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(imgArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<image :src="item2.shortUrl" mode="aspectFill" />
</view>
<view class="img-show" v-for="(item2,index2) in videoArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(videoArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="item" controls></video> -->
<DomVideoPlayer :src="item2.url" objectFit="cover" />
</view>
<!-- loading -->
<view class="img-con" v-if="imgLoading">
<view class="upload-loading">
<uni-icons type="refreshempty" size="30" color="#C9C9C9"></uni-icons>
<view>上传中....</view>
</view>
</view>
<view class="img-con" @click="chooseMedia" v-else>
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
<!-- #ifdef APP-PLUS -->
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<!-- <view class="img-con">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view> -->
<!-- #endif -->
</view>
</view>
<view class="btn-submit">
<button type="primary" @click="handleSubmit">提交</button>
</view>
</view>
</mescroll-uni>
</view>
<!-- 图片放大 -->
<mediaPreview :visible="isVisible" :url="mediaUrl" @close="handlePreviewClose"></mediaPreview>
<!-- 选择图片或者视频 -->
<chooseMediaVue ref="chooseMediaRef" @getMediaArr="getMediaArr" @closeMedia="closeMedia"></chooseMediaVue>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive,getCurrentInstance } from 'vue'
import { onLoad,onHide, onShow} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import mediaPreview from "@/components/mediaPreview.vue"
import chooseMediaVue from '@/components/chooseMedia.vue'
import DomVideoPlayer from 'uniapp-video-player'
import { getUserInfo } from '@/api/auth.js'
import { patrolBugDetail,patrolBugAddLog,minioUpload } from '@/api/polling.js'
import { MINIO_KEY } from '@/enums/cacheEnums';
import {showAlert,showLoading,hideLoading} from '@/utils/message.js'
import {compressImageUni} from '@/utils/common.js'
const { proxy } = getCurrentInstance();
let bugId = ref('');
let realname = ref('');
let desc = ref('');//描述
let minioObj = {};
onLoad(async option => {
// console.log(option)
bugId.value = option.bugId;
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
let userinfo = await getUserInfo({});
realname.value = userinfo.realname
getList();
})
onShow(()=>{
imgLoading.value=false;
})
// 查询列表
let list = ref([]);
let optionObj = ref({})
// 下拉刷新
const mescrollRef = ref(null);
const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll;
};
const downOption = ref({
use:false,
auto: false,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
textLoading: '刷新中...'
});
// 下拉刷新
const downCallback = async (mescroll) => {
try {
getList();
} catch (error) {
mescroll.endErr();
} finally {
mescroll.endSuccess();
}
}
// 获取数据列表
const getList = async () => {
let res = await patrolBugDetail({bugId:bugId.value});
let data = res || {};
// data.list = data.pointName.split(",") || [];
optionObj.value = data;
}
// 图片或视频
let mediaArr = ref([]);//传给后台的地址
let imgArr=ref([]);//图片 后台返回的
let videoArr = ref([]);//视频 后台返回的
let imgLoading = ref(false)
const chooseMedia = () => {
proxy.$refs["chooseMediaRef"].openPicker();
imgLoading.value=true;
/* uni.chooseMedia({
count: 9,
mediaType: ['image', 'video'], // 指定可选择图片和视频
sourceType: ['album', 'camera'],
maxDuration: 60, // 拍摄视频最长拍摄时间
camera: 'back',
success: (res) => {
console.log("问题跟踪chooseMedia=>",res)
res.tempFiles.forEach(async file => {
console.log(`文件类型: ${file.type}, 文件路径: ${file.tempFilePath}`);
let compressImg = file.tempFilePath;
// 图片进行压缩
if (file.fileType === 'image') {
// #ifdef APP-PLUS
// 压缩图片
compressImg = await compressImageUni(file.tempFilePath);
// #endif
}
// 执行上传
let param = {
filePath: compressImg,
name: 'file',
formData: {
directory:'polling'
},
}
imgLoading.value=true;
minioUpload(param).then(res=>{
let data = res.data;
mediaArr.value.push(data.fileName);
if (file.fileType === 'image') {// 图片
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
} else if (file.fileType === 'video') {// 视频
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}
}).finally(()=>{
imgLoading.value=false;
})
});
}
});*/
}
const closeMedia = ()=>{
imgLoading.value=false;
}
// 插件回调
const getMediaArr=(arr)=>{
// console.log("插件回调=>",arr)
try {
arr.forEach(data=>{
mediaArr.value.push(data.fileName);
if(data.fileType=="image"){
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}else if(data.fileType == "video"){
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}
})
// console.log(imgArr.value,videoArr.value)
imgLoading.value=false;
} catch (error) {
console.error(error)
imgLoading.value=false;
}
}
// 视频或图片删除 根据数组下标删除数组里的某个图片或视频
const handleDelete=(arr,index2)=>{
arr.splice(index2, 1);
let item = arr[index2];
mediaArr.value = mediaArr.value.filter(item2=>item2!=item.name);
// console.log("删除后=>",mediaArr.value)
}
// 放大视频或图片
let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(item)=>{
// console.log("调用放大视频==>",item)
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = item.url
}
const handlePreviewClose=()=>{
isVisible.value = false;
videoShow.value = true;
}
// 提交
const handleSubmit=()=>{
let param = {
bugId:bugId.value,
logVedio:mediaArr.value.join(","),
logDesc:desc.value
}
// console.log("patrolBugAddLog=>",param)
patrolBugAddLog(param).then(res=>{
showAlert("新建问题跟踪成功!");
uni.navigateBack();
}).finally(()=>{
hideLoading();
})
}
</script>
<style scoped>
.scroll-h{
/* #ifdef APP-PLUS */
height:calc(100vh - 78px) !important;
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 58px) !important;
/* #endif */
}
:deep(.mescroll-upwarp){
display: none !important;
}
.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-flex{
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
width:100%;
}
.report-list .img-show img,
.report-list .img-show image,
.report-list .img-show video,
.report-list .img-show .player-wrapper{
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: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;
}
.report-list .img-show{
position: relative;
}
.report-list .img-show .img-delete{
position: absolute;
/* #ifndef APP-PLUS */
right:4rpx;
/* #endif */
/* #ifdef APP-PLUS */
right:4rpx;
/* #endif */
top:8rpx;
width:34rpx;
height:34rpx;
line-height:34rpx;
border-radius:3rpx;
background-color: rgba(0, 0, 0, 0.6);
text-align: center;
z-index:1;
}
.report-list .img-show .img-icon{
position: absolute;
top:50%;
left: 50%;
width: 64rpx;
height:64rpx;
margin-left:-32rpx;
margin-top:-32rpx;
z-index:1;
}
.report-list .img-show .img-icon img{
width: 64rpx;
height:64rpx;
}
.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

@@ -46,7 +46,7 @@
<view class="r-list">
<view class="r-left">
<view class="r-l-left" style="width:220rpx">跟踪次数<span class="r-gray">{{ item.logNum }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{parseTime(item.lastLogTime,'{m}-{d} {h}:{i}') }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{parseTime(item.lastLogTime,'{y}-{m}-{d} {h}:{i}') }}</span></view>
</view>
</view>
<view class="report-border" v-if="index<list.length-1"></view>
@@ -84,7 +84,7 @@ onMounted(() => {
// 搜索处理
let searchShow = ref(false);
let searchText = ref(undefined);
let searchTypeObj = ref({typeId:3,typeName:'巡检类型'});
let searchTypeObj = ref({typeId:4,typeName:'巡检类型'});
let noticeTypeList=ref([
{id:1,name:'日常巡检'},
{id:2,name:'临时巡检'},
@@ -152,7 +152,7 @@ const downCallback = async (mescroll) => {
const res = await getList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
// mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {
@@ -245,10 +245,10 @@ const handleDetail = (item) =>{
}
.scroll-h{
/* #ifdef APP-PLUS */
height: calc(100vh - 130px);
height: calc(100vh - 123px) !important;
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 105px);
height: calc(100vh - 105px) !important;
/* #endif */
}
@@ -295,4 +295,7 @@ const handleDetail = (item) =>{
:deep(.mescroll-upwarp){
display: none !important;
}
:deep(.mescroll-empty){
display: none !important;
}
</style>

View File

@@ -42,21 +42,40 @@
<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 class="img-show" v-for="(item2,index2) in imgArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(imgArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<image :src="item2.shortUrl" mode="aspectFill" />
</view>
<view class="img-show" v-for="(item,index) in videoArr" :key="index">
<video :src="item" controls></video>
<view class="img-show" v-for="(item2,index2) in videoArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(videoArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="item" controls></video> -->
<DomVideoPlayer :src="item2.url" objectFit="cover" />
</view>
<!-- #ifdef APP-PLUS -->
<view class="img-con" @click="chooseMedia">
<!-- loading -->
<view class="img-con" v-if="imgLoading">
<view class="upload-loading">
<uni-icons type="refreshempty" size="30" color="#C9C9C9"></uni-icons>
<view>上传中....</view>
</view>
</view>
<view class="img-con" @click="chooseMedia" v-else>
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
<!-- #ifdef APP-PLUS -->
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<view class="img-con">
<!-- <view class="img-con">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
</view> -->
<!-- #endif -->
</view>
</view>
@@ -66,34 +85,47 @@
</view>
</mescroll-uni>
</view>
<!-- 图片放大 -->
<mediaPreview :visible="isVisible" :url="mediaUrl" @close="handlePreviewClose"></mediaPreview>
<!-- 选择图片或者视频 -->
<chooseMediaVue ref="chooseMediaRef" @getMediaArr="getMediaArr" @closeMedia="closeMedia"></chooseMediaVue>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive } from 'vue'
import { ref,onMounted,onUnmounted,nextTick,computed,reactive,getCurrentInstance } from 'vue'
import { onLoad,onHide, onShow} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import mediaPreview from "@/components/mediaPreview.vue"
import chooseMediaVue from '@/components/chooseMedia.vue'
import DomVideoPlayer from 'uniapp-video-player'
import { getUserInfo } from '@/api/auth.js'
import { problemDetail,problemAddLog,minioUpload } from '@/api/polling.js'
import { MINIO_KEY } from '@/enums/cacheEnums';
import {showAlert,showLoading,hideLoading} from '@/utils/message.js'
import {compressImageUni} from '@/utils/common.js'
const { proxy } = getCurrentInstance();
let problemId = ref('');
let realname = ref('');
let desc = ref('');//描述
let minioObj = {};
onLoad(async option => {
// console.log(option)
problemId.value = option.problemId;
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
let userinfo = await getUserInfo({});
realname.value = userinfo.realname
})
onShow(()=>{
getList();
})
onShow(()=>{
imgLoading.value=false;
})
// 查询列表
let list = ref([]);
@@ -105,6 +137,7 @@ const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll;
};
const downOption = ref({
use:false,
auto: false,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
@@ -133,8 +166,11 @@ const getList = async () => {
let mediaArr = ref([]);//传给后台的地址
let imgArr=ref([]);//图片 后台返回的
let videoArr = ref([]);//视频 后台返回的
let imgLoading = ref(false)
const chooseMedia = () => {
uni.chooseMedia({
proxy.$refs["chooseMediaRef"].openPicker();
imgLoading.value=true;
/* uni.chooseMedia({
count: 9,
mediaType: ['image', 'video'], // 指定可选择图片和视频
sourceType: ['album', 'camera'],
@@ -159,19 +195,86 @@ const chooseMedia = () => {
formData: {
directory:'polling'
},
}
}
imgLoading.value=true;
minioUpload(param).then(res=>{
let data = res.data;
mediaArr.value.push(data.fileName);
if (file.fileType === 'image') {// 图片
imgArr.value.push(data.fileUrl)
} else if (file.type === 'video') {// 视频
videoArr.value.push(data.fileUrl)
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
} else if (file.fileType === 'video') {// 视频
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}
}).finally(()=>{
imgLoading.value=false;
})
});
}
});
});*/
}
const closeMedia = ()=>{
imgLoading.value=false;
}
// 插件回调
const getMediaArr=(arr)=>{
// console.log("插件回调=>",arr)
try {
arr.forEach(data=>{
mediaArr.value.push(data.fileName);
if(data.fileType=="image"){
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}else if(data.fileType == "video"){
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}
})
// console.log(imgArr.value,videoArr.value)
imgLoading.value=false;
} catch (error) {
console.error(error)
imgLoading.value=false;
}
}
// 视频或图片删除 根据数组下标删除数组里的某个图片或视频
const handleDelete=(arr,index2)=>{
arr.splice(index2, 1);
let item = arr[index2];
mediaArr.value = mediaArr.value.filter(item2=>item2!=item.name);
// console.log("删除后=>",mediaArr.value)
}
// 放大视频或图片
let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(item)=>{
// console.log("调用放大视频==>",item)
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = item.url
}
const handlePreviewClose=()=>{
isVisible.value = false;
videoShow.value = true;
}
// 提交
@@ -181,7 +284,7 @@ const handleSubmit=()=>{
logVedio:mediaArr.value.join(","),
logDesc:desc.value
}
console.log("problemAddLog=>",param)
// console.log("problemAddLog=>",param)
problemAddLog(param).then(res=>{
showAlert("新建问题跟踪成功!");
uni.navigateBack();
@@ -311,6 +414,27 @@ const handleSubmit=()=>{
padding-top:5rpx;
/* #endif */
}
.report-list .img-flex{
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
width:100%;
}
.report-list .img-show img,
.report-list .img-show image,
.report-list .img-show video,
.report-list .img-show .player-wrapper{
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;
@@ -335,6 +459,41 @@ const handleSubmit=()=>{
margin-left:0;
}
.report-list .img-show{
position: relative;
}
.report-list .img-show .img-delete{
position: absolute;
/* #ifndef APP-PLUS */
right:4rpx;
/* #endif */
/* #ifdef APP-PLUS */
right:4rpx;
/* #endif */
top:8rpx;
width:34rpx;
height:34rpx;
line-height:34rpx;
border-radius:3rpx;
background-color: rgba(0, 0, 0, 0.6);
text-align: center;
z-index:1;
}
.report-list .img-show .img-icon{
position: absolute;
top:50%;
left: 50%;
width: 64rpx;
height:64rpx;
margin-left:-32rpx;
margin-top:-32rpx;
z-index:1;
}
.report-list .img-show .img-icon img{
width: 64rpx;
height:64rpx;
}
.btn-submit{
width:400rpx;
margin:90rpx auto;

View File

@@ -45,21 +45,40 @@
<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" @click="showMediaPreview(item)">
<img :src="item.shortUrl" />
<view class="img-show" v-for="(item2,index2) in imgArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(imgArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<!-- <img :src="item2.shortUrl" /> -->
<image :src="item2.shortUrl" mode="aspectFill" />
</view>
<view class="img-show" v-for="(item,index) in videoArr" :key="index" @click="showMediaPreview(item)">
<video :src="item.url" controls></video>
<view class="img-show" v-for="(item2,index2) in videoArr" :key="index2" @click="showMediaPreview(item2)">
<view class="img-delete" @click.stop="handleDelete(videoArr,index2)">
<uni-icons type="closeempty" size="16" color="#fff"></uni-icons>
</view>
<view class="img-icon">
<img :src="'static/images/polling/icon-play.png'" />
</view>
<!-- <video :src="item.url" controls></video> -->
<DomVideoPlayer :src="item2.url" objectFit="cover" />
</view>
<!-- loading -->
<view class="img-con" v-if="imgLoading">
<view class="upload-loading">
<uni-icons type="refreshempty" size="30" color="#C9C9C9"></uni-icons>
<view>上传中....</view>
</view>
</view>
<view class="img-con" @click="chooseMedia" v-else>
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
<!-- #ifdef APP-PLUS -->
<view class="img-con" @click="chooseMedia">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<view class="img-con">
<!-- <view class="img-con">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view>
</view> -->
<!-- #endif -->
</view>
</view>
@@ -72,20 +91,26 @@
<!-- 图片放大 -->
<mediaPreview :visible="isVisible" :url="mediaUrl" @close="handlePreviewClose"></mediaPreview>
<!-- 选择图片或者视频 -->
<chooseMediaVue ref="chooseMediaRef" @getMediaArr="getMediaArr" @closeMedia="closeMedia"></chooseMediaVue>
</view>
</template>
<script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive, watch } from 'vue'
import { onLoad,onHide} from '@dcloudio/uni-app';
import { ref,onMounted,onUnmounted,nextTick,computed,reactive, watch,getCurrentInstance } from 'vue'
import { onLoad,onShow,onHide} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import chooseMediaVue from '@/components/chooseMedia.vue'
import mediaPreview from "@/components/mediaPreview.vue"
import DomVideoPlayer from 'uniapp-video-player'
import { getUserInfo } from '@/api/auth.js'
import { problemDetail,problemAdd,problemEdit,minioUpload } from '@/api/polling.js'
import {compressImageUni,getFileType} from '@/utils/common.js'
import {showAlert,showLoading,hideLoading} from '@/utils/message.js'
import { MINIO_KEY } from '@/enums/cacheEnums';
const { proxy } = getCurrentInstance();
let taskId = ref('');//任务id
let groupId = ref('');//组id
@@ -102,7 +127,12 @@ onLoad(async option => {
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
let userinfo = await getUserInfo({});
realname.value = userinfo.realname
realname.value = userinfo.realname;
getList();
})
onShow(()=>{
imgLoading.value=false;
})
// 查询列表
@@ -115,6 +145,7 @@ const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll;
};
const downOption = ref({
use:false,
auto: true,
textInOffset: '下拉刷新',
textOutOffset: '释放更新',
@@ -163,11 +194,13 @@ const getList = async () => {
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+item,
url:minioObj.minioUrl +"/"+item,
name:item
})
}else if(getFileType(item)=='video'){
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+item,
url:minioObj.minioUrl +"/"+item,
name:item
})
}
})
@@ -178,56 +211,102 @@ const getList = async () => {
let mediaArr = ref([]);//传给后台的地址
let imgArr=ref([]);//图片 后台返回的
let videoArr = ref([]);//视频 后台返回的
let imgLoading = ref(false)
const chooseMedia = () => {
uni.chooseMedia({
count: 9,
mediaType: ['image', 'video'], // 指定可选择图片和视频
sourceType: ['album', 'camera'],
maxDuration: 60, // 拍摄视频最长拍摄时间
camera: 'back',
success: (res) => {
console.log("chooseMedia=>",res)
res.tempFiles.forEach(async file => {
// tempFilePath
console.log(`文件类型: ${file.fileType}, 文件路径: ${file.tempFilePath}`);
let compressImg = file.tempFilePath;
// 图片进行压缩
if (file.fileType === 'image') {
// #ifdef APP-PLUS
// 压缩图片
compressImg = await compressImageUni(file.tempFilePath);
// #endif
}
// console.log("chooseMedia=>")
proxy.$refs["chooseMediaRef"].openPicker();
imgLoading.value=true;
// uni.chooseMedia({
// count: 9,
// mediaType: ['image', 'video'], // 指定可选择图片和视频
// sourceType: ['album', 'camera'],
// maxDuration: 60, // 拍摄视频最长拍摄时间
// camera: 'back',
// success: (res) => {
// // console.log("chooseMedia=>",res)
// res.tempFiles.forEach(async file => {
// // console.log(`文件类型: ${file.fileType}, 文件路径: ${file.tempFilePath}`);
// let compressImg = file.tempFilePath;
// // 图片进行压缩
// if (file.fileType === 'image') {
// // #ifdef APP-PLUS
// // 压缩图片
// compressImg = await compressImageUni(file.tempFilePath);
// // #endif
// }
// 执行上传
let param = {
filePath: compressImg,
name: 'file',
formData: {
directory:'polling'
},
}
minioUpload(param).then(res=>{
let data = res.data;
mediaArr.value.push(data.fileName);
if (file.fileType === 'image') {// 图片
// imgArr.value.push(data.fileUrl)
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
})
} else if (file.type === 'video') {// 视频
// videoArr.value.push(data.fileUrl)
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
})
}
})
// // 执行上传
// let param = {
// filePath: compressImg,
// name: 'file',
// formData: {
// directory:'polling'
// },
// }
// imgLoading.value=true;
// minioUpload(param).then(res=>{
// let data = res.data;//console.log("上传成功后=>",res)
// mediaArr.value.push(data.fileName);
// if (file.fileType === 'image') {// 图片
// imgArr.value.push({
// shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
// url:minioObj.minioUrl +"/"+data.fileName,
// name:data.fileName
// })
// } else if (file.fileType === 'video') {// 视频
// videoArr.value.push({
// shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
// url:minioObj.minioUrl +"/"+data.fileName,
// name:data.fileName
// })
// }
// }).finally(()=>{
// imgLoading.value=false;
// })
});
}
});
// });
// }
// });
}
const closeMedia = ()=>{
imgLoading.value=false;
}
// 插件回调
const getMediaArr=(arr)=>{
// console.log("插件回调=>",arr)
try {
arr.forEach(data=>{
mediaArr.value.push(data.fileName);
if(data.fileType=="image"){
imgArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}else if(data.fileType == "video"){
videoArr.value.push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName,
name:data.fileName
})
}
})
// console.log(imgArr.value,videoArr.value)
imgLoading.value=false;
} catch (error) {
console.error(error)
imgLoading.value=false;
}
}
// 视频或图片删除 根据数组下标删除数组里的某个图片或视频
const handleDelete=(arr,index2)=>{
let item = arr[index2];
arr.splice(index2, 1);
mediaArr.value = mediaArr.value.filter(item2=>item2!=item.name);
console.log("删除后=>",mediaArr.value)
}
// 放大视频或图片
@@ -235,6 +314,7 @@ let isVisible= ref(false);//放大处理
let mediaUrl= ref('');//放大地址
let videoShow = ref(true);
const showMediaPreview=(item)=>{
// console.log("调用放大视频==>",item)
isVisible.value = true;
videoShow.value = false;
mediaUrl.value = item.url
@@ -269,7 +349,10 @@ const handleSubmit=()=>{
}
// console.log("problemEdit=>",param)
problemEdit(param).then(res=>{
showAlert("修改问题上报成功!")
// showAlert = (content, title = '提示',showCancel=false,succFun)
showAlert("修改问题上报成功!","提示",false,()=>{
uni.navigateBack()
})
}).finally(()=>{
hideLoading();
})
@@ -283,8 +366,10 @@ const handleSubmit=()=>{
}
// console.log("problemAdd=>",param)
problemAdd(param).then(res=>{
showAlert("新建问题上报成功!");
problemId.value = res;
showAlert("新建问题上报成功!","提示",false,()=>{
problemId.value = res;
uni.navigateBack()
})
}).finally(()=>{
hideLoading();
})
@@ -419,9 +504,12 @@ const handleSubmit=()=>{
display: flex;
flex-flow:row wrap;
margin-bottom:30rpx;
width:100%;
}
.report-list .img-show img,
.report-list .img-show video{
.report-list .img-show image,
.report-list .img-show video,
.report-list .img-show .player-wrapper{
width:210rpx;
height: 140rpx;
}
@@ -450,6 +538,40 @@ const handleSubmit=()=>{
width: calc(100% / 3 - 10px); /* 减去一些间隙以避免溢出 */
margin:20rpx 20rpx 0 0;
}
.report-list .img-show{
position: relative;
}
.report-list .img-show .img-delete{
position: absolute;
/* #ifndef APP-PLUS */
right:4rpx;
/* #endif */
/* #ifdef APP-PLUS */
right:4rpx;
/* #endif */
top:8rpx;
width:34rpx;
height:34rpx;
line-height:34rpx;
border-radius:3rpx;
background-color: rgba(0, 0, 0, 0.6);
text-align: center;
z-index:1;
}
.report-list .img-show .img-icon{
position: absolute;
top:50%;
left: 50%;
width: 64rpx;
height:64rpx;
margin-left:-32rpx;
margin-top:-32rpx;
z-index:1;
}
.report-list .img-show .img-icon img{
width: 64rpx;
height:64rpx;
}
.btn-submit{

View File

@@ -79,8 +79,8 @@
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.groupFinishNum==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
<img v-if="item.beginTime" :src="'static/images/polling/icon-pending.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-start.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" />
@@ -91,8 +91,26 @@
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ parseTime(item.planTime,'{h}:{i}') }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
<view v-if="item.beginTime">
执行时间<span class="r-gray">{{ parseTime(item.beginTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
<view v-else>
开始时间<span class="r-gray">{{ parseTime(item.planTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
任务状态<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.beginTime">执行中</block>
<block v-else>待执行</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
<view class="r-l-right">
任务时长<span class="r-gray">{{ item.workHour }}小时</span>
</view>
</view>
</view>
<view class="r-list">
@@ -100,20 +118,12 @@
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.groupFinishNum<item.groupNum}">{{item.groupFinishNum}}</span>/{{item.groupNum}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</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.groupFinishNum==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
<view class="r-l-right">
完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</span>
</view>
</view>
</view>
<view class="report-border" v-if="index<row.list1.length-1"></view>
</view>
</block>
@@ -136,8 +146,8 @@
<!-- 任务(巡检)状态 1=未发布 2=已发布 3 进行中 4 已完成 5 已过期 -->
<!-- 状态为3进行中时 进度>0执行中 进度=0为待执行 -->
<block v-if="item.taskStatus==3">
<img v-if="item.groupFinishNum==0" :src="'static/images/polling/icon-start.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-pending.png'" class="img-w" />
<img v-if="item.beginTime" :src="'static/images/polling/icon-pending.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-start.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" />
@@ -148,8 +158,26 @@
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ parseTime(item.planTime,'{h}:{i}') }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
<view v-if="item.beginTime">
执行时间<span class="r-gray">{{ parseTime(item.beginTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
<view v-else>
开始时间<span class="r-gray">{{ parseTime(item.planTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
任务状态<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.beginTime">执行中</block>
<block v-else>待执行</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
<view class="r-l-right">
任务时长<span class="r-gray">{{ item.workHour }}小时</span>
</view>
</view>
</view>
<view class="r-list">
@@ -157,17 +185,8 @@
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.groupFinishNum<item.groupNum}">{{item.groupFinishNum}}</span>/{{item.groupNum}}</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.groupFinishNum==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
<view class="r-l-right">
完成比率<span class="r-blue">{{item.percentage}}</span>
</view>
</view>
</view>
@@ -200,7 +219,7 @@
<view class="r-list">
<view class="r-left">
<view class="r-l-left" style="width:220rpx">跟踪次数<span class="r-gray">{{ item.logNum }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{ parseTime(item.lastLogTime,'{m}-{d} {h}:{i}') }}</span></view>
<view class="r-l-right">最近跟踪时间<span class="r-gray">{{ parseTime(item.lastLogTime,'{y}-{m}-{d} {h}:{i}') }}</span></view>
</view>
</view>
<view class="report-border" v-if="index<row.list3.length-1"></view>
@@ -337,12 +356,12 @@ const scrollToPosition = () => {
// 下拉刷新
const downCallback = async (mescroll) => {
try {
console.log("下拉刷新")
// console.log("下拉刷新")
isMonth.value=true;
const res = await getList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
// mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {
@@ -354,7 +373,7 @@ const downCallback = async (mescroll) => {
// 上拉加载更多
const upCallback = async (mescroll) => {
try {
console.log("上拉加载更多")
// console.log("上拉加载更多")
let res = await getList(mescroll.num, mescroll.size);
if (mescroll.num === 1) {
list.value = res.list;
@@ -633,6 +652,7 @@ const handleJump = (item)=>{
}
.report-list .r-list .r-left{
display: flex;
align-items:center;
}
.report-list .r-list .r-gray{
margin-left:10rpx;

View File

@@ -173,7 +173,7 @@ const downCallback = async (mescroll) => {
const res = await getList(1, upOption.value.page.size);
cssFlag.value = false;
taskObj.value = res;
mescroll.resetUpScroll();
// mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {
@@ -205,160 +205,8 @@ const getList = (pageIndex, pageSize) => {
taskId:taskId.value
}
let res = await taskDetail(param);
// let res = {
// "code": 200,
// "msg": "操作成功",
// "data": {
// taskId:236,
// taskName:'日常巡检任务AAA',
// planTime:new Date().getTime(),
// progress:62,
// count:3,
// total:9,
// workHour:1,
// 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||{};
progress.value = data.progress
progress.value = (data.groupFinishNum / data.groupNum*100).toFixed()
resolve({
...data,
});

View File

@@ -37,8 +37,8 @@
<!-- 任务(巡检)状态 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" />
<img v-if="item.beginTime" :src="'static/images/polling/icon-pending.png'" class="img-w" />
<img v-else :src="'static/images/polling/icon-start.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" />
@@ -52,8 +52,26 @@
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">开始时间<span class="r-gray">{{ parseTime(item.planTime,'{y}-{m}-{d} {h}:{i}') }}</span></view>
<view class="r-l-right">任务时长<span class="r-gray">{{ item.workHour }}小时</span></view>
<view v-if="item.beginTime">
执行时间<span class="r-gray">{{ parseTime(item.beginTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
<view v-else>
开始时间<span class="r-gray">{{ parseTime(item.planTime,'{y}-{m}-{d} {h}:{i}') }}</span>
</view>
</view>
</view>
<view class="r-list">
<view class="r-left">
<view class="r-l-left">
任务状态<span class="r-gray" v-if="item.taskStatus==3">
<block v-if="item.beginTime">执行中</block>
<block v-else>待执行</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
</view>
<view class="r-l-right">
任务时长<span class="r-gray">{{ item.workHour }}小时</span>
</view>
</view>
</view>
<view class="r-list">
@@ -61,20 +79,12 @@
<view class="r-l-left">
完成进度<span class="r-gray"><span :class="{'r-red':item.groupFinishNum<item.groupNum}">{{item.groupFinishNum}}</span>/{{item.groupNum}}</span>
</view>
<view class="r-l-right">完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</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.groupFinishNum==0">待执行</block>
<block v-else>执行中</block>
</span>
<span class="r-gray" v-else>{{formatTaskStatus(item.taskStatus) }}</span>
<view class="r-l-right">
完成比率<span class="r-blue">{{(item.groupFinishNum/item.groupNum*100).toFixed()+'%'}}</span>
</view>
</view>
</view>
<view class="report-border" v-if="index<list.length-1"></view>
</view>
@@ -183,13 +193,12 @@ const mescrollInit = (mescroll) => {
// 下拉刷新
const downCallback = async (mescroll) => {
console.log("mescroll=>",mescroll)
try {
console.log("下拉刷新");
const res = await getList(1, upOption.value.page.size,undefined);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll()
// mescroll.resetUpScroll()
} catch (error) {
mescroll.endErr();
} finally {

View File

@@ -28,7 +28,8 @@ import { MINIO_KEY } from '@/enums/cacheEnums';
import { versionCheck,getBindStatus } from '@/api/auth.js';
import { formatIOS } from '@/utils/status.js'
// import { requestAndroidPermissionAsync,requestAndroidPermission } from '@/utils/common.js'
import {showAlert} from '@/utils/message.js'
import {showAlert,showToast} from '@/utils/message.js'
import {isNetwork,openNetworkSettings} from '@/utils/common.js'
import { minioParam } from '@/api/polling.js'
import { useUserStore } from '@/stores/user';
const userStore = useUserStore();
@@ -55,6 +56,17 @@ onLoad(async(opt) => {
// uni.preloadPage({url: "/pages/home/home"});
// #ifdef APP-PLUS
let res = await isNetwork();//验证是否有网络
if(res==='none'){
showAlert('当前网络不可用,请检查网络设置','提示',false,()=>{
openNetworkSettings();
});
return;
}
//手机通知授权
noticMsgTool();
// 查询版本接口
await getOSVesion();
@@ -216,6 +228,78 @@ const handleClose=()=>{
selectDeviceId()
}
}
//通知授权
const noticMsgTool=()=>{
if (uni.getSystemInfoSync().platform == "ios") {
// iOS: 使用 iOS API 检查通知权限
plus.ios.import('UserNotifications');
var UNNotificationSettings = plus.ios.invoke('UNUserNotificationCenter', 'currentNotificationCenter');
plus.ios.invoke(UNNotificationSettings, 'getNotificationSettingsWithCompletionHandler', function(settings) {
var authorizationStatus = plus.ios.invoke(settings, 'authorizationStatus');
if (authorizationStatus !== 3) { // 3表示已授权
//苹果打开对应的通知栏
uni.showModal({
title: '通知权限开启提醒',
content: '您还没有开启通知权限,无法接受到消息通知,请前往设置!',
showCancel: false,
confirmText: '去设置',
success: function(res) {
if (res.confirm) {
var app = plus.ios.invoke('UIApplication', 'sharedApplication');
var setting = plus.ios.invoke('NSURL', 'URLWithString:','app-settings:');
plus.ios.invoke(app, 'openURL:', setting);
plus.ios.deleteObject(setting);
plus.ios.deleteObject(app);
}
}
});
}
});
} else {
//android打开对应的通知栏
var main = plus.android.runtimeMainActivity();
var pkName = main.getPackageName();
var uid = main.getApplicationInfo().plusGetAttribute("uid");
var context = main.getApplicationContext();
var NotificationManager = plus.android.importClass('android.app.NotificationManager');
var notificationManager = context.getSystemService(context.NOTIFICATION_SERVICE);
var areNotificationsEnabled = notificationManager.areNotificationsEnabled();
if (!areNotificationsEnabled) {
// 提示用户开启通知权限
uni.showModal({
title: '通知权限开启提醒',
content: '您还没有开启通知权限,无法接受到消息通知,请前往设置!',
showCancel: false,
confirmText: '去设置',
success: function(res) {
if (res.confirm) {
var Intent = plus.android.importClass('android.content.Intent');
var Build = plus.android.importClass("android.os.Build");
//android 8.0引导
if (Build.VERSION.SDK_INT >= 26) {
var intent = new Intent('android.settings.APP_NOTIFICATION_SETTINGS');
intent.putExtra('android.provider.extra.APP_PACKAGE', pkName);
} else if (Build.VERSION.SDK_INT >= 21) { //android 5.0-7.0
var intent = new Intent('android.settings.APP_NOTIFICATION_SETTINGS');
intent.putExtra("app_package", pkName);
intent.putExtra("app_uid", uid);
} else { //(<21)其他--跳转到该应用管理的详情页
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
}
// 跳转到该应用的系统通知设置页
main.startActivity(intent);
}
}
});
}
}
}
</script>
<style>

View File

@@ -88,7 +88,7 @@ import { AGREEWELCOME_KEY } from '@/enums/cacheEnums';
import { getNavBarPaddingTop} from '@/utils/system.js'
import { useUserStore } from '@/stores/user';
import encryptObj from '@/utils/encrypt.js'
import {showAlert} from '@/utils/message.js'
import {showAlert,showToast} from '@/utils/message.js'
// #ifdef APP-PLUS
@@ -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 ='9a41dec6-536f-443f-9d98-8dc5c0b18332';//'1af78c0a-b878-425f-9dc5-bee42146860a'
param.uniqCode = '9a41dec6-536f-443f-9d98-8dc5c0b18332';
let res = await login(param);
userStore.login(res);
uni.switchTab({ url: '/pages/home/home' })
@@ -302,20 +302,21 @@ const submitForm = () => {
})
} else {
// showAlert('读取失败:'+res3.msg)
showToast('读取失败:'+res3.msg)
console.error('读取失败:', error);
btnLoading.value = false;
}
})
} catch (error) {
console.error('读取失败:', error);
// showAlert('出错:', error)
showToast('err:'+ error)
btnLoading.value = false;
}
// #endif
}).catch(err => {
console.log('表单错误11:', err);
showToast('err:'+ err)
btnLoading.value = false;
});
}

View File

@@ -162,7 +162,7 @@ const downCallback = async (mescroll) => {
const res = await getNoticeList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
// mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {
@@ -260,7 +260,7 @@ const handleJump = (item)=>{
}
.scroll-h{
/* #ifdef APP-PLUS */
height: calc(100vh - 120px);
height: calc(100vh - 150px);
/* #endif */
/* #ifndef APP-PLUS */
height: calc(100vh - 160px);

View File

@@ -103,7 +103,7 @@ const downCallback = async (mescroll) => {
const res = await getFlowList(1, upOption.value.page.size);
cssFlag.value = false;
list.value = res.list;
mescroll.resetUpScroll();
// mescroll.resetUpScroll();
} catch (error) {
mescroll.endErr();
} finally {

View File

@@ -793,4 +793,25 @@ uni-button[type='primary'][plain] {
display: none !important;
}
/* 日历修改 end */
/* 日历修改 end */
/* 图片loading */
.img-flex .img-con{position: relative;}
.img-flex .img-con .upload-loading{
position: absolute;
left:0;
top:0;
background-color: #EEEEEE;
width:210rpx;
height: 120rpx;
/* line-height: 140rpx; */
padding-top:20rpx;
}
.upload-loading .uni-icons{
display: inline-block;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,5 +1,60 @@
import { showAlert } from '@/utils/message.js'
// 验证是否有网络
export const isNetwork=()=>{
return new Promise((resolve, reject) => {
uni.getNetworkType({
success: function (res) {
resolve(res.networkType)
},fail:function(err){
reject(err)
}
});
});
}
// 打开网络设置
export const openNetworkSettings = () => {
// 根据不同平台打开网络设置
// #ifdef APP-PLUS
// App端
const platform = uni.getSystemInfoSync().platform;
if (platform === 'ios') {
// iOS打开设置页面
plus.runtime.openURL('App-Prefs:root')
} else if (platform === 'android') {
// Android打开网络设置
// 方式1使用原生API
const main = plus.android.runtimeMainActivity()
const Intent = plus.android.importClass('android.content.Intent')
const Settings = plus.android.importClass('android.provider.Settings')
const intent = new Intent(Settings.ACTION_WIFI_SETTINGS)
main.startActivity(intent)
// 方式2使用scheme某些设备可能不支持
// plus.runtime.openURL('android.settings.WIFI_SETTINGS')
}
// #endif
// #ifdef H5
// H5端无法直接跳转系统设置只能提示
uni.showToast({
title: '请手动打开网络设置',
icon: 'none'
})
// #endif
// #ifdef MP-WEIXIN
// 微信小程序无法跳转系统设置,可以引导用户检查网络
uni.showModal({
title: '提示',
content: '请检查手机网络或WiFi连接',
showCancel: false
})
// #endif
}
// 递归算法
export const initTree = (arr, parentId = '0', id) => {
const tree = [];
@@ -283,4 +338,121 @@ export const compressImageUni = (file) => {
console.log('压缩失败:', error);
}
})
}
// H5 获取视频第一帧
const getFirstFrameInH5 = (videoPath) => {
return new Promise((resolve, reject) => {
const video = document.createElement('video')
video.src = videoPath
video.crossOrigin = 'anonymous'
video.addEventListener('loadeddata', () => {
video.currentTime = 0.1
})
video.addEventListener('seeked', () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
const dataUrl = canvas.toDataURL('image/jpeg', 0.8)
resolve(dataUrl)
})
video.addEventListener('error', reject)
})
}
// App平台获取第一帧Android/iOS
const getFirstFrameInApp = (videoPath) => {
return new Promise((resolve, reject) => {
// 方法1使用原生VideoPlayer推荐
// #ifdef APP-PLUS
try {
const videoPlayer = plus.video.createVideoPlayer('firstFramePlayer', {
src: videoPath,
autoplay: false,
controls: false,
showLoading: false,
showProgress: false
})
// 监听视频准备完成
videoPlayer.addEventListener('loadeddata', () => {
// 截图
videoPlayer.snapshot({
format: 'jpg',
quality: 80
}, (res) => {
// res.target 是图片临时路径
console.log('截图成功:', res)
resolve(res.target)
// 销毁播放器
videoPlayer.close()
}, (error) => {
console.error('截图失败:', error)
reject(error)
videoPlayer.close()
})
}, false)
videoPlayer.addEventListener('error', (error) => {
console.error('视频加载失败:', error)
reject(error)
videoPlayer.close()
})
} catch (error) {
console.error('创建VideoPlayer失败:', error)
// 方法2备用方案使用HTML5 video如果原生方法失败
// showAppVideo.value = true
// showAppCanvas.value = true
// // 等待DOM更新后获取元素
// setTimeout(() => {
// const video = document.getElementById('appVideo')
// const canvas = document.getElementById('appCanvas')
// const ctx = canvas.getContext('2d')
// video.onloadedmetadata = () => {
// canvas.width = video.videoWidth
// canvas.height = video.videoHeight
// video.onseeked = () => {
// ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
// const dataUrl = canvas.toDataURL('image/jpeg', 0.8)
// // 清理
// showAppVideo.value = false
// showAppCanvas.value = false
// resolve(dataUrl)
// }
// video.currentTime = 0.1
// }
// video.onerror = reject
// }, 100)
}
// #endif
})
}
// 获取视频第一帧(跨平台处理)
export const getVideoFirstFrame = (videoPath) => {
return new Promise((resolve, reject) => {
// #ifdef H5
// H5平台使用canvas
getFirstFrameInH5(videoPath).then(resolve).catch(reject)
// #endif
// #ifdef APP-PLUS
// App平台使用plus.video
getFirstFrameInApp(videoPath).then(resolve).catch(reject)
// #endif
})
}

View File

@@ -129,7 +129,7 @@ export default class HttpRequest {
// console.log("success111=>",response)
if (responseInterceptorsHook && isFunction(responseInterceptorsHook)) {
try {
response = await responseInterceptorsHook(response, mergeConfig);
response = await responseInterceptorsHook(response, mergeConfig,options);
resolve(response);
}
catch (error) {

View File

@@ -1,9 +1,8 @@
import HttpRequest from './http';
import { merge } from 'lodash-es';
import { getToken } from '../auth';
import { getToken,clearToken } from '../auth';
import { CLIENT_ID } from '@/enums/cacheEnums';
import { RequestCodeEnum, RequestMethodsEnum } from '@/enums/requestEnums';
import { clearToken } from '@/utils/auth'
import { RequestCodeEnum, RequestMethodsEnum,RequestUrlAlertEum } from '@/enums/requestEnums';
import { useUserStore } from '@/stores/user'
import { useMessage } from '../message';
const message = useMessage();
@@ -29,7 +28,7 @@ const requestHooks = {
return options;
},
// 响应拦截器
responseInterceptorsHook(response, config) {
responseInterceptorsHook(response, config,options) {
// console.log("response=>",response)
// console.log("response=>config=>",config)
const { isTransformResponse, isReturnDefaultResponse, isAuth } = config;
@@ -60,6 +59,13 @@ const requestHooks = {
return Promise.reject();
default:
// 判断弹窗处理
const isUrlFlag = RequestUrlAlertEum.includes(options.url);
// console.log("返回处理==>",isUrlFlag,options.url)
if(isUrlFlag){
return {code,msg};
}
if(msg)
message.alert(msg)
else
@@ -70,6 +76,7 @@ const requestHooks = {
},
// 响应异常拦截器
responseInterceptorsCatchHook(options, err) {
// console.log("responseInterceptorsCatchHook=>",options,err)
if (options.method.toUpperCase() == RequestMethodsEnum.POST) {
console.log('请求失败:', err, options);
}
@@ -82,7 +89,7 @@ const defaultOptions = {
requestOptions: {// 请求配置
timeout: 10 * 1000,
header: {
version: '1.0.0',
// version: '1.0.0',
clientId:`${CLIENT_ID || 2}`, // clientId传2
}
},

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long