12 Commits

Author SHA1 Message Date
xuli
59485ce24c Merge branch 'develop' into test 2025-12-31 14:15:42 +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
dbe7effec5 新增请假模块 2025-11-17 16:01:15 +08:00
31 changed files with 1754 additions and 346 deletions

9
package-lock.json generated
View File

@@ -30,6 +30,7 @@
"minio": "^8.0.6", "minio": "^8.0.6",
"minio-js": "^1.0.7", "minio-js": "^1.0.7",
"pinia": "2.0.20", "pinia": "2.0.20",
"uniapp-video-player": "^1.3.0",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vue": "3.4.21", "vue": "3.4.21",
"vue-i18n": "9.14.5" "vue-i18n": "9.14.5"
@@ -12288,6 +12289,14 @@
"license": "MIT", "license": "MIT",
"peer": true "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": { "node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "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": "^8.0.6",
"minio-js": "^1.0.7", "minio-js": "^1.0.7",
"pinia": "2.0.20", "pinia": "2.0.20",
"uniapp-video-player": "^1.3.0",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vue": "3.4.21", "vue": "3.4.21",
"vue-i18n": "9.14.5" "vue-i18n": "9.14.5"

View File

@@ -3,7 +3,7 @@ import request from "@/utils/request";
//新增地图开始签到接口 //新增地图开始签到接口
export function addStartMap(data) { export function addStartMap(data) {
return request.post({ return request.post({
url: "/crm/jys/app/appVisistMap/Startadd", url: "/crm/app/appVisistMap/Startadd",
data, data,
},{ },{
isTransformResponse:false isTransformResponse:false
@@ -13,7 +13,7 @@ export function addStartMap(data) {
//出差打卡接口 //出差打卡接口
export function businessTripClockIn(data) { export function businessTripClockIn(data) {
return request.post({ return request.post({
url: '/crm/jys/app/appVisistMap/businessTripClockIn', url: '/crm/app/appVisistMap/businessTripClockIn',
data data
},{isTransformResponse:false}); },{isTransformResponse:false});
} }
@@ -21,7 +21,7 @@ export function businessTripClockIn(data) {
//新增地图开始打卡接口 //新增地图开始打卡接口
export function addStartMapForClockIn(data) { export function addStartMapForClockIn(data) {
return request.post({ return request.post({
url: "/crm/jys/app/appVisistMap/StartaddForClockIn", url: "/crm/app/appVisistMap/StartaddForClockIn",
data, data,
},{ },{
isTransformResponse:false isTransformResponse:false

View File

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

@@ -1,6 +1,9 @@
<template> <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'"> <block v-if="getFileType(mediaUrl)=='image'">
<image <image
:src="mediaUrl" :src="mediaUrl"
@@ -10,12 +13,24 @@
</block> </block>
<view :class="videoClass" v-else-if="getFileType(mediaUrl)=='video'"> <view :class="videoClass" v-else-if="getFileType(mediaUrl)=='video'">
<!-- object-fit="cover" --> <!-- 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>
</view> </view>
</template> </template>
<script setup> <script setup>
import DomVideoPlayer from 'uniapp-video-player'
import { ref,watch } from 'vue'; import { ref,watch } from 'vue';
import {getFileType} from '@/utils/common.js'; import {getFileType} from '@/utils/common.js';
const props = defineProps({ const props = defineProps({
@@ -54,7 +69,7 @@ const onVideoLoaded = (e) => {
let w = e.detail.width; let w = e.detail.width;
let h = e.detail.height; let h = e.detail.height;
// 你也可以在这里进行后续操作比如根据宽高比调整UI // 你也可以在这里进行后续操作比如根据宽高比调整UI
console.log(w,h) // console.log(w,h)
if(h>w){ if(h>w){
videoClass.value="enlarged-image" videoClass.value="enlarged-image"
}else{ }else{
@@ -77,6 +92,21 @@ const onVideoLoaded = (e) => {
align-items: center; align-items: center;
z-index: 10000; 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){ :deep(uni-video){
width:100%; width:100%;

View File

@@ -63,7 +63,7 @@
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />", "<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />",
"<uses-permission android:name=\"android.permission.NFC\"/>", "<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\"/>" "<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\"/>"
], ],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ], "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
"minSdkVersion" : 23, "minSdkVersion" : 23,
@@ -87,22 +87,22 @@
"unipush" : { "unipush" : {
"version" : "2", "version" : "2",
"offline" : true, "offline" : true,
"icons" : { "icons" : {
"push" : { "push" : {
"ldpi" : "/static/images/icon.png", "ldpi" : "/static/images/icon.png",
"mdpi" : "/static/images/icon.png", "mdpi" : "/static/images/icon.png",
"hdpi" : "/static/images/icon.png", "hdpi" : "/static/images/icon.png",
"xhdpi" : "/static/images/icon.png", "xhdpi" : "/static/images/icon.png",
"xxhdpi" : "/static/images/icon.png" "xxhdpi" : "/static/images/icon.png"
}, },
"small" : { "small" : {
"ldpi" : "/static/images/icon_small.png", "ldpi" : "/static/images/icon_small.png",
"mdpi" : "/static/images/icon_small.png", "mdpi" : "/static/images/icon_small.png",
"hdpi" : "/static/images/icon_small.png", "hdpi" : "/static/images/icon_small.png",
"xhdpi" : "/static/images/icon_small.png", "xhdpi" : "/static/images/icon_small.png",
"xxhdpi" : "/static/images/icon_small.png" "xxhdpi" : "/static/images/icon_small.png"
} }
} }
} }
} }
} }
@@ -155,3 +155,5 @@
"template" : "index.html" "template" : "index.html"
} }
} }
/* ios */

View File

@@ -730,6 +730,28 @@
{ {
"path": "pages/business/CRM/leave/reviewDetail", "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": { "style": {
"navigationBarTitleText": "" "navigationBarTitleText": ""
} }

View File

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

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/taskPlanAdded">
<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 class="item-border"></view>
<navigator url="/pages/business/CRM/plan/planView">
<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>
</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,219 @@
<template>
<view class="con-body">
<view class="con-bg">
<!-- 头部 -->
<customHeader ref="customHeaderRef" :title="'任务清单查看'" :leftFlag="true" :rightFlag="true">
<template #right>
<view class="head-right">
</view>
</template>
</customHeader>
<!-- 高度来避免头部遮挡 -->
<view class="top-height" :style="{ paddingTop: navBarPaddingTop + 'px' }"></view>
<!-- 正文内容 -->
<view class="all-body">
<!-- 搜索 @blur="blur" @focus="focus" @input="input" @cancel="cancel" @clear="clear"-->
<!-- <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> -->
<!-- 分页部分 -->
<mescroll-uni 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="handleDetail(item)">
<view class="report-list">
<view class="title">{{ item.title }}</view>
<view class="r-list">
<view class="r-name">{{ item.name }}</view>
<view class="r-right btn-orange" size="mini">{{ item.statusName }}</view>
</view>
<view class="border-bottom"></view>
<view class="r-list">
<view class="r-left">报告类型</view>
<view class="r-right">{{ item.reportTypeName }}</view>
</view>
<view class="border-bottom"></view>
<view class="r-list">
<view class="r-left">报告人</view>
<view class="r-right">{{ item.reportPeople }}</view>
</view>
<view class="border-bottom"></view>
<view class="r-list">
<view class="r-left">报告日期</view>
<view class="r-right">{{ item.dateStr }}</view>
</view>
</view>
</view>
</mescroll-uni>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import customHeader from '@/components/customHeader.vue'
import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import { getNavBarPaddingTop } from '@/utils/system.js'
import { SalesManTaskList } from '@/api/crm/api_ys.js'
// 获取导航栏高度用于内容区域padding
const navBarPaddingTop = ref(0);
onMounted(() => {
navBarPaddingTop.value = getNavBarPaddingTop() * 2;
})
let searchValue = ref(null)
// 查询搜索跳转
let handleSearch = () => {
console.log(searchValue.value)
}
// 新增
let handleAdd = ()=>{
uni.navigateTo({ url:'/pages/business/CRM/visitorReportAdd' })
}
// 查询列表
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: 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);
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 = (pageIndex, pageSize) => {
return new Promise(async (resolve) => {
let param = {
pageIndex,
pageSize
}
let res = await SalesManTaskList(param);
resolve({
list: res.list,
total: res.totalCount
});
});
}
// 跳转到详情
let handleDetail=(item)=>{
uni.navigateTo({
url: "/pages/business/CRM/visitorReportDetail?id="+item.id
})
}
</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;
}
</style>

View File

@@ -0,0 +1,388 @@
<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="cusName" class="f-c-right">
<view
@click="chooseCustomer"
class="form-item-container"
>
<text class="name">{{ formData.cusName || '点击选择主线任务' }}</text>
</view>
</uni-forms-item>
<uni-forms-item label="计划内容" name="opportunityType" class="f-c-right">
<picker @change="onOpportunityTypeChange" :value="opportunityTypeIndex" :range="array"
:range-key="'name'">
<view class="picker">
{{ array[opportunityTypeIndex]?.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="understandTheWay"
class="uni-forms-item is-direction-top is-top">
<uni-easyinput type="textarea" autoHeight v-model="formData.understandTheWay"
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 customHeader from '@/components/customHeader.vue'
import {
getGuestList
} from '@/api/business.js'
import { isEmpty } from '@/utils/validate.js'
import {crmMarketInformationAdd } from '@/api/crm/api_ys.js'
let customerUser = reactive({})
// 客户相关
const guestList = ref([])
// 表单数据
const formData = ref({
cusId: null,
cusName: null,
opportunityType: "", // 机会类型
plannedStartTime: "", // 计划开始时间
plannedCompTime: "", // 计划完成时间
understandTheWay: "", // 了解途径
opportunityDescription: "", // 机会描述
opportunityStatus: "", // 机会所处状态
predictedAmount: "", // 预测金额或情况
competitionSituation: "", // 竞争情况
remark: "", // 备注
picture: "", // 图片
informationType: "市场机会" // 信息类型
})
// 表单验证规则
const rules = {
cusName: {
rules: [{
required: true,
errorMessage: '请选择主线任务'
}]
},
opportunityType: {
rules: [{
required: true,
errorMessage: '请选择计划内容'
}]
},
plannedStartTime: {
rules: [{
required: true,
errorMessage: '请选择计划开始时间'
}]
},
plannedCompTime: {
rules: [
{
required: true,
errorMessage: '请选择计划完成时间'
},
{
validateFunction: function(rule, value, data, callback) {
if (value && data.plannedStartTime) {
if (new Date(value) < new Date(data.plannedStartTime)) {
callback('计划完成时间不能早于计划开始时间')
return
}
}
callback()
}
}
]
},
understandTheWay: {
rules: [{
required: true,
errorMessage: '请输入内容描述'
}]
},
opportunityDescription: {
rules: [{
required: true,
errorMessage: '请输入机会描述'
}]
},
opportunityStatus: {
rules: [{
required: true,
errorMessage: '请输入机会所处状态'
}]
},
predictedAmount: {
rules: [{
required: true,
errorMessage: '请输入预测金额或情况'
}]
},
competitionSituation: {
rules: [{
required: true,
errorMessage: '请输入竞争情况'
}]
},
}
const imgList = ref([])
// 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 form = ref({
cusId: null,
cusName: null,
opportunityType: array.value[0].name, // 机会类型
understandTheWay: "", // 了解途径
opportunityDescription: "", // 机会描述
opportunityStatus: "", // 机会所处状态
predictedAmount: "", // 预测金额或情况
competitionSituation: "", // 竞争情况
remark: "", // 备注
picture: "", // 图片
informationType: "市场机会" // 信息类型
})
// picker 选项
const opportunityTypeOptions = ref([{
id: 0,
name: '新产品需求'
},
{
id: 1,
name: '新客户开发'
},
{
id: 2,
name: '批产任务'
},
{
id: 3,
name: '新研任务'
},
{
id: 4,
name: '二筛服务'
},
{
id: 5,
name: '对手失利'
},
{
id: 6,
name: '其它'
}
])
const opportunityTypeIndex = ref(0)
// 表单引用 & 客户选择器引用
const formRef = ref(null)
const customHeaderRef = ref(null)
// 选择客户页面跳转
function chooseCustomer(){
uni.navigateTo({
url: '/pages/business/CRM/marketActivity/chooseCus'
})
}
//定义数据接收的值
let selectedCustomer = reactive(null)
//监听时间
onMounted(() => {
// 设置机会类型的默认值
formData.value.opportunityType = array.value[0].name;
opportunityTypeIndex.value = 0;
// 原有的监听客户选择事件
uni.$on('onCustomerSelected', handleCustomerSelected);
})
//取消监听
onUnmounted(() => {
uni.$off('onCustomerSelected', handleCustomerSelected)
})
//处理 接收数据
const handleCustomerSelected = (data) => {
formData.value.cusName = data.cusName
formData.value.cusId = data.cusId
console.log("收到客户数据的值:", customerUser)
}
// 提交表单
const submitForm = async () => {
try {
// 表单校验
await formRef.value.validate()
const res = await crmMarketInformationAdd(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 onOpportunityTypeChange = (e) => {
opportunityTypeIndex.value = e.detail.value
console.log('opportunityTypeIndex:', array.value[e.detail.value]?.name)
formData.value.opportunityType = array.value[e.detail.value]?.name || ''
}
// 计划开始时间变化事件
const onStartTimeChange = (e) => {
formData.value.plannedStartTime = e.detail.value
// 如果已选择完成时间,重新校验完成时间
if (formData.value.plannedCompTime) {
formRef.value.validateField('plannedCompTime')
}
}
// 计划完成时间变化事件
const onCompTimeChange = (e) => {
formData.value.plannedCompTime = e.detail.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

@@ -170,7 +170,7 @@
</template> </template>
<script setup> <script setup>
import { ref,onMounted } from 'vue' 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 customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.vue'; import MescrollUni from 'mescroll-uni/mescroll-uni.vue';
import { parseTime } from '@/utils/datetime.js'; import { parseTime } from '@/utils/datetime.js';
@@ -188,6 +188,7 @@ onLoad(option => {
let list1 = ref([]); let list1 = ref([]);
let list2 = ref([]); let list2 = ref([]);
let list3 = ref([]); let list3 = ref([]);
let mescroll = null; // 用于存放mescroll实例
const mescrollRef = ref(null); const mescrollRef = ref(null);
const upOption = ref({ const upOption = ref({
use: false, use: false,
@@ -209,8 +210,16 @@ const downOption = ref({
}); });
let cssFlag=ref(false);//控制样式 let cssFlag=ref(false);//控制样式
const mescrollInit = (mescroll) => {
onShow(()=>{
if(mescroll)
mescroll.triggerDownScroll()
})
const mescrollInit = (mescrollInstance) => {
cssFlag.value = true; cssFlag.value = true;
mescroll = mescrollInstance;
// mescroll.setMescroll(mescrollRef.value); // 绑定mescroll实例
mescrollRef.value = mescroll; mescrollRef.value = mescroll;
}; };

View File

@@ -141,13 +141,23 @@
<view>{{ item.pointName }}</view> <view>{{ item.pointName }}</view>
</view> </view>
<view class="img-flex"> <view class="img-flex">
<view class="img-show" v-for="(item2,index) in item.imgArr2" :key="index" @click="showMediaPreview(item2)"> <view class="img-show" v-for="(item2,index2) in item.imgArr2" :key="index2" @click="showMediaPreview(item2)">
<img :src="item2.shortUrl" /> <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>
<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" /> <img :src="'static/images/polling/icon-AddPic.png'" class="img-pic" />
<view>添加照片</view> <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>
</view> </view>
</block> </block>
<!-- 视频 --> <!-- 视频 -->
@@ -157,12 +167,24 @@
<view>{{ item.pointName }}</view> <view>{{ item.pointName }}</view>
</view> </view>
<view class="img-flex"> <view class="img-flex">
<view class="img-show" v-for="(item2,index) in item.videoArr2" :key="index" @click="showMediaPreview(item2)"> <view class="img-show" v-for="(item2,index2) in item.videoArr2" :key="index2" @click="showMediaPreview(item2)">
<video :src="item2.url" controls v-show="videoShow"></video> <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>
<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" /> <img :src="'static/images/polling/icon-AddVideo.png'" class="img-pic" />
<view>添加视频</view> <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>
</view> </view>
</block> </block>
@@ -232,7 +254,7 @@
</template> </template>
<script setup> <script setup>
import { ref,onMounted,onUnmounted,nextTick,computed,reactive,getCurrentInstance } from 'vue' 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 { MINIO_KEY } from '@/enums/cacheEnums';
import customHeader from '@/components/customHeader.vue'; import customHeader from '@/components/customHeader.vue';
@@ -242,14 +264,16 @@ import pollingShowModal from "@/components/pollingShowModal.vue";
import customShowModal from "@/components/customShowModal.vue" import customShowModal from "@/components/customShowModal.vue"
import mediaPreview from "@/components/mediaPreview.vue" import mediaPreview from "@/components/mediaPreview.vue"
import NFCTemplate from "@/components/NFCTemplate.vue" import NFCTemplate from "@/components/NFCTemplate.vue"
import DomVideoPlayer from 'uniapp-video-player'
import { parseTime } from '@/utils/datetime.js'; import { parseTime } from '@/utils/datetime.js';
import { formatTaskStatus } from '@/utils/status.js'; import { formatTaskStatus } from '@/utils/status.js';
import { taskGroupDetail,submitResult,minioUpload } from '@/api/polling.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' // import {uploadFileMinio} from '@/utils/minio.js'
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
let imgLoading = ref(false);
let taskId = ref(undefined); let taskId = ref(undefined);
let groupId = ref(undefined); let groupId = ref(undefined);
let minioObj = {}; let minioObj = {};
@@ -260,7 +284,12 @@ onLoad(option => {
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}") minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
// console.log(minioObj) // console.log(minioObj)
getList();
})
onShow(()=>{
}) })
// 下拉刷新 // 下拉刷新
@@ -269,6 +298,7 @@ const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll; mescrollRef.value = mescroll;
}; };
const downOption = ref({ const downOption = ref({
use:false,
auto: true, auto: true,
textInOffset: '下拉刷新', textInOffset: '下拉刷新',
textOutOffset: '释放更新', textOutOffset: '释放更新',
@@ -304,9 +334,10 @@ const getList = async() => {
item['videoArr']=[]; item['videoArr']=[];
item['videoArr2']=[]; item['videoArr2']=[];
item['chooseList']=[]; item['chooseList']=[];
item.loading=false;
if(item.resultContent){ if(item.resultContent){
if(item.pointType==2 ||item.pointType==1){ 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){ }else if(item.pointType==7){
let imgList = item.resultContent?.split(",")||[]; let imgList = item.resultContent?.split(",")||[];
imgList.forEach(imgUrl=>{ imgList.forEach(imgUrl=>{
@@ -327,7 +358,6 @@ const getList = async() => {
}) })
} }
} }
}) })
// console.log(data.pointList) // console.log(data.pointList)
optionObj.value = data optionObj.value = data
@@ -363,11 +393,10 @@ const changeCheck = (param,param2,item) => {
} }
// 照片及拍照处理 // 照片及拍照处理
// const imgArr = ref([]); const chooseImage = (item,index) => {
// const imgArr2=ref([]);
const chooseImage = (item) => {
// console.log("item=>",item) // console.log("item=>",item)
try { try {
uni.chooseImage({ uni.chooseImage({
// count: 1, // 默认是9这里设置为1次只选1张 // count: 1, // 默认是9这里设置为1次只选1张
// sizeType: ['compressed'], // 可以指定是原图还是压缩图,可选 'original' 或 'compressed' // sizeType: ['compressed'], // 可以指定是原图还是压缩图,可选 'original' 或 'compressed'
@@ -394,23 +423,28 @@ const chooseImage = (item) => {
formData: { formData: {
directory:'polling' directory:'polling'
}, },
} }
optionObj.value.pointList[index].loading=true;
minioUpload(param).then(res=>{ minioUpload(param).then(res=>{
let data = res.data; let data = res.data;
// console.log("444图片上传成功=>",data) item.imgArr2.push({
// imgArr2.value.push(data.fileUrl)
item['imgArr2'].push({
shortUrl:minioObj.minioThumbUrl +"/"+data.fileName, shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
url:minioObj.minioUrl +"/"+data.fileName, url:minioObj.minioUrl +"/"+data.fileName,
}) })
item['imgArr'].push(data.fileName)//传给后台的路径 item.imgArr.push(data.fileName)//传给后台的路径
item.resultContent = item['imgArr'].join(",")
})
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) => { fail: (err) => {
console.log('选择图片失败', err); console.log('选择图片失败', err);
} }
}); });
} catch (error) { } catch (error) {
@@ -419,22 +453,20 @@ const chooseImage = (item) => {
}; };
// 视频处理 // 视频处理
// const videoSrc = ref(''); const chooseVideo = (item,index) => {
// const videoArr = ref([]);
// const videoArr2 = ref([]);
const chooseVideo = (item) => {
uni.chooseVideo({ uni.chooseVideo({
sourceType: ['album', 'camera'], // 来源:相册和相机 sourceType: ['album', 'camera'], // 来源:相册和相机
maxDuration: 60, // 最大时长(秒) maxDuration: 60, // 最大时长(秒)
camera: 'back', // 使用后置摄像头 camera: 'back', // 使用后置摄像头
compressed: true, // 压缩视频 compressed: true, // 压缩视频
success: (res) => { success: async (res) => {
console.log("res=>",res) // console.log("res=>",res)
// videoSrc.value=res.tempFilePath; // videoSrc.value=res.tempFilePath;
// videoArr.value.push(res.tempFilePath) // videoArr.value.push(res.tempFilePath)
console.log('视频路径:', res.tempFilePath); // console.log('视频路径:', res.tempFilePath);
// console.log('视频时长:', res.duration); // console.log('视频时长:', res.duration);
// console.log('视频大小:', res.size); // console.log('视频大小:', res.size);
// 执行上传 // 执行上传
let param = { let param = {
filePath: res.tempFilePath, filePath: res.tempFilePath,
@@ -442,18 +474,24 @@ const chooseVideo = (item) => {
formData: { formData: {
directory:'polling' directory:'polling'
}, },
} }
minioUpload(param).then(uploadRes=>{ optionObj.value.pointList[index].loading=true;
minioUpload(param).then(uploadRes=>{
let data = uploadRes.data; let data = uploadRes.data;
item['videoArr'].push(data.fileName)//传给后台的路径 item.videoArr2.push({
item['videoArr2'].push(data.fileUrl) shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
item.resultContent = item['videoArr'].join(",") 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(()=>{ }).finally(()=>{
optionObj.value.pointList[index].loading=false;
}) })
},fail: (err) => {
},
fail: (err) => {
console.error('选择视频失败:', err); console.error('选择视频失败:', err);
} }
}); });
@@ -474,6 +512,26 @@ const handlePreviewClose=()=>{
videoShow.value = true; 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 处理 // nfc 处理
let nfcShow = ref(false); let nfcShow = ref(false);
const nfcReaderRef = ref(null); const nfcReaderRef = ref(null);
@@ -497,9 +555,9 @@ const nfcClose = async(item) => {
const handleNfcData=(data)=>{ const handleNfcData=(data)=>{
console.log("NFC数据:", data); console.log("NFC数据:", data);
console.log("NFC数据1111:", optionObj.value.pointList,nfcIndex.value) // console.log("NFC数据1111:", optionObj.value.pointList,nfcIndex.value)
optionObj.value.pointList[nfcIndex.value].resultContent = data; optionObj.value.pointList[nfcIndex.value].resultContent = data;
console.log("NFC数据1111:", optionObj.value.pointList[nfcIndex.value]) // console.log("NFC数据1111:", optionObj.value.pointList[nfcIndex.value])
// nfcShow.value = false; // nfcShow.value = false;
} }
@@ -553,7 +611,7 @@ const handleSubmit=()=>{
groupId:groupId.value, groupId:groupId.value,
resultList:arr resultList:arr
} }
console.log(flag) // console.log("submitParam=>",submitParam)
// 都答好了直接提交 // 都答好了直接提交
if(!flag){ if(!flag){
@@ -640,10 +698,10 @@ onUnmounted(() => {
.scroll-h{ .scroll-h{
/* #ifdef APP-PLUS */ /* #ifdef APP-PLUS */
height:calc(100vh - 78px) !important; height:calc(100vh - 76px) !important;
/* #endif */ /* #endif */
/* #ifndef APP-PLUS */ /* #ifndef APP-PLUS */
height: calc(100vh - 60px) !important; height: calc(100vh - 58px) !important;
/* #endif */ /* #endif */
} }
.head-right{} .head-right{}
@@ -750,11 +808,15 @@ onUnmounted(() => {
display: flex; display: flex;
flex-flow:row wrap; flex-flow:row wrap;
margin-bottom:30rpx; margin-bottom:30rpx;
/* gap:30rpx 20rpx ; */
width:100%;
} }
.report-list .img-show img, .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; width:210rpx;
height: 140rpx; height:140rpx;
} }
.report-list .img-show :deep(.uni-video-cover-play-button){ .report-list .img-show :deep(.uni-video-cover-play-button){
width:64rpx; width:64rpx;
@@ -782,6 +844,41 @@ onUnmounted(() => {
margin:20rpx 20rpx 0 0; 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{ .bg-border{
width:750rpx; width:750rpx;
height:20rpx; height:20rpx;

View File

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

View File

@@ -42,21 +42,40 @@
<view class="report-list" style="display:block;"> <view class="report-list" style="display:block;">
<view class="r-title">问题点位照片或视频 <text>*</text></view> <view class="r-title">问题点位照片或视频 <text>*</text></view>
<view class="img-flex"> <view class="img-flex">
<view class="img-show" v-for="(item,index) in imgArr" :key="index"> <view class="img-show" v-for="(item2,index2) in imgArr" :key="index2" @click="showMediaPreview(item2)">
<img :src="item" /> <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>
<view class="img-show" v-for="(item,index) in videoArr" :key="index"> <view class="img-show" v-for="(item2,index2) in videoArr" :key="index2" @click="showMediaPreview(item2)">
<video :src="item" controls></video> <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> </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" /> <img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view> </view>
<!-- #ifdef APP-PLUS -->
<!-- #endif --> <!-- #endif -->
<!-- #ifndef APP-PLUS --> <!-- #ifndef APP-PLUS -->
<view class="img-con"> <!-- <view class="img-con">
<img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" /> <img :src="'static/images/polling/icon-AddPorV.png'" class="img-pic" />
</view> </view> -->
<!-- #endif --> <!-- #endif -->
</view> </view>
</view> </view>
@@ -66,34 +85,47 @@
</view> </view>
</mescroll-uni> </mescroll-uni>
</view> </view>
<!-- 图片放大 -->
<mediaPreview :visible="isVisible" :url="mediaUrl" @close="handlePreviewClose"></mediaPreview>
<!-- 选择图片或者视频 -->
<chooseMediaVue ref="chooseMediaRef" @getMediaArr="getMediaArr" @closeMedia="closeMedia"></chooseMediaVue>
</view> </view>
</template> </template>
<script setup> <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 { onLoad,onHide, onShow} from '@dcloudio/uni-app';
import customHeader from '@/components/customHeader.vue'; import customHeader from '@/components/customHeader.vue';
import MescrollUni from 'mescroll-uni/mescroll-uni.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 { getUserInfo } from '@/api/auth.js'
import { problemDetail,problemAddLog,minioUpload } from '@/api/polling.js' import { problemDetail,problemAddLog,minioUpload } from '@/api/polling.js'
import { MINIO_KEY } from '@/enums/cacheEnums'; import { MINIO_KEY } from '@/enums/cacheEnums';
import {showAlert,showLoading,hideLoading} from '@/utils/message.js' import {showAlert,showLoading,hideLoading} from '@/utils/message.js'
import {compressImageUni} from '@/utils/common.js' import {compressImageUni} from '@/utils/common.js'
const { proxy } = getCurrentInstance();
let problemId = ref(''); let problemId = ref('');
let realname = ref(''); let realname = ref('');
let desc = ref('');//描述 let desc = ref('');//描述
let minioObj = {};
onLoad(async option => { onLoad(async option => {
// console.log(option) // console.log(option)
problemId.value = option.problemId; problemId.value = option.problemId;
minioObj = JSON.parse(uni.getStorageSync(MINIO_KEY) || "\{\}")
let userinfo = await getUserInfo({}); let userinfo = await getUserInfo({});
realname.value = userinfo.realname realname.value = userinfo.realname
})
onShow(()=>{
getList(); getList();
}) })
onShow(()=>{
imgLoading.value=false;
})
// 查询列表 // 查询列表
let list = ref([]); let list = ref([]);
@@ -105,6 +137,7 @@ const mescrollInit = (mescroll) => {
mescrollRef.value = mescroll; mescrollRef.value = mescroll;
}; };
const downOption = ref({ const downOption = ref({
use:false,
auto: false, auto: false,
textInOffset: '下拉刷新', textInOffset: '下拉刷新',
textOutOffset: '释放更新', textOutOffset: '释放更新',
@@ -133,8 +166,11 @@ const getList = async () => {
let mediaArr = ref([]);//传给后台的地址 let mediaArr = ref([]);//传给后台的地址
let imgArr=ref([]);//图片 后台返回的 let imgArr=ref([]);//图片 后台返回的
let videoArr = ref([]);//视频 后台返回的 let videoArr = ref([]);//视频 后台返回的
let imgLoading = ref(false)
const chooseMedia = () => { const chooseMedia = () => {
uni.chooseMedia({ proxy.$refs["chooseMediaRef"].openPicker();
imgLoading.value=true;
/* uni.chooseMedia({
count: 9, count: 9,
mediaType: ['image', 'video'], // 指定可选择图片和视频 mediaType: ['image', 'video'], // 指定可选择图片和视频
sourceType: ['album', 'camera'], sourceType: ['album', 'camera'],
@@ -159,19 +195,86 @@ const chooseMedia = () => {
formData: { formData: {
directory:'polling' directory:'polling'
}, },
} }
imgLoading.value=true;
minioUpload(param).then(res=>{ minioUpload(param).then(res=>{
let data = res.data; let data = res.data;
mediaArr.value.push(data.fileName); mediaArr.value.push(data.fileName);
if (file.fileType === 'image') {// 图片 if (file.fileType === 'image') {// 图片
imgArr.value.push(data.fileUrl) imgArr.value.push({
} else if (file.type === 'video') {// 视频 shortUrl:minioObj.minioThumbUrl +"/"+data.fileName,
videoArr.value.push(data.fileUrl) 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(","), logVedio:mediaArr.value.join(","),
logDesc:desc.value logDesc:desc.value
} }
console.log("problemAddLog=>",param) // console.log("problemAddLog=>",param)
problemAddLog(param).then(res=>{ problemAddLog(param).then(res=>{
showAlert("新建问题跟踪成功!"); showAlert("新建问题跟踪成功!");
uni.navigateBack(); uni.navigateBack();
@@ -311,6 +414,27 @@ const handleSubmit=()=>{
padding-top:5rpx; padding-top:5rpx;
/* #endif */ /* #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{ .report-list .img-con{
background-color: #EEEEEE; background-color: #EEEEEE;
width:210rpx; width:210rpx;
@@ -335,6 +459,41 @@ const handleSubmit=()=>{
margin-left:0; 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{ .btn-submit{
width:400rpx; width:400rpx;
margin:90rpx auto; margin:90rpx auto;

View File

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

View File

@@ -205,160 +205,8 @@ const getList = (pageIndex, pageSize) => {
taskId:taskId.value taskId:taskId.value
} }
let res = await taskDetail(param); 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||{}; let data = res||{};
progress.value = data.progress progress.value = (data.groupFinishNum / data.groupNum*100).toFixed()
resolve({ resolve({
...data, ...data,
}); });

View File

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

View File

@@ -793,4 +793,25 @@ uni-button[type='primary'][plain] {
display: none !important; 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); }
}

View File

@@ -283,4 +283,121 @@ export const compressImageUni = (file) => {
console.log('压缩失败:', error); 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
})
} }

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