Commit 86878dfb by 陈岩

feat: 增加热力图

1 parent 685b0a49
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,viewport-fit=cover,initial-scale=1.0,maximum-scale=1.0, user-scalable=no">
<title>视频巡店</title>
<script src="/jessibuca-pro/jessibuca-pro-demo.js"></script>
<script src="/jessibuca-pro/jessibuca-pro-talk-demo.js"></script>
</head>
<body>
<div id="app" class="fullPage"></div>
<script type="module" src="/src/main.js"></script>
</body>
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width,viewport-fit=cover,initial-scale=1.0,maximum-scale=1.0, user-scalable=no"
/>
<title>视频巡店</title>
<script src="/jessibuca-pro/jessibuca-pro-demo.js"></script>
<script src="/jessibuca-pro/jessibuca-pro-talk-demo.js"></script>
<script src="/jessibuca-pro/uni.webview.1.5.5.js"></script>
</head>
<body>
<div id="app" class="fullPage"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
......@@ -10,14 +10,12 @@
"axios": "^0.26.1",
"blueimp-md5": "^2.16.0",
"echarts": "^5.3.2",
<<<<<<< HEAD
"ezuikit-js": "^7.7.2",
=======
"ezuikit-js": "^7.7.0",
"fengmap": "^3.1.5",
"heatmap.js": "2.0.5",
"jquery": "^3.7.1",
"lodash": "^4.17.21",
"moment": "^2.29.4",
>>>>>>> c16b0fd5ce3372f783957d2726c759038451ac92
"postcss-px-to-viewport": "^1.1.1",
"rollup-plugin-copy": "^3.4.0",
"sass": "^1.32.7",
......@@ -26,6 +24,7 @@
"vant": "^3.6.12",
"vconsole": "^3.14.6",
"vue": "^3.0.6",
"vue-router": "^4.5.1",
"weixin-js-sdk": "^1.6.0"
},
"devDependencies": {
......
This diff could not be displayed because it is too large.
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function i(e,i){return n.call(e,i)}var t=[];function o(){return window.__dcloud_weex_postMessage||window.__dcloud_weex_}function a(){return window.__uniapp_x_postMessage||window.__uniapp_x_}var r=function(e,n){var i={options:{timestamp:+new Date},name:e,arg:n};if(a()){if("postMessage"===e){var r={data:n};return window.__uniapp_x_postMessage?window.__uniapp_x_postMessage(r):window.__uniapp_x_.postMessage(JSON.stringify(r))}var d={type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}};window.__uniapp_x_postMessage?window.__uniapp_x_postMessageToService(d):window.__uniapp_x_.postMessageToService(JSON.stringify(d))}else if(o()){if("postMessage"===e){var s={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(s):window.__dcloud_weex_.postMessage(JSON.stringify(s))}var w={type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(w):window.__dcloud_weex_.postMessageToService(JSON.stringify(w))}else{if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:i,pageId:""},"*");if(0===t.length){var u=plus.webview.currentWebview();if(!u)throw new Error("plus.webview.currentWebview() is undefined");var g=u.parent(),v="";v=g?g.id:u.id,t.push(v)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:i,webviewIds:t}},"__uniapp__service");else{var c=JSON.stringify(i);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(c,",").concat(JSON.stringify(t),");"))}}},d={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;r("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;r("redirectTo",{url:encodeURI(n)})},getEnv:function(e){a()?e({uvue:!0}):o()?e({nvue:!0}):window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};r("postMessage",e.data||{})}},s=/uni-app/i.test(navigator.userAgent),w=/Html5Plus/i.test(navigator.userAgent),u=/complete|loaded|interactive/;var g=window.my&&navigator.userAgent.indexOf(["t","n","e","i","l","C","y","a","p","i","l","A"].reverse().join(""))>-1;var v=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var _=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var m=window.qa&&/quickapp/i.test(navigator.userAgent);var f=window.ks&&window.ks.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var l=window.tt&&window.tt.miniProgram&&/Lark|Feishu/i.test(navigator.userAgent);var E=window.jd&&window.jd.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var x=window.xhs&&window.xhs.miniProgram&&/xhsminiapp/i.test(navigator.userAgent);for(var S,h=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},y=[function(e){if(s||w)return window.__uniapp_x_postMessage||window.__uniapp_x_||window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&u.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),d},function(e){if(_)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(g){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(v)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(p)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(m){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(f)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.ks.miniProgram},function(e){if(l)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(E)return window.JDJSBridgeReady&&window.JDJSBridgeReady.invoke?setTimeout(e,0):document.addEventListener("JDJSBridgeReady",e),window.jd.miniProgram},function(e){if(x)return window.xhs.miniProgram},function(e){return document.addEventListener("DOMContentLoaded",e),d}],M=0;M<y.length&&!(S=y[M](h));M++);S||(S={});var P="undefined"!=typeof uni?uni:{};if(!P.navigateTo)for(var b in S)i(S,b)&&(P[b]=S[b]);return P.webView=S,P}));
<template>
<div class="fullPage touchmove">
<div v-if="picUrl">
<!-- <img mode="widthFix" style="position:relative;width:100vw;height: 50vwpx;" :src="picUrl"/> -->
<canvas id="painter" ref="captureCanvas"></canvas>
<div class="template-name-box">
<div class="template-name-content">
<span class="template-name-title">模板名称:</span>
<van-cell :title="templateNameDefault" @click="show = true" is-link />
<van-popup v-model:show="show" position="bottom">
<van-picker :columns="templateNameList" @confirm="onConfirm" @cancel="onCancel" />
</van-popup>
</div>
<van-button class="template-btn" plain type="primary" size="small" @click="templateClick"> + 问题项</van-button>
</div>
<div class="template-select-box" v-if="checkboxRefsShow.length > 0">
<div class="template-select-list">
<van-tag class="template-select-one" v-for="(item, index) in checkboxRefsShow" :key="index" closeable
size="large" type="primary" @close="deleteTemplate(item,index)">
{{item.name}}
</van-tag>
</div>
</div>
<van-popup v-model:show="templateDialogShow" position="bottom" class="template-list-model" closeable round>
<div class="template-list-box">
<div class="template-list-title">添加问题项</div>
<div class="template-search-box">
<form action="/">
<van-search v-model="templateSearch" placeholder="请输入搜索问题项名称" @search="templateSearchChange"/>
</form>
</div>
<div class="template-list">
<van-checkbox-group shape="square" v-model="checked" ref="checkboxGroup">
<van-collapse v-model="templatecollapseNames">
<van-collapse-item v-for="(item, index) in templateGroupList" :key="item.id" :title="item.name" :name="item.id">
<van-cell-group inset>
<van-cell v-for="(one, ind) in item.sopProjects" center clickable :key="one.id" :title="one.name" @click="toggle(ind)">
<template #right-icon>
<van-checkbox :name="one.id" :ref="el => checkboxRefs[one.allIndex] = el" @click.stop />
</template>
</van-cell>
</van-cell-group>
</van-collapse-item>
</van-collapse>
</van-checkbox-group>
</div>
<van-button class="template-list-submit" round block type="primary" @click="templateSubmit">确认</van-button>
</div>
</van-popup>
<div class="formBox">
<van-field v-model="message" rows="2" autosize label="备注" type="textarea" placeholder="请输入备注" />
<van-field name="radio" label="是否需要整改">
<template #input>
<van-radio-group v-model="isRectify" direction="horizontal">
<van-radio name="1">需要</van-radio>
<van-radio name="2">不需要</van-radio>
</van-radio-group>
</template>
</van-field>
<van-field v-show="isRectify==1" v-model="rectificationUserName" is-link readonly name="picker" label="整改人"
placeholder="点击选择整改人" @click="showRectification = true" />
<van-popup v-model:show="showRectification" position="bottom">
<van-picker :columns="userNameList" @confirm="onUserConfirm" @cancel="showRectification = false" />
</van-popup>
<div style="padding:15px;display: flex;align-items: center;justify-content: space-around;">
<van-button style="width: 45%;" round block @click="goBackVideo">返回</van-button>
<van-button style="width: 45%;" round block type="primary" @click="submitContent">提交</van-button>
</div>
</div>
</div>
<div v-else>
<livePlayer ref="vionPlayer" @screenshotEnd="screenshotEnd"></livePlayer>
<div class="tour-btns" v-if="paramObj.type=='titem'">
<van-button @click="navBackTour" type="primary">返回上一页</van-button>
<van-button @click="goCaptureImg" type="primary">确认并截图</van-button>
</div>
<div class="tour-btns" v-else-if="paramObj.type=='view'">
<van-button block @click="navBack" type="primary">查看其他监控点</van-button>
</div>
<div class="tour-btns1" v-else>
<div class="box gate-name-box">
<span @click="navBack" class="" style="display: inline-flex;align-items: center;">
<span class="text-ellipsis">{{gateEditName}}</span>
<van-icon class="icon" name="play" />
</span>
<img src="../src/assets/edit.png" alt="" class="gate-name-edit-img" @click="gateEdit">
</div>
<div class="box box1">
<span v-if="bookmark" @click="delCollectGate">
<van-icon class="iconMargin" color="#F7B500" name="star" />收藏
</span>
<span v-else @click="collectGate">
<van-icon class="iconMargin" name="star-o" />收藏
</span>
<span @click="goCaptureImg">
<van-icon class="iconMargin" name="photograph" />巡检
</span>
</div>
</div>
<!-- 监控点选择器 -->
<van-popup v-model:show="pointPopupShow" round position="bottom">
<van-picker :default-index="pointListIndex" :title="paramObj.mallName" :columns="pointList" :option-height="40" @confirm="pointConfirm"
@cancel="pointPopupShow = false" />
</van-popup>
<!-- 监控场景名称修改 -->
<van-dialog use-slot title="监控点信息修改" v-model:show="gateEditShow" show-cancel-button @cancel="gateEditHide" @confirm="gateEditSave" :confirm-button-disabled="gateEditName.trim() == ''">
<div style="margin:20px 10px;">
<van-cell-group>
<van-field v-model="gateEditName" placeholder="请输入监控点名称">
<template #label>
<span style="display: inline-flex;align-items: center;">
监控点名称
<span class="grop-name-required">*</span>
</span>
</template>
</van-field>
</van-cell-group>
<van-cell-group style="display: flex;align-items: center;">
<van-field style="display: inline-flex;" v-model="gateEditGroup" label="所属分组" placeholder="请输入分组名称">
</van-field>
<van-button style="width: 14vw;" size="small" type="primary" @click="groupPicker = true">选择</van-button>
</van-cell-group>
<div class="group-remark">
<div>所属分组信息允许为空。</div>
<div>新分组可直接输入名称自动创建,已有分组信息从列表中选择。</div>
</div>
</div>
</van-dialog>
<!-- 监控场景分组选择 -->
<van-popup v-model:show="groupPicker" round position="bottom">
<van-picker :default-index="gateEditGroupIndex" :columns="groupList" @cancel="groupPicker = false" @confirm="groupChange" />
</van-popup>
<!-- 监控场景添加分组 -->
<van-dialog use-slot title="新增分组" v-model:show="groupAddShow" show-cancel-button @cancel="groupAddHide" @confirm="groupAddSave" :confirm-button-disabled="groupAddName.trim() == ''">
<div style="margin:20px 10px;">
<van-cell-group>
<van-field v-model="groupAddName" required label="分组名称" placeholder="请输入分组名称" />
</van-cell-group>
</div>
</van-dialog>
</div>
</div>
<div id="app">
<router-view />
</div>
</template>
<script setup>
import {
reactive,
ref,
onMounted,
getCurrentInstance
} from 'vue';
import {
Toast
} from 'vant';
import livePlayer from '@/components/extension/index.vue';
import shopTour from '@/components/shopTour.vue';
//import vconsole from 'vconsole';
if (process.env.NODE_ENV !== 'production') {
//new vconsole();
}
/*获取 Url 参数 S*/
import parse from 'url-param-parser';
// let url = 'https://store.keliuyun.com/video/?userId=8840&atoken=8eaf1373-654b-4cf2-a3c7-f90d42cba574&type=undefined&id=632&name=会议室门口&mallId=9217&mallName=河南分公司&bookmark=false&channelNo=4&deviceSerial=F16423875&gateUnid=cf21f4a8-65c6-11ee-837e-00163e143ecd&ptzEnable=0&aiChannelId=&mallOrgName=办公室&accountId=337&terminalType=devtools&newLevel=' // 萤石
// const paramObj = parse(url).search || {}
const paramObj = parse(window.location.href).search || {}
/********************************/
paramObj.name = paramObj.name?decodeURIComponent(paramObj.name):'';
paramObj.mallName = paramObj.mallName?decodeURIComponent(paramObj.mallName):'';
paramObj.mallOrgName = paramObj.mallOrgName?decodeURIComponent(paramObj.mallOrgName):'';
document.title = paramObj.name;
window.localStorage.setItem('atoken', paramObj.atoken);
window.localStorage.setItem('lang', 'zh_CN');
/*获取 Url 参数 E*/
import tourApi from '@/api';
//var data = reactive(v);
const dataURLtoFile = (dataurl, filename, filetype) => {
var arr = dataurl.split(","),
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {
type: filetype
});
}
const navBack = () => {
pointPopupShow.value = true
console.log(paramObj)
};
// 监控场景编辑
const gateEditShow = ref(false)
const groupPicker = ref(false)
const gateEditName = ref('')
const gateEditGroup = ref('')
const gateEditGroupIndex = ref(0)
gateEditName.value = paramObj.name
gateEditGroup.value = paramObj.mallOrgName
// 监控场景编辑弹框显示
const gateEdit = () => {
gateEditName.value = paramObj.name
gateEditGroup.value = paramObj.mallOrgName
gateEditShow.value = true
};
// 监控场景编辑弹框关闭
const gateEditHide = () => {
gateEditName.value = paramObj.name
gateEditGroup.value = paramObj.mallOrgName
gateEditShow.value = false
};
// 监控场景编辑保存
const gateEditSave = () => {
let par = {
id:paramObj.id,
mallId:paramObj.mallId,
aiChannelId:paramObj.aiChannelId?paramObj.aiChannelId:'',
name:gateEditName.value,
mallOrgName:gateEditGroup.value,
}
tourApi.editPatrolGate(par).then(res => {
if (res.data.code == 200) {
// 更新监控点名称
gateEditShow.value = false
paramObj.name = gateEditName.value
paramObj.mallOrgName = gateEditGroup.value
document.title = paramObj.name;
reqPatrolGateList()
reqGateGroupList()
} else {
Toast.fail('修改失败');
}
})
};
// 监控场景分组修改
const groupChange = (val) => {
gateEditGroup.value = val.name
gateEditGroupIndex.value = (groupList.value.filter(v => v.text == gateEditGroup.value)[0]).index
groupPicker.value = false
};
// 获取分组列表
const groupList = ref([])
const reqGateGroupList = () => {
tourApi.getDeviceGroupList(paramObj.mallId).then(res => {
if (res.data.code == 200) {
let list = res.data.data ? res.data.data : [];
for(let i = 0; i < list.length; i++){
list[i].text = list[i].name
list[i].value = list[i].id
list[i].index = i
}
groupList.value = list;
gateEditGroupIndex.value = gateEditGroup.value?(groupList.value.filter(v => v.text == gateEditGroup.value)[0]).index:0;
}
})
}
//监控场景添加分组
const groupAddShow = ref(false)
const groupAddName = ref('')
// 监控场景添加分组弹框显示
const groupAdd = () => {
groupAddName.value = ''
groupAddShow.value = true
};
// 监控场景添加分组弹框关闭
const groupAddHide = () => {
groupAddShow.value = false
};
// 监控场景添加分组保存
const groupAddSave = () => {
// console.log('保存')
// 更新分组名称
gateEditGroup.value = groupAddName.value
groupAddShow.value = false
};
// 获取检查项列表
const pointPopupShow = ref(false)
const pointList = ref([])
const pointListIndex = ref(0)
const reqPatrolGateList = () => {
tourApi.getPatrolGateList({
pageNum: 1,
pageSize: 99999,
mallId: paramObj.mallId,
}).then(res => {
// console.log(res)
if (res.data.code == 200) {
let list = res.data.data && res.data.data.list ? res.data.data.list : [];
for(let i = 0; i < list.length; i++){
list[i].text = list[i].name
list[i].value = list[i].id
list[i].index = i
}
pointList.value = list;
pointListIndex.value = (pointList.value.filter(v => v.text == paramObj.name)[0]).index;
// console.log(list)
// console.log(pointList.value)
}
})
}
const pointConfirm = (row) => {
console.log(row)
paramObj.id = row.id
paramObj.name = row.name
gateEditName.value = paramObj.name
paramObj.bookmark = row.bookmark
paramObj.mallOrgName = row.mallOrgName
paramObj.gateUnid = row.unid
document.title = paramObj.name;
pointListIndex.value = (pointList.value.filter(v => v.text == paramObj.name)[0]).index;
pointPopupShow.value = false
vionPlayer.value.playWebVideo(paramObj);
}
const navBackTour = () => {
wx.miniProgram.navigateBack();
}
const bookmark = ref(paramObj.bookmark)
const collectGate = () => {
tourApi.setBookmark({
userId: paramObj.userId,
patrolGateIds: [paramObj.id]
}).then(res => {
if (res.data.code == 200) {
bookmark.value = true;
return Toast.success('收藏成功');
}
})
}
const delCollectGate = () => {
tourApi.delBookmark({
userId: paramObj.userId,
patrolGateIds: [paramObj.id]
}).then(res => {
if (res.data.code == 200) {
bookmark.value = false;
return Toast.success('取消收藏成功');
}
})
}
const show = ref(false);
const showPopup = () => {
show.value = true;
};
const checked = ref([]);
const onConfirm = (value, index) => {
console.log(value, index)
templateNameDefault.value = value;
checkboxRefsShow.value = [];
checkboxRefs.value = [];
checked.value = []
templateSearch.value = ''
if (index > 0) {
getPatrolTemplateOne(templateList.value[index].id)
} else {
templateGroupList.value = []
associateData.value.forEach(item => {
templateGroupList.value.push(item)
})
}
show.value = false;
};
const onCancel = () => {
show.value = false;
}
const checkboxRefs = ref([]);
const checkboxRefsShow = ref([]);
const templateSearch = ref('');
const templateClick = (val) => {
templateDialogShow.value = true;
}
const templatecollapseNames = ref([]);
const templateSearchChange = (val) => {
console.log(val)
templateSearch.value = val;
let allList = templateItemList.value
let groupList = {}
allList.forEach((item,index)=>{
item.allIndex = index
if(val==''||(val!=''&&item.name.indexOf(val) != -1)) {
if(groupList[item.sopTypeId]) {
groupList[item.sopTypeId].sopProjects.push(item)
} else {
groupList[item.sopTypeId] = {
id:item.sopTypeId,
name:item.sopTypeName,
sopProjects:[item]
}
}
}
})
templateGroupList.value = []
templatecollapseNames.value = []
for(let i in groupList) {
templateGroupList.value.push(groupList[i])
templatecollapseNames.value.push(groupList[i].id)
}
}
const toggle = (index) => {
checkboxRefs.value[index].toggle();
}
const templateSubmit = (index) => {
templateDialogShow.value = false;
if (checked.value && checked.value.length > 0) {
checkboxRefsShow.value = []
checked.value.forEach(item => {
let sopItem = templateItemList.value.find(childItem => item == childItem.id);
checkboxRefsShow.value.push({
"id": sopItem.id,
"name": sopItem.name,
})
})
}
}
const deleteTemplate = (item, index) => {
checkboxRefsShow.value.splice(index, 1)
checked.value.splice(index, 1)
}
const message = ref();
const isRectify = ref("1")
const showRectification = ref(false)
const rectificationUserName = ref('')
const onUserConfirm = (value) => {
showRectification.value = false;
rectificationUserName.value = value
};
const goBackVideo = () => {
message.value = '';
rectificationUserName.value = '';
checked.value = [];
checkboxRefsShow.value = [];
checkboxRefs.value = [];
picUrl.value = '';
setTimeout(() => {
vionPlayer.value.playWebVideo(paramObj);
}, 200)
}
const submitContent = () => {
let base64Str = dataURLtoFile(captureCanvas.value.toDataURL('image/jpeg'), new Date().getTime(), 'image/jpeg')
let formData = new FormData();
formData.append('accountId', paramObj.accountId)
formData.append('mallId', paramObj.mallId)
formData.append('gateId', paramObj.id)
formData.append('patrolType', 1)
let sops = []
if (checked.value && checked.value.length > 0) {
checked.value.forEach(item => {
let sopItem = templateItemList.value.find(childItem => item == childItem.id);
sops.push({
"id": sopItem.id,
"status": 0,
"score": sopItem.score
})
})
formData.append('sops', JSON.stringify(sops))
} else {
return Toast.fail('请选择检查项');
}
if (message.value) {
formData.append('remark', message.value)
}
if (isRectify.value == 1) {
if (rectificationUserName.value) {
let user = userList.value.find(item => item.name == rectificationUserName.value);
formData.append('handler', user.id)
} else {
return Toast.fail('请选择整改人');
}
} else {
formData.append('handler', '')
}
formData.append('pic', base64Str)
tourApi.confirmPatrolRecord(formData, {
headers: {
'Content-type': 'application/x-www-form-urlencoded;charset=utf-8' //multipart/form-data; charset=utf-8
}
}).then(res => {
let result = res.data;
if (result.code == '200') {
Toast.success('提交成功');
setTimeout(() => {
message.value = '';
rectificationUserName.value = '';
checked.value = [];
checkboxRefsShow.value = [];
checkboxRefs.value = [];
let context = captureCanvas.value.getContext("2d")
context.clearRect(0, 0, window.innerWidth, window.innerWidth / 1.78)
picUrl.value = '';
}, 1000)
} else {
return Toast.fail(result.msg);
}
})
}
const userList = ref([]);
const userNameList = ref([]);
const getUserList = () => {
userList.value = []
userNameList.value = []
tourApi.getUsers({
mallId: paramObj.mallId
}).then(data => {
let result = data.data
if (result.data && result.data.length) {
result.data.forEach(item => {
item.name = item.realName + '(' + item.loginName + ')';
userNameList.value.push(item.realName + '(' + item.loginName + ')')
})
}
userList.value = result.data
})
}
const vionPlayer = ref()
const picUrl = ref('')
const captureCanvas = ref('')
// 获取检查项
const templateList = ref([]);
const templateNameList = ref([]);
const templateNameDefault = ref();
const tempalteId = ref()
// 单个模板检查项
const templateItemList = ref([]);
// 单个模板检查项--带分组
const templateGroupList = ref([]);
setTimeout(() => {
vionPlayer.value.playWebVideo(paramObj);
}, 1000)
// 检查项弹框显示
const templateDialogShow = ref(false);
const getPatrolTemplateList = () => {
tourApi.getPatrolTemplateListFun({
accountId: paramObj.accountId,
pageSize: 999,
}).then(res => {
let result = res.data;
if (result.code == 200) {
templateList.value = result.data.list;
if (templateList.value.length) {
tempalteId.value = templateList.value[0].id
getPatrolTemplateOne(templateList.value[0].id)
// templateList.value.unshift({
// name: '全部',
// id: ''
// })
templateNameList.value = []
templateList.value.forEach(item => {
templateNameList.value.push(item.name)
})
templateNameDefault.value = templateList.value[0].name;
}
}
})
}
// 获取单个模板检查项
const getPatrolTemplateOne = (val) => {
tourApi.getPatrolTemplateOne({
id: val,
}).then(res => {
let result = res.data;
if (result.code == 200) {
templateItemList.value = result.data.sopProjects || [];
let sopProjects = result.data.sopProjects
let groupList = {}
sopProjects.forEach((item,index)=>{
item.allIndex = index
if(groupList[item.sopTypeId]) {
groupList[item.sopTypeId].sopProjects.push(item)
} else {
groupList[item.sopTypeId] = {
id:item.sopTypeId,
name:item.sopTypeName,
sopProjects:[item]
}
}
})
templateGroupList.value = []
templatecollapseNames.value = []
for(let i in groupList) {
templateGroupList.value.push(groupList[i])
templatecollapseNames.value.push(groupList[i].id)
}
}
})
}
// 获取关联的SOP
const associateData = ref([])
const getAssociateList = (record) => {
associateData.value = [];
tourApi.getPatrolSopTypeTree({
accountId: paramObj.accountId,
system: 0
}).then(data => {
let result = data.data;
if (result.code == '200') {
let tableData = result.data
tableData.sort((a,b)=>{
return a.id - b.id
})
console.log('tableData',tableData)
tableData.forEach((item, index) => {
if (item.sopProjects.length > 0) {
item.key = 1000 + item.id
let sopProjects = JSON.parse(JSON.stringify(item.sopProjects))
item.sops = sopProjects
item.len = sopProjects.length
item.choiceNum = 0
associateData.value.push(item)
}
})
}
})
}
const locHistory = ref([]);
const lastLoc = ref();
const isMouseDown = ref(false);
const windowToCanvas = function(x, y) {
var bbox = captureCanvas.value.getBoundingClientRect(); //获取canvas的位置信息
return {
x: Math.round(x - bbox.left),
y: Math.round(y - bbox.top)
} //返回当前鼠标相对于canvas的位置
}
const capture = function() {
// 绘制
var context = captureCanvas.value.getContext("2d");
captureCanvas.value.addEventListener('touchstart', function(e) {
e.preventDefault();
isMouseDown.value = true;
lastLoc.value = windowToCanvas(e.touches[0].clientX, e.touches[0].clientY)
locHistory.value.push({
x: lastLoc.value.x,
y: lastLoc.value.y,
})
console.log(locHistory.value)
})
captureCanvas.value.addEventListener('touchmove', function(e) {
e.preventDefault();
if (isMouseDown.value) {
moveStroke({
x: e.touches[0].clientX,
y: e.touches[0].clientY
}, context)
}
})
captureCanvas.value.addEventListener('touchend', function(e) {
locHistory.value.push({
x: lastLoc.value.x,
y: lastLoc.value.y,
})
isMouseDown.value = false;
})
}
const calcDistance = function(loc1, loc2) {
return Math.sqrt((loc1.x - loc2.x) * (loc1.x - loc2.x) + (loc1.y - loc2.y) * (loc1.y - loc2
.y)) //通过起始结束坐标x,y值计算路程长度
}
const moveStroke = function(point, context) {
var curLoc = windowToCanvas(point.x, point.y)
var curTimestamp = new Date().getTime();
var s = calcDistance(curLoc, lastLoc.value)
var lineWidth = 3
//draw
context.beginPath()
context.moveTo(lastLoc.value.x, lastLoc.value.y)
context.lineTo(curLoc.x, curLoc.y)
locHistory.value.push({
x: curLoc.x,
y: curLoc.y,
})
context.lineWidth = 3
context.strokeStyle = '#FF0000';
context.lineCap = "round"
context.linJoin = "round"
context.stroke();
//每次过程结束时,将结束值赋给初始值,一直延续
lastLoc.value = curLoc
}
// 前端截图
const goCaptureImg = () => {
getPatrolTemplateList()
getAssociateList()
getUserList()
Toast.loading({
duration: 0,
message: '视频截图中···',
forbidClick: true,
});
vionPlayer.value.screenshot().then(data => {
screenshotEnd(data)
}).catch(err => {
})
}
// 截图出口
const screenshotEnd = (imgData) => {
console.log(imgData)
let reg = new RegExp('data:image/jpeg;base64,');
let regPng = new RegExp('data:image/png;base64,');
let pic = imgData.replace(reg, '')
pic = pic.replace(regPng, '')
tourApi.uploadScreenshot({
gateId: paramObj.id,
pic: pic
}).then(res => {
// console.log(res)
let result = res.data;
if (result.code == 200) {
Toast.success('截图成功');
pictureProcess(result.data.pic)
} else {
Toast.success(result.msg);
}
})
}
const pictureProcess = (imgUrl) => {
picUrl.value = imgUrl;
Toast.clear();
if (paramObj.type != 'titem') {
// picUrl.value = 'https://store.keliuyun.com/images/patrol/capture/20230825/ecd2a635-84af-4968-bc30-1af130460d12.jpg'
vionPlayer.value.stopPlay();
let image = new Image()
image.src = 'https://store.keliuyun.com/images/' + picUrl.value;
image.style.width = window.innerWidth + 'px';
let heightC = window.innerWidth / 1.78
image.style.height = heightC + 'px'
image.crossOrigin = 'Anonymous'
image.onload = () => {
captureCanvas.value.width = window.innerWidth;
captureCanvas.value.height = heightC;
let ctx = captureCanvas.value.getContext("2d");
ctx.drawImage(image, 0, 0, window.innerWidth, heightC);
capture()
}
} else {
/***********巡店详情截图****************/
if(paramObj.newLevel) {
wx.miniProgram.redirectTo({
url: `/pages/tour/inspectionDetail/index?action=capture&picUrl=${imgUrl}&id=${paramObj.tid}`
});
} else {
wx.miniProgram.redirectTo({
url: `/pages/tour/titemDetail/index?action=capture&picUrl=${picUrl}&id=${paramObj.tid}`
});
}
}
};
onMounted(() => {
reqPatrolGateList()
reqGateGroupList()
})
</script>
<style scoped lang="less">
.tour-btns {
display: flex;
justify-content: space-between;
padding: 20px 2vw 0;
--van-button-border-radius: 60px;
:deep(.van-button) {
margin-bottom: 20px;
border-radius: 60px;
width: 47vw;
&.van-button--block {
width: 100%;
}
}
}
.tour-btns1 {
display: flex;
background: #E6EDFF;
height: 15vw;
line-height: 15vw;
padding: 0 20px;
.box {
display: inline-block;
flex: 1;
}
.box1 {
text-align: right;
span {
margin-left: 20px;
}
}
.icon {
transform: rotate(90deg);
}
}
.iconMargin {
margin-right: 10px;
}
.itemList {
overflow: auto;
height: 50%;
}
:deep(.van-cell-group--inset) {
margin: 0;
}
.template-select-box {
background: #fff;
}
.template-select-list {
padding: 30px 0px 10px;
margin: 0 30px;
border-bottom: 1px solid var(--van-cell-border-color);
.template-select-one {
margin: 0 20px 20px 0;
padding: 1.8vw;
/deep/.van-tag__close {
margin-left: 10px;
}
}
}
.template-name-box {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 0 30px;
border-bottom: 1px solid var(--van-cell-border-color);
}
.template-name-content {
display: flex;
align-items: center;
}
.template-name-title {
width: 5em;
min-width: 5em;
font-size: var(--van-cell-font-size);
color: var(--van-field-label-color);
}
.template-btn {
width: 6em;
}
.template-name-content /deep/.van-cell {
padding-left: 0px;
}
.template-list-model {
.template-list-box {
width: calc(100vw - 0px);
height: calc(80vh - 0px);
padding: 30px;
background: #fff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
.template-list-title {
font-size: var(--van-font-size-lg);
line-height: var(--van-font-size-lg);
margin-bottom: 10px;
}
.template-search-box{
// border-top: 1px solid #cccccc;
border-bottom: 1px solid #cccccc;
margin-bottom: 30px;
}
.template-list {
height: 54vh;
overflow: auto;
}
.template-list-submit {
margin-top: 30px;
}
}
}
.gate-name-box{
display: inline-flex!important;
align-items: center;
}
.gate-name-edit-img{
width: 40px;
height:40px;
display: inline-block;
margin-left: 20px;
}
.group-remark{
font-size: var(--van-calendar-info-font-size);
line-height: 34px;
color: #bbb;
margin-top: 20px;
}
.grop-name-required{
position: relative;
top:4px;
font-size: 12px;
margin-left: 8px;
color: var(--van-field-required-mark-color);
content: "*";
}
.text-ellipsis{
display: inline-block;
max-width: 30vw;
margin-right: 10px;
overflow:hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow:ellipsis;
}
</style>
\ No newline at end of file
import req from "@/api/http.js";
const heatmap = {
//获取设备通道
getChannelsListApi(params, config) {
return req("get", `/report/channels`, params, config);
},
// 获取热力图数据
getHeatMapValueAPi(params, config) {
return req("post", `/report/heatMap/get`, params, config);
},
};
export default heatmap;
import { createApp } from 'vue';
import wx from 'weixin-js-sdk';
import { createApp } from "vue";
import wx from "weixin-js-sdk";
window.wx = wx;
import App from './App.vue';
import Vant from 'vant';
import '@s/css/index.css';
import 'vant/lib/index.css';
import router from "./router";
import App from "./App.vue";
import Vant from "vant";
import "@s/css/index.css";
import "vant/lib/index.css";
const app = createApp(App);
app.use(Vant);
app.mount('#app')
app.use(router);
app.mount("#app");
import { createRouter, createWebHistory } from "vue-router";
// 示例路由配置
const routes = [
{
path: "/",
name: "Video",
component: () => import("@/views/video/index.vue"),
},
{
path: "/heatMap",
name: "HeatMap",
component: () => import("@/views/heatMap/index.vue"),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
/*
* heatmap.js v2.0.5 | JavaScript Heatmap Library
*
* Copyright 2008-2016 Patrick Wied <heatmapjs@patrick-wied.at> - All rights reserved.
* Dual licensed under MIT and Beerware license
*
* :: 2016-09-05 01:16
*/
// Heatmap Config stores default values and will be merged with instance config
var HeatmapConfig = {
defaultRadius: 40,
defaultRenderer: "canvas2d",
defaultGradient: {
0.25: "rgb(0,0,255)",
0.55: "rgb(0,255,0)",
0.85: "yellow",
1.0: "rgb(255,0,0)",
},
defaultMaxOpacity: 1,
defaultMinOpacity: 0,
defaultBlur: 0.85,
defaultXField: "x",
defaultYField: "y",
defaultValueField: "value",
plugins: {},
};
var Store = (function StoreClosure() {
var Store = function Store(config) {
this._coordinator = {};
this._data = [];
this._radi = [];
this._min = 10;
this._max = 1;
this._xField = config["xField"] || config.defaultXField;
this._yField = config["yField"] || config.defaultYField;
this._valueField = config["valueField"] || config.defaultValueField;
if (config["radius"]) {
this._cfgRadius = config["radius"];
}
};
var defaultRadius = HeatmapConfig.defaultRadius;
Store.prototype = {
// when forceRender = false -> called from setData, omits renderall event
_organiseData: function (dataPoint, forceRender) {
var x = dataPoint[this._xField];
var y = dataPoint[this._yField];
var radi = this._radi;
var store = this._data;
var max = this._max;
var min = this._min;
var value = dataPoint[this._valueField] || 1;
var radius = dataPoint.radius || this._cfgRadius || defaultRadius;
if (!store[x]) {
store[x] = [];
radi[x] = [];
}
if (!store[x][y]) {
store[x][y] = value;
radi[x][y] = radius;
} else {
store[x][y] += value;
}
var storedVal = store[x][y];
if (storedVal > max) {
if (!forceRender) {
this._max = storedVal;
} else {
this.setDataMax(storedVal);
}
return false;
} else if (storedVal < min) {
if (!forceRender) {
this._min = storedVal;
} else {
this.setDataMin(storedVal);
}
return false;
} else {
return {
x: x,
y: y,
value: value,
radius: radius,
min: min,
max: max,
};
}
},
_unOrganizeData: function () {
var unorganizedData = [];
var data = this._data;
var radi = this._radi;
for (var x in data) {
for (var y in data[x]) {
unorganizedData.push({
x: x,
y: y,
radius: radi[x][y],
value: data[x][y],
});
}
}
return {
min: this._min,
max: this._max,
data: unorganizedData,
};
},
_onExtremaChange: function () {
this._coordinator.emit("extremachange", {
min: this._min,
max: this._max,
});
},
addData: function () {
if (arguments[0].length > 0) {
var dataArr = arguments[0];
var dataLen = dataArr.length;
while (dataLen--) {
this.addData.call(this, dataArr[dataLen]);
}
} else {
// add to store
var organisedEntry = this._organiseData(arguments[0], true);
if (organisedEntry) {
// if it's the first datapoint initialize the extremas with it
if (this._data.length === 0) {
this._min = this._max = organisedEntry.value;
}
this._coordinator.emit("renderpartial", {
min: this._min,
max: this._max,
data: [organisedEntry],
});
}
}
return this;
},
setData: function (data) {
var dataPoints = data.data;
var pointsLen = dataPoints.length;
// reset data arrays
this._data = [];
this._radi = [];
for (var i = 0; i < pointsLen; i++) {
this._organiseData(dataPoints[i], false);
}
this._max = data.max;
this._min = data.min || 0;
this._onExtremaChange();
this._coordinator.emit("renderall", this._getInternalData());
return this;
},
removeData: function () {
// TODO: implement
},
setDataMax: function (max) {
this._max = max;
this._onExtremaChange();
this._coordinator.emit("renderall", this._getInternalData());
return this;
},
setDataMin: function (min) {
this._min = min;
this._onExtremaChange();
this._coordinator.emit("renderall", this._getInternalData());
return this;
},
setCoordinator: function (coordinator) {
this._coordinator = coordinator;
},
_getInternalData: function () {
return {
max: this._max,
min: this._min,
data: this._data,
radi: this._radi,
};
},
getData: function () {
return this._unOrganizeData();
},
/*,
TODO: rethink.
getValueAt: function(point) {
var value;
var radius = 100;
var x = point.x;
var y = point.y;
var data = this._data;
if (data[x] && data[x][y]) {
return data[x][y];
} else {
var values = [];
// radial search for datapoints based on default radius
for(var distance = 1; distance < radius; distance++) {
var neighbors = distance * 2 +1;
var startX = x - distance;
var startY = y - distance;
for(var i = 0; i < neighbors; i++) {
for (var o = 0; o < neighbors; o++) {
if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) {
if (data[startY+i] && data[startY+i][startX+o]) {
values.push(data[startY+i][startX+o]);
}
} else {
continue;
}
}
}
}
if (values.length > 0) {
return Math.max.apply(Math, values);
}
}
return false;
}*/
};
return Store;
})();
var Canvas2dRenderer = (function Canvas2dRendererClosure() {
var _getColorPalette = function (config) {
var gradientConfig = config.gradient || config.defaultGradient;
var paletteCanvas = document.createElement("canvas");
var paletteCtx = paletteCanvas.getContext("2d");
paletteCanvas.width = 256;
paletteCanvas.height = 1;
var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
for (var key in gradientConfig) {
gradient.addColorStop(key, gradientConfig[key]);
}
paletteCtx.fillStyle = gradient;
paletteCtx.fillRect(0, 0, 256, 1);
return paletteCtx.getImageData(0, 0, 256, 1).data;
};
var _getPointTemplate = function (radius, blurFactor) {
var tplCanvas = document.createElement("canvas");
var tplCtx = tplCanvas.getContext("2d");
var x = radius;
var y = radius;
tplCanvas.width = tplCanvas.height = radius * 2;
if (blurFactor == 1) {
tplCtx.beginPath();
tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
tplCtx.fillStyle = "rgba(0,0,0,1)";
tplCtx.fill();
} else {
var gradient = tplCtx.createRadialGradient(
x,
y,
radius * blurFactor,
x,
y,
radius
);
gradient.addColorStop(0, "rgba(0,0,0,1)");
gradient.addColorStop(1, "rgba(0,0,0,0)");
tplCtx.fillStyle = gradient;
tplCtx.fillRect(0, 0, 2 * radius, 2 * radius);
}
return tplCanvas;
};
var _prepareData = function (data) {
var renderData = [];
var min = data.min;
var max = data.max;
var radi = data.radi;
var data = data.data;
var xValues = Object.keys(data);
var xValuesLen = xValues.length;
while (xValuesLen--) {
var xValue = xValues[xValuesLen];
var yValues = Object.keys(data[xValue]);
var yValuesLen = yValues.length;
while (yValuesLen--) {
var yValue = yValues[yValuesLen];
var value = data[xValue][yValue];
var radius = radi[xValue][yValue];
renderData.push({
x: xValue,
y: yValue,
value: value,
radius: radius,
});
}
}
return {
min: min,
max: max,
data: renderData,
};
};
function Canvas2dRenderer(config) {
var container = config.container;
var shadowCanvas = (this.shadowCanvas = document.createElement("canvas"));
var canvas = (this.canvas =
config.canvas || document.createElement("canvas"));
var renderBoundaries = (this._renderBoundaries = [10000, 10000, 0, 0]);
var computed = getComputedStyle(config.container) || {};
canvas.className = "heatmap-canvas";
this._width =
canvas.width =
shadowCanvas.width =
config.width || +computed.width.replace(/px/, "");
this._height =
canvas.height =
shadowCanvas.height =
config.height || +computed.height.replace(/px/, "");
this.shadowCtx = shadowCanvas.getContext("2d");
this.ctx = canvas.getContext("2d");
// @TODO:
// conditional wrapper
canvas.style.cssText = shadowCanvas.style.cssText =
"position:absolute;left:0;top:0;";
container.style.position = "relative";
container.appendChild(canvas);
this._palette = _getColorPalette(config);
this._templates = {};
this._setStyles(config);
}
Canvas2dRenderer.prototype = {
renderPartial: function (data) {
if (data.data.length > 0) {
this._drawAlpha(data);
this._colorize();
}
},
renderAll: function (data) {
// reset render boundaries
this._clear();
if (data.data.length > 0) {
this._drawAlpha(_prepareData(data));
this._colorize();
}
},
_updateGradient: function (config) {
this._palette = _getColorPalette(config);
},
updateConfig: function (config) {
if (config["gradient"]) {
this._updateGradient(config);
}
this._setStyles(config);
},
setDimensions: function (width, height) {
this._width = width;
this._height = height;
this.canvas.width = this.shadowCanvas.width = width;
this.canvas.height = this.shadowCanvas.height = height;
},
_clear: function () {
this.shadowCtx.clearRect(0, 0, this._width, this._height);
this.ctx.clearRect(0, 0, this._width, this._height);
},
_setStyles: function (config) {
this._blur = config.blur == 0 ? 0 : config.blur || config.defaultBlur;
if (config.backgroundColor) {
this.canvas.style.backgroundColor = config.backgroundColor;
}
this._width =
this.canvas.width =
this.shadowCanvas.width =
config.width || this._width;
this._height =
this.canvas.height =
this.shadowCanvas.height =
config.height || this._height;
this._opacity = (config.opacity || 0) * 255;
this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255;
this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255;
this._useGradientOpacity = !!config.useGradientOpacity;
},
_drawAlpha: function (data) {
var min = (this._min = data.min);
var max = (this._max = data.max);
var data = data.data || [];
var dataLen = data.length;
// on a point basis?
var blur = 1 - this._blur;
while (dataLen--) {
var point = data[dataLen];
var x = point.x;
var y = point.y;
var radius = point.radius;
// if value is bigger than max
// use max as value
var value = Math.min(point.value, max);
var rectX = x - radius;
var rectY = y - radius;
var shadowCtx = this.shadowCtx;
var tpl;
if (!this._templates[radius]) {
this._templates[radius] = tpl = _getPointTemplate(radius, blur);
} else {
tpl = this._templates[radius];
}
// value from minimum / value range
// => [0, 1]
var templateAlpha = (value - min) / (max - min);
// this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData
shadowCtx.globalAlpha = templateAlpha < 0.01 ? 0.01 : templateAlpha;
shadowCtx.drawImage(tpl, rectX, rectY);
// update renderBoundaries
if (rectX < this._renderBoundaries[0]) {
this._renderBoundaries[0] = rectX;
}
if (rectY < this._renderBoundaries[1]) {
this._renderBoundaries[1] = rectY;
}
if (rectX + 2 * radius > this._renderBoundaries[2]) {
this._renderBoundaries[2] = rectX + 2 * radius;
}
if (rectY + 2 * radius > this._renderBoundaries[3]) {
this._renderBoundaries[3] = rectY + 2 * radius;
}
}
},
_colorize: function () {
var x = this._renderBoundaries[0];
var y = this._renderBoundaries[1];
var width = this._renderBoundaries[2] - x;
var height = this._renderBoundaries[3] - y;
var maxWidth = this._width;
var maxHeight = this._height;
var opacity = this._opacity;
var maxOpacity = this._maxOpacity;
var minOpacity = this._minOpacity;
var useGradientOpacity = this._useGradientOpacity;
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
if (x + width > maxWidth) {
width = maxWidth - x;
}
if (y + height > maxHeight) {
height = maxHeight - y;
}
var img = this.shadowCtx.getImageData(x, y, width, height);
var imgData = img.data;
var len = imgData.length;
var palette = this._palette;
for (var i = 3; i < len; i += 4) {
var alpha = imgData[i];
var offset = alpha * 4;
if (!offset) {
continue;
}
var finalAlpha;
if (opacity > 0) {
finalAlpha = opacity;
} else {
if (alpha < maxOpacity) {
if (alpha < minOpacity) {
finalAlpha = minOpacity;
} else {
finalAlpha = alpha;
}
} else {
finalAlpha = maxOpacity;
}
}
imgData[i - 3] = palette[offset];
imgData[i - 2] = palette[offset + 1];
imgData[i - 1] = palette[offset + 2];
imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
}
// img.data = imgData;
this.ctx.putImageData(img, x, y);
this._renderBoundaries = [1000, 1000, 0, 0];
},
getValueAt: function (point) {
var value;
var shadowCtx = this.shadowCtx;
var img = shadowCtx.getImageData(point.x, point.y, 1, 1);
var data = img.data[3];
var max = this._max;
var min = this._min;
value = (Math.abs(max - min) * (data / 255)) >> 0;
return value;
},
getDataURL: function () {
return this.canvas.toDataURL();
},
};
return Canvas2dRenderer;
})();
var Renderer = (function RendererClosure() {
var rendererFn = false;
if (HeatmapConfig["defaultRenderer"] === "canvas2d") {
rendererFn = Canvas2dRenderer;
}
return rendererFn;
})();
var Util = {
merge: function () {
var merged = {};
var argsLen = arguments.length;
for (var i = 0; i < argsLen; i++) {
var obj = arguments[i];
for (var key in obj) {
merged[key] = obj[key];
}
}
return merged;
},
};
// Heatmap Constructor
var Heatmap = (function HeatmapClosure() {
var Coordinator = (function CoordinatorClosure() {
function Coordinator() {
this.cStore = {};
}
Coordinator.prototype = {
on: function (evtName, callback, scope) {
var cStore = this.cStore;
if (!cStore[evtName]) {
cStore[evtName] = [];
}
cStore[evtName].push(function (data) {
return callback.call(scope, data);
});
},
emit: function (evtName, data) {
var cStore = this.cStore;
if (cStore[evtName]) {
var len = cStore[evtName].length;
for (var i = 0; i < len; i++) {
var callback = cStore[evtName][i];
callback(data);
}
}
},
};
return Coordinator;
})();
var _connect = function (scope) {
var renderer = scope._renderer;
var coordinator = scope._coordinator;
var store = scope._store;
coordinator.on("renderpartial", renderer.renderPartial, renderer);
coordinator.on("renderall", renderer.renderAll, renderer);
coordinator.on("extremachange", function (data) {
scope._config.onExtremaChange &&
scope._config.onExtremaChange({
min: data.min,
max: data.max,
gradient:
scope._config["gradient"] || scope._config["defaultGradient"],
});
});
store.setCoordinator(coordinator);
};
function Heatmap() {
var config = (this._config = Util.merge(HeatmapConfig, arguments[0] || {}));
this._coordinator = new Coordinator();
if (config["plugin"]) {
var pluginToLoad = config["plugin"];
if (!HeatmapConfig.plugins[pluginToLoad]) {
throw new Error(
"Plugin '" +
pluginToLoad +
"' not found. Maybe it was not registered."
);
} else {
var plugin = HeatmapConfig.plugins[pluginToLoad];
// set plugin renderer and store
this._renderer = new plugin.renderer(config);
this._store = new plugin.store(config);
}
} else {
this._renderer = new Renderer(config);
this._store = new Store(config);
}
_connect(this);
}
// @TODO:
// add API documentation
Heatmap.prototype = {
addData: function () {
this._store.addData.apply(this._store, arguments);
return this;
},
removeData: function () {
this._store.removeData &&
this._store.removeData.apply(this._store, arguments);
return this;
},
setData: function () {
this._store.setData.apply(this._store, arguments);
return this;
},
setDataMax: function () {
this._store.setDataMax.apply(this._store, arguments);
return this;
},
setDataMin: function () {
this._store.setDataMin.apply(this._store, arguments);
return this;
},
configure: function (config) {
this._config = Util.merge(this._config, config);
this._renderer.updateConfig(this._config);
this._coordinator.emit("renderall", this._store._getInternalData());
return this;
},
repaint: function () {
this._coordinator.emit("renderall", this._store._getInternalData());
return this;
},
getData: function () {
return this._store.getData();
},
getDataURL: function () {
return this._renderer.getDataURL();
},
getValueAt: function (point) {
if (this._store.getValueAt) {
return this._store.getValueAt(point);
} else if (this._renderer.getValueAt) {
return this._renderer.getValueAt(point);
} else {
return null;
}
},
};
return Heatmap;
})();
// core
var heatmapFactory = {
create: function (config) {
return new Heatmap(config);
},
register: function (pluginKey, plugin) {
HeatmapConfig.plugins[pluginKey] = plugin;
},
};
export { heatmapFactory };
<template>
<div>
<div class="canvas">
<img
:src="floorImage"
class="editFloorimg"
id="editFloorimg"
style="width: 100%"
/>
<div
class="canvas-position"
id="canvas-position"
@mousemove="mousemoveHandle"
@mouseout="mouseoutHandle"
></div>
</div>
<!-- <el-slider v-model="sliderVal" :marks="marks" :format-tooltip="formatTooltip" :max="sliderMax" :min="1" vertical @change="slideHandle(sliderVal)" height="200px"></el-slider> -->
<van-slider
bar-height="20px"
:max="sliderMax"
:min="1"
v-model="sliderVal"
@change="slideHandle"
/>
</div>
</template>
<script setup>
import { onMounted, watch, ref, nextTick } from "vue";
import { useRoute } from "vue-router";
import heatmap from "@/api/heatmap"; // 假设你的API文件路径
import { heatmapFactory } from "@/utils/heatmap.js";
let map = null; // 地图对象
const $route = useRoute();
// 监听route参数
// ?atoken='xxx'&mallId='xxx'
const storeId = ref(""); // 店铺id
const channelList = ref([]); // 渠道列表
const getChannelList = async (mallId) => {
try {
const { data } = await heatmap.getChannelsListApi({ mallId, status: 1 });
if (data.code === 200) {
channelList.value =
data.data?.map((item) => {
return item.serialnum;
}) || [];
if (channelList.value.length) {
getHeatMapData({
mallId,
channelList: channelList.value,
});
}
} else {
console.error("Failed to fetch channel list:", data.message);
channelList.value = [];
}
} catch (error) {
console.error("Error fetching channel list:", error);
}
};
// 获取热力图数据
const getHeatMapData = async (params) => {
try {
const options = {
channelSerialnum_arr: params.channelList,
type: 2,
genders: [1, 0],
ages: ["0-18", "19-35", "36-55", "56-100"],
personTypes: [0],
counttime_gte: "2025-05-29 10:00:00",
counttime_lte: "2025-05-29 22:00:00",
countdate: "2025-05-29",
mallId: params.mallId,
};
const { data } = await heatmap.getHeatMapValueAPi(options);
if (data.code === 200) {
heatDataObj.value = data.data || [];
nextTick(() => {
dealHeatData();
});
} else {
}
} catch (error) {
console.error("Error fetching heat map data:", error);
return [];
}
};
const floorImage =
"https://store.keliuyun.com/images/report/mallPic/32a04280-5a32-4b0e-a2a5-15874da46a1120240826163115.jpg"; // 楼层图片
const heatInstance = ref(null);
const heatRadius = ref(10); // 热力图半径
const heatDataObj = ref([]); // 热力图数据对象
const timeLevel = ref("rt"); // 时间级别,默认实时 rt:停留时长 rc:顾客人次
const normalWidth = ref(100);
const interpolationCache = ref([]); // 插值缓存
const pointsData = ref([]); // 热力图点数据
const sliderMax = ref(0); // 滑块最大值
const sliderVal = ref(0); // 滑块当前值
const marks = ref({}); // 滑块标记
function dealHeatData() {
const img = document.getElementById("editFloorimg");
console.log(img.complete, img.naturalWidth);
if (!img.complete || img.naturalWidth === 0) {
// 等待图片加载完成
img.onload = () => dealHeatData();
return;
}
if (heatInstance.value) {
heatInstance.value.destroy();
}
let { width, height, naturalWidth, naturalHeight } =
document.getElementById("editFloorimg");
let radix = 0;
if (naturalWidth === 0 || naturalHeight === 0) {
return;
}
if (naturalWidth !== 0 && naturalHeight !== 0) {
radix = width / naturalWidth;
if (heatInstance.value) {
heatInstance.value.setData({ data: [] });
}
let heatDom = document.getElementById("canvas-position");
heatDom.style.width = width + "px";
heatDom.style.height = height + "px";
heatInstance.value = heatmapFactory.create({
container: heatDom,
radius: heatRadius.value,
onExtremaChange: function (data) {
console.log(data);
},
});
let points = [];
let maxVal = 1;
heatDataObj.value.forEach((item, index) => {
const x = parseInt((item.rx / normalWidth.value) * width);
const y = parseInt((item.ry / normalWidth.value) * height);
maxVal = maxVal > item[timeLevel.value] ? maxVal : item[timeLevel.value];
points.push({
x: combinePoint(x),
y: combinePoint(y),
value: item[timeLevel.value] || 1,
});
});
let newPoints = [];
points.forEach((one) => {
let isAdd = true;
for (let i = 0; i < newPoints.length; i++) {
if (newPoints[i].x == one.x && newPoints[i].y == one.y) {
newPoints[i].value = newPoints[i].value + one.value;
isAdd = false;
break;
}
}
if (isAdd) {
newPoints.push({
x: one.x,
y: one.y,
value: one.value,
});
}
});
newPoints.forEach((item, index) => {
maxVal = maxVal > item.value ? maxVal : item.value;
});
interpolationCache.value = [];
// let maxVal = Math.max(..._.pluck(points, "value")) * 5;
// maxVal = maxVal*10;
pointsData.value = newPoints;
console.log(newPoints, "--s");
heatInstance.value.setData({ data: newPoints });
heatInstance.value.setDataMax(maxVal);
let newMax =
timeLevel.value == "rt"
? (parseInt(maxVal / 3600) * 1 + 1) * 3600
: maxVal;
let newVal = newMax / 4;
// 固定最大值好默认值
sliderMax.value = newMax;
sliderVal.value = newVal;
slideHandle(sliderVal.value);
}
}
function combinePoint(point, pointToler = 8) {
return parseInt(point - (point % pointToler));
}
function slideHandle(val) {
marks.value = {};
marks.value[val] =
timeLevel.value == "rt" ? getTimeMin(val) : Math.round(val) + "";
heatInstance.value.setDataMax(sliderVal.value);
}
function getTimeMin(seconds) {
if (isNaN(seconds)) return seconds;
return (
numFormat(parseInt(seconds / 3600)) +
":" +
numFormat(parseInt((seconds % 3600) / 60)) +
":00"
);
}
function numFormat(val) {
return val > 9 ? val : "0" + val;
}
watch(
() => $route.query,
(newVal) => {
const { token, mallId } = newVal;
if (token) {
window.localStorage.setItem("atoken", token);
}
if (mallId) {
storeId.value = mallId;
getChannelList(mallId);
}
},
{ immediate: true }
);
</script>
<style>
.canvas {
position: relative;
width: 100%;
}
.canvas .canvas-position {
position: absolute !important;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
</style>
<template>
<div class="fullPage touchmove">
<div v-if="picUrl">
<!-- <img mode="widthFix" style="position:relative;width:100vw;height: 50vwpx;" :src="picUrl"/> -->
<canvas id="painter" ref="captureCanvas"></canvas>
<div class="template-name-box">
<div class="template-name-content">
<span class="template-name-title">模板名称:</span>
<van-cell :title="templateNameDefault" @click="show = true" is-link />
<van-popup v-model:show="show" position="bottom">
<van-picker
:columns="templateNameList"
@confirm="onConfirm"
@cancel="onCancel"
/>
</van-popup>
</div>
<van-button
class="template-btn"
plain
type="primary"
size="small"
@click="templateClick"
>
+ 问题项</van-button
>
</div>
<div class="template-select-box" v-if="checkboxRefsShow.length > 0">
<div class="template-select-list">
<van-tag
class="template-select-one"
v-for="(item, index) in checkboxRefsShow"
:key="index"
closeable
size="large"
type="primary"
@close="deleteTemplate(item, index)"
>
{{ item.name }}
</van-tag>
</div>
</div>
<van-popup
v-model:show="templateDialogShow"
position="bottom"
class="template-list-model"
closeable
round
>
<div class="template-list-box">
<div class="template-list-title">添加问题项</div>
<div class="template-search-box">
<form action="/">
<van-search
v-model="templateSearch"
placeholder="请输入搜索问题项名称"
@search="templateSearchChange"
/>
</form>
</div>
<div class="template-list">
<van-checkbox-group
shape="square"
v-model="checked"
ref="checkboxGroup"
>
<van-collapse v-model="templatecollapseNames">
<van-collapse-item
v-for="(item, index) in templateGroupList"
:key="item.id"
:title="item.name"
:name="item.id"
>
<van-cell-group inset>
<van-cell
v-for="(one, ind) in item.sopProjects"
center
clickable
:key="one.id"
:title="one.name"
@click="toggle(ind)"
>
<template #right-icon>
<van-checkbox
:name="one.id"
:ref="(el) => (checkboxRefs[one.allIndex] = el)"
@click.stop
/>
</template>
</van-cell>
</van-cell-group>
</van-collapse-item>
</van-collapse>
</van-checkbox-group>
</div>
<van-button
class="template-list-submit"
round
block
type="primary"
@click="templateSubmit"
>确认</van-button
>
</div>
</van-popup>
<div class="formBox">
<van-field
v-model="message"
rows="2"
autosize
label="备注"
type="textarea"
placeholder="请输入备注"
/>
<van-field name="radio" label="是否需要整改">
<template #input>
<van-radio-group v-model="isRectify" direction="horizontal">
<van-radio name="1">需要</van-radio>
<van-radio name="2">不需要</van-radio>
</van-radio-group>
</template>
</van-field>
<van-field
v-show="isRectify == 1"
v-model="rectificationUserName"
is-link
readonly
name="picker"
label="整改人"
placeholder="点击选择整改人"
@click="showRectification = true"
/>
<van-popup v-model:show="showRectification" position="bottom">
<van-picker
:columns="userNameList"
@confirm="onUserConfirm"
@cancel="showRectification = false"
/>
</van-popup>
<div
style="
padding: 15px;
display: flex;
align-items: center;
justify-content: space-around;
"
>
<van-button style="width: 45%" round block @click="goBackVideo"
>返回</van-button
>
<van-button
style="width: 45%"
round
block
type="primary"
@click="submitContent"
>提交</van-button
>
</div>
</div>
</div>
<div v-else>
<livePlayer ref="vionPlayer" @screenshotEnd="screenshotEnd"></livePlayer>
<div class="tour-btns" v-if="paramObj.type == 'titem'">
<van-button @click="navBackTour" type="primary">返回上一页</van-button>
<van-button @click="goCaptureImg" type="primary">确认并截图</van-button>
</div>
<div class="tour-btns" v-else-if="paramObj.type == 'view'">
<van-button block @click="navBack" type="primary"
>查看其他监控点</van-button
>
</div>
<div class="tour-btns1" v-else>
<div class="box gate-name-box">
<span
@click="navBack"
class=""
style="display: inline-flex; align-items: center"
>
<span class="text-ellipsis">{{ gateEditName }}</span>
<van-icon class="icon" name="play" />
</span>
<img
src="../src/assets/edit.png"
alt=""
class="gate-name-edit-img"
@click="gateEdit"
/>
</div>
<div class="box box1">
<span v-if="bookmark" @click="delCollectGate">
<van-icon class="iconMargin" color="#F7B500" name="star" />收藏
</span>
<span v-else @click="collectGate">
<van-icon class="iconMargin" name="star-o" />收藏
</span>
<span @click="goCaptureImg">
<van-icon class="iconMargin" name="photograph" />巡检
</span>
</div>
</div>
<!-- 监控点选择器 -->
<van-popup v-model:show="pointPopupShow" round position="bottom">
<van-picker
:default-index="pointListIndex"
:title="paramObj.mallName"
:columns="pointList"
:option-height="40"
@confirm="pointConfirm"
@cancel="pointPopupShow = false"
/>
</van-popup>
<!-- 监控场景名称修改 -->
<van-dialog
use-slot
title="监控点信息修改"
v-model:show="gateEditShow"
show-cancel-button
@cancel="gateEditHide"
@confirm="gateEditSave"
:confirm-button-disabled="gateEditName.trim() == ''"
>
<div style="margin: 20px 10px">
<van-cell-group>
<van-field v-model="gateEditName" placeholder="请输入监控点名称">
<template #label>
<span style="display: inline-flex; align-items: center">
监控点名称
<span class="grop-name-required">*</span>
</span>
</template>
</van-field>
</van-cell-group>
<van-cell-group style="display: flex; align-items: center">
<van-field
style="display: inline-flex"
v-model="gateEditGroup"
label="所属分组"
placeholder="请输入分组名称"
>
</van-field>
<van-button
style="width: 14vw"
size="small"
type="primary"
@click="groupPicker = true"
>选择</van-button
>
</van-cell-group>
<div class="group-remark">
<div>所属分组信息允许为空。</div>
<div>新分组可直接输入名称自动创建,已有分组信息从列表中选择。</div>
</div>
</div>
</van-dialog>
<!-- 监控场景分组选择 -->
<van-popup v-model:show="groupPicker" round position="bottom">
<van-picker
:default-index="gateEditGroupIndex"
:columns="groupList"
@cancel="groupPicker = false"
@confirm="groupChange"
/>
</van-popup>
<!-- 监控场景添加分组 -->
<van-dialog
use-slot
title="新增分组"
v-model:show="groupAddShow"
show-cancel-button
@cancel="groupAddHide"
@confirm="groupAddSave"
:confirm-button-disabled="groupAddName.trim() == ''"
>
<div style="margin: 20px 10px">
<van-cell-group>
<van-field
v-model="groupAddName"
required
label="分组名称"
placeholder="请输入分组名称"
/>
</van-cell-group>
</div>
</van-dialog>
</div>
</div>
</template>
<script setup>
import { reactive, ref, onMounted, getCurrentInstance } from "vue";
import { Toast } from "vant";
import livePlayer from "@/components/extension/index.vue";
import shopTour from "@/components/shopTour.vue";
//import vconsole from 'vconsole';
if (process.env.NODE_ENV !== "production") {
//new vconsole();
}
/*获取 Url 参数 S*/
import parse from "url-param-parser";
// let url = 'https://store.keliuyun.com/video/?userId=8840&atoken=8eaf1373-654b-4cf2-a3c7-f90d42cba574&type=undefined&id=632&name=会议室门口&mallId=9217&mallName=河南分公司&bookmark=false&channelNo=4&deviceSerial=F16423875&gateUnid=cf21f4a8-65c6-11ee-837e-00163e143ecd&ptzEnable=0&aiChannelId=&mallOrgName=办公室&accountId=337&terminalType=devtools&newLevel=' // 萤石
// const paramObj = parse(url).search || {}
const paramObj = parse(window.location.href).search || {};
/********************************/
paramObj.name = paramObj.name ? decodeURIComponent(paramObj.name) : "";
paramObj.mallName = paramObj.mallName
? decodeURIComponent(paramObj.mallName)
: "";
paramObj.mallOrgName = paramObj.mallOrgName
? decodeURIComponent(paramObj.mallOrgName)
: "";
document.title = paramObj.name;
window.localStorage.setItem("atoken", paramObj.atoken);
window.localStorage.setItem("lang", "zh_CN");
/*获取 Url 参数 E*/
import tourApi from "@/api";
//var data = reactive(v);
const dataURLtoFile = (dataurl, filename, filetype) => {
var arr = dataurl.split(","),
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {
type: filetype,
});
};
const navBack = () => {
pointPopupShow.value = true;
console.log(paramObj);
};
// 监控场景编辑
const gateEditShow = ref(false);
const groupPicker = ref(false);
const gateEditName = ref("");
const gateEditGroup = ref("");
const gateEditGroupIndex = ref(0);
gateEditName.value = paramObj.name;
gateEditGroup.value = paramObj.mallOrgName;
// 监控场景编辑弹框显示
const gateEdit = () => {
gateEditName.value = paramObj.name;
gateEditGroup.value = paramObj.mallOrgName;
gateEditShow.value = true;
};
// 监控场景编辑弹框关闭
const gateEditHide = () => {
gateEditName.value = paramObj.name;
gateEditGroup.value = paramObj.mallOrgName;
gateEditShow.value = false;
};
// 监控场景编辑保存
const gateEditSave = () => {
let par = {
id: paramObj.id,
mallId: paramObj.mallId,
aiChannelId: paramObj.aiChannelId ? paramObj.aiChannelId : "",
name: gateEditName.value,
mallOrgName: gateEditGroup.value,
};
tourApi.editPatrolGate(par).then((res) => {
if (res.data.code == 200) {
// 更新监控点名称
gateEditShow.value = false;
paramObj.name = gateEditName.value;
paramObj.mallOrgName = gateEditGroup.value;
document.title = paramObj.name;
reqPatrolGateList();
reqGateGroupList();
} else {
Toast.fail("修改失败");
}
});
};
// 监控场景分组修改
const groupChange = (val) => {
gateEditGroup.value = val.name;
gateEditGroupIndex.value = groupList.value.filter(
(v) => v.text == gateEditGroup.value
)[0].index;
groupPicker.value = false;
};
// 获取分组列表
const groupList = ref([]);
const reqGateGroupList = () => {
tourApi.getDeviceGroupList(paramObj.mallId).then((res) => {
if (res.data.code == 200) {
let list = res.data.data ? res.data.data : [];
for (let i = 0; i < list.length; i++) {
list[i].text = list[i].name;
list[i].value = list[i].id;
list[i].index = i;
}
groupList.value = list;
gateEditGroupIndex.value = gateEditGroup.value
? groupList.value.filter((v) => v.text == gateEditGroup.value)[0].index
: 0;
}
});
};
//监控场景添加分组
const groupAddShow = ref(false);
const groupAddName = ref("");
// 监控场景添加分组弹框显示
const groupAdd = () => {
groupAddName.value = "";
groupAddShow.value = true;
};
// 监控场景添加分组弹框关闭
const groupAddHide = () => {
groupAddShow.value = false;
};
// 监控场景添加分组保存
const groupAddSave = () => {
// console.log('保存')
// 更新分组名称
gateEditGroup.value = groupAddName.value;
groupAddShow.value = false;
};
// 获取检查项列表
const pointPopupShow = ref(false);
const pointList = ref([]);
const pointListIndex = ref(0);
const reqPatrolGateList = () => {
tourApi
.getPatrolGateList({
pageNum: 1,
pageSize: 99999,
mallId: paramObj.mallId,
})
.then((res) => {
// console.log(res)
if (res.data.code == 200) {
let list =
res.data.data && res.data.data.list ? res.data.data.list : [];
for (let i = 0; i < list.length; i++) {
list[i].text = list[i].name;
list[i].value = list[i].id;
list[i].index = i;
}
pointList.value = list;
pointListIndex.value = pointList.value.filter(
(v) => v.text == paramObj.name
)[0].index;
// console.log(list)
// console.log(pointList.value)
}
});
};
const pointConfirm = (row) => {
console.log(row);
paramObj.id = row.id;
paramObj.name = row.name;
gateEditName.value = paramObj.name;
paramObj.bookmark = row.bookmark;
paramObj.mallOrgName = row.mallOrgName;
paramObj.gateUnid = row.unid;
document.title = paramObj.name;
pointListIndex.value = pointList.value.filter(
(v) => v.text == paramObj.name
)[0].index;
pointPopupShow.value = false;
vionPlayer.value.playWebVideo(paramObj);
};
const navBackTour = () => {
wx.miniProgram.navigateBack();
};
const bookmark = ref(paramObj.bookmark);
const collectGate = () => {
tourApi
.setBookmark({
userId: paramObj.userId,
patrolGateIds: [paramObj.id],
})
.then((res) => {
if (res.data.code == 200) {
bookmark.value = true;
return Toast.success("收藏成功");
}
});
};
const delCollectGate = () => {
tourApi
.delBookmark({
userId: paramObj.userId,
patrolGateIds: [paramObj.id],
})
.then((res) => {
if (res.data.code == 200) {
bookmark.value = false;
return Toast.success("取消收藏成功");
}
});
};
const show = ref(false);
const showPopup = () => {
show.value = true;
};
const checked = ref([]);
const onConfirm = (value, index) => {
console.log(value, index);
templateNameDefault.value = value;
checkboxRefsShow.value = [];
checkboxRefs.value = [];
checked.value = [];
templateSearch.value = "";
if (index > 0) {
getPatrolTemplateOne(templateList.value[index].id);
} else {
templateGroupList.value = [];
associateData.value.forEach((item) => {
templateGroupList.value.push(item);
});
}
show.value = false;
};
const onCancel = () => {
show.value = false;
};
const checkboxRefs = ref([]);
const checkboxRefsShow = ref([]);
const templateSearch = ref("");
const templateClick = (val) => {
templateDialogShow.value = true;
};
const templatecollapseNames = ref([]);
const templateSearchChange = (val) => {
console.log(val);
templateSearch.value = val;
let allList = templateItemList.value;
let groupList = {};
allList.forEach((item, index) => {
item.allIndex = index;
if (val == "" || (val != "" && item.name.indexOf(val) != -1)) {
if (groupList[item.sopTypeId]) {
groupList[item.sopTypeId].sopProjects.push(item);
} else {
groupList[item.sopTypeId] = {
id: item.sopTypeId,
name: item.sopTypeName,
sopProjects: [item],
};
}
}
});
templateGroupList.value = [];
templatecollapseNames.value = [];
for (let i in groupList) {
templateGroupList.value.push(groupList[i]);
templatecollapseNames.value.push(groupList[i].id);
}
};
const toggle = (index) => {
checkboxRefs.value[index].toggle();
};
const templateSubmit = (index) => {
templateDialogShow.value = false;
if (checked.value && checked.value.length > 0) {
checkboxRefsShow.value = [];
checked.value.forEach((item) => {
let sopItem = templateItemList.value.find(
(childItem) => item == childItem.id
);
checkboxRefsShow.value.push({
id: sopItem.id,
name: sopItem.name,
});
});
}
};
const deleteTemplate = (item, index) => {
checkboxRefsShow.value.splice(index, 1);
checked.value.splice(index, 1);
};
const message = ref();
const isRectify = ref("1");
const showRectification = ref(false);
const rectificationUserName = ref("");
const onUserConfirm = (value) => {
showRectification.value = false;
rectificationUserName.value = value;
};
const goBackVideo = () => {
message.value = "";
rectificationUserName.value = "";
checked.value = [];
checkboxRefsShow.value = [];
checkboxRefs.value = [];
picUrl.value = "";
setTimeout(() => {
vionPlayer.value.playWebVideo(paramObj);
}, 200);
};
const submitContent = () => {
let base64Str = dataURLtoFile(
captureCanvas.value.toDataURL("image/jpeg"),
new Date().getTime(),
"image/jpeg"
);
let formData = new FormData();
formData.append("accountId", paramObj.accountId);
formData.append("mallId", paramObj.mallId);
formData.append("gateId", paramObj.id);
formData.append("patrolType", 1);
let sops = [];
if (checked.value && checked.value.length > 0) {
checked.value.forEach((item) => {
let sopItem = templateItemList.value.find(
(childItem) => item == childItem.id
);
sops.push({
id: sopItem.id,
status: 0,
score: sopItem.score,
});
});
formData.append("sops", JSON.stringify(sops));
} else {
return Toast.fail("请选择检查项");
}
if (message.value) {
formData.append("remark", message.value);
}
if (isRectify.value == 1) {
if (rectificationUserName.value) {
let user = userList.value.find(
(item) => item.name == rectificationUserName.value
);
formData.append("handler", user.id);
} else {
return Toast.fail("请选择整改人");
}
} else {
formData.append("handler", "");
}
formData.append("pic", base64Str);
tourApi
.confirmPatrolRecord(formData, {
headers: {
"Content-type": "application/x-www-form-urlencoded;charset=utf-8", //multipart/form-data; charset=utf-8
},
})
.then((res) => {
let result = res.data;
if (result.code == "200") {
Toast.success("提交成功");
setTimeout(() => {
message.value = "";
rectificationUserName.value = "";
checked.value = [];
checkboxRefsShow.value = [];
checkboxRefs.value = [];
let context = captureCanvas.value.getContext("2d");
context.clearRect(0, 0, window.innerWidth, window.innerWidth / 1.78);
picUrl.value = "";
}, 1000);
} else {
return Toast.fail(result.msg);
}
});
};
const userList = ref([]);
const userNameList = ref([]);
const getUserList = () => {
userList.value = [];
userNameList.value = [];
tourApi
.getUsers({
mallId: paramObj.mallId,
})
.then((data) => {
let result = data.data;
if (result.data && result.data.length) {
result.data.forEach((item) => {
item.name = item.realName + "(" + item.loginName + ")";
userNameList.value.push(item.realName + "(" + item.loginName + ")");
});
}
userList.value = result.data;
});
};
const vionPlayer = ref();
const picUrl = ref("");
const captureCanvas = ref("");
// 获取检查项
const templateList = ref([]);
const templateNameList = ref([]);
const templateNameDefault = ref();
const tempalteId = ref();
// 单个模板检查项
const templateItemList = ref([]);
// 单个模板检查项--带分组
const templateGroupList = ref([]);
setTimeout(() => {
vionPlayer.value.playWebVideo(paramObj);
}, 1000);
// 检查项弹框显示
const templateDialogShow = ref(false);
const getPatrolTemplateList = () => {
tourApi
.getPatrolTemplateListFun({
accountId: paramObj.accountId,
pageSize: 999,
})
.then((res) => {
let result = res.data;
if (result.code == 200) {
templateList.value = result.data.list;
if (templateList.value.length) {
tempalteId.value = templateList.value[0].id;
getPatrolTemplateOne(templateList.value[0].id);
// templateList.value.unshift({
// name: '全部',
// id: ''
// })
templateNameList.value = [];
templateList.value.forEach((item) => {
templateNameList.value.push(item.name);
});
templateNameDefault.value = templateList.value[0].name;
}
}
});
};
// 获取单个模板检查项
const getPatrolTemplateOne = (val) => {
tourApi
.getPatrolTemplateOne({
id: val,
})
.then((res) => {
let result = res.data;
if (result.code == 200) {
templateItemList.value = result.data.sopProjects || [];
let sopProjects = result.data.sopProjects;
let groupList = {};
sopProjects.forEach((item, index) => {
item.allIndex = index;
if (groupList[item.sopTypeId]) {
groupList[item.sopTypeId].sopProjects.push(item);
} else {
groupList[item.sopTypeId] = {
id: item.sopTypeId,
name: item.sopTypeName,
sopProjects: [item],
};
}
});
templateGroupList.value = [];
templatecollapseNames.value = [];
for (let i in groupList) {
templateGroupList.value.push(groupList[i]);
templatecollapseNames.value.push(groupList[i].id);
}
}
});
};
// 获取关联的SOP
const associateData = ref([]);
const getAssociateList = (record) => {
associateData.value = [];
tourApi
.getPatrolSopTypeTree({
accountId: paramObj.accountId,
system: 0,
})
.then((data) => {
let result = data.data;
if (result.code == "200") {
let tableData = result.data;
tableData.sort((a, b) => {
return a.id - b.id;
});
console.log("tableData", tableData);
tableData.forEach((item, index) => {
if (item.sopProjects.length > 0) {
item.key = 1000 + item.id;
let sopProjects = JSON.parse(JSON.stringify(item.sopProjects));
item.sops = sopProjects;
item.len = sopProjects.length;
item.choiceNum = 0;
associateData.value.push(item);
}
});
}
});
};
const locHistory = ref([]);
const lastLoc = ref();
const isMouseDown = ref(false);
const windowToCanvas = function (x, y) {
var bbox = captureCanvas.value.getBoundingClientRect(); //获取canvas的位置信息
return {
x: Math.round(x - bbox.left),
y: Math.round(y - bbox.top),
}; //返回当前鼠标相对于canvas的位置
};
const capture = function () {
// 绘制
var context = captureCanvas.value.getContext("2d");
captureCanvas.value.addEventListener("touchstart", function (e) {
e.preventDefault();
isMouseDown.value = true;
lastLoc.value = windowToCanvas(e.touches[0].clientX, e.touches[0].clientY);
locHistory.value.push({
x: lastLoc.value.x,
y: lastLoc.value.y,
});
console.log(locHistory.value);
});
captureCanvas.value.addEventListener("touchmove", function (e) {
e.preventDefault();
if (isMouseDown.value) {
moveStroke(
{
x: e.touches[0].clientX,
y: e.touches[0].clientY,
},
context
);
}
});
captureCanvas.value.addEventListener("touchend", function (e) {
locHistory.value.push({
x: lastLoc.value.x,
y: lastLoc.value.y,
});
isMouseDown.value = false;
});
};
const calcDistance = function (loc1, loc2) {
return Math.sqrt(
(loc1.x - loc2.x) * (loc1.x - loc2.x) +
(loc1.y - loc2.y) * (loc1.y - loc2.y)
); //通过起始结束坐标x,y值计算路程长度
};
const moveStroke = function (point, context) {
var curLoc = windowToCanvas(point.x, point.y);
var curTimestamp = new Date().getTime();
var s = calcDistance(curLoc, lastLoc.value);
var lineWidth = 3;
//draw
context.beginPath();
context.moveTo(lastLoc.value.x, lastLoc.value.y);
context.lineTo(curLoc.x, curLoc.y);
locHistory.value.push({
x: curLoc.x,
y: curLoc.y,
});
context.lineWidth = 3;
context.strokeStyle = "#FF0000";
context.lineCap = "round";
context.linJoin = "round";
context.stroke();
//每次过程结束时,将结束值赋给初始值,一直延续
lastLoc.value = curLoc;
};
// 前端截图
const goCaptureImg = () => {
getPatrolTemplateList();
getAssociateList();
getUserList();
Toast.loading({
duration: 0,
message: "视频截图中···",
forbidClick: true,
});
vionPlayer.value
.screenshot()
.then((data) => {
screenshotEnd(data);
})
.catch((err) => {});
};
// 截图出口
const screenshotEnd = (imgData) => {
console.log(imgData);
let reg = new RegExp("data:image/jpeg;base64,");
let regPng = new RegExp("data:image/png;base64,");
let pic = imgData.replace(reg, "");
pic = pic.replace(regPng, "");
tourApi
.uploadScreenshot({
gateId: paramObj.id,
pic: pic,
})
.then((res) => {
// console.log(res)
let result = res.data;
if (result.code == 200) {
Toast.success("截图成功");
pictureProcess(result.data.pic);
} else {
Toast.success(result.msg);
}
});
};
const pictureProcess = (imgUrl) => {
picUrl.value = imgUrl;
Toast.clear();
if (paramObj.type != "titem") {
// picUrl.value = 'https://store.keliuyun.com/images/patrol/capture/20230825/ecd2a635-84af-4968-bc30-1af130460d12.jpg'
vionPlayer.value.stopPlay();
let image = new Image();
image.src = "https://store.keliuyun.com/images/" + picUrl.value;
image.style.width = window.innerWidth + "px";
let heightC = window.innerWidth / 1.78;
image.style.height = heightC + "px";
image.crossOrigin = "Anonymous";
image.onload = () => {
captureCanvas.value.width = window.innerWidth;
captureCanvas.value.height = heightC;
let ctx = captureCanvas.value.getContext("2d");
ctx.drawImage(image, 0, 0, window.innerWidth, heightC);
capture();
};
} else {
/***********巡店详情截图****************/
if (paramObj.newLevel) {
wx.miniProgram.redirectTo({
url: `/pages/tour/inspectionDetail/index?action=capture&picUrl=${imgUrl}&id=${paramObj.tid}`,
});
} else {
wx.miniProgram.redirectTo({
url: `/pages/tour/titemDetail/index?action=capture&picUrl=${picUrl}&id=${paramObj.tid}`,
});
}
}
};
onMounted(() => {
reqPatrolGateList();
reqGateGroupList();
});
</script>
<style scoped lang="less">
.tour-btns {
display: flex;
justify-content: space-between;
padding: 20px 2vw 0;
--van-button-border-radius: 60px;
:deep(.van-button) {
margin-bottom: 20px;
border-radius: 60px;
width: 47vw;
&.van-button--block {
width: 100%;
}
}
}
.tour-btns1 {
display: flex;
background: #e6edff;
height: 15vw;
line-height: 15vw;
padding: 0 20px;
.box {
display: inline-block;
flex: 1;
}
.box1 {
text-align: right;
span {
margin-left: 20px;
}
}
.icon {
transform: rotate(90deg);
}
}
.iconMargin {
margin-right: 10px;
}
.itemList {
overflow: auto;
height: 50%;
}
:deep(.van-cell-group--inset) {
margin: 0;
}
.template-select-box {
background: #fff;
}
.template-select-list {
padding: 30px 0px 10px;
margin: 0 30px;
border-bottom: 1px solid var(--van-cell-border-color);
.template-select-one {
margin: 0 20px 20px 0;
padding: 1.8vw;
/deep/.van-tag__close {
margin-left: 10px;
}
}
}
.template-name-box {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 0 30px;
border-bottom: 1px solid var(--van-cell-border-color);
}
.template-name-content {
display: flex;
align-items: center;
}
.template-name-title {
width: 5em;
min-width: 5em;
font-size: var(--van-cell-font-size);
color: var(--van-field-label-color);
}
.template-btn {
width: 6em;
}
.template-name-content /deep/.van-cell {
padding-left: 0px;
}
.template-list-model {
.template-list-box {
width: calc(100vw - 0px);
height: calc(80vh - 0px);
padding: 30px;
background: #fff;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
.template-list-title {
font-size: var(--van-font-size-lg);
line-height: var(--van-font-size-lg);
margin-bottom: 10px;
}
.template-search-box {
// border-top: 1px solid #cccccc;
border-bottom: 1px solid #cccccc;
margin-bottom: 30px;
}
.template-list {
height: 54vh;
overflow: auto;
}
.template-list-submit {
margin-top: 30px;
}
}
}
.gate-name-box {
display: inline-flex !important;
align-items: center;
}
.gate-name-edit-img {
width: 40px;
height: 40px;
display: inline-block;
margin-left: 20px;
}
.group-remark {
font-size: var(--van-calendar-info-font-size);
line-height: 34px;
color: #bbb;
margin-top: 20px;
}
.grop-name-required {
position: relative;
top: 4px;
font-size: 12px;
margin-left: 8px;
color: var(--van-field-required-mark-color);
content: "*";
}
.text-ellipsis {
display: inline-block;
max-width: 30vw;
margin-right: 10px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
</style>
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import pxtovw from 'postcss-px-to-viewport'
import styleImport, { VantResolve } from 'vite-plugin-style-import';
const loder_pxtovw=pxtovw({
unitToConvert: "px", // 需要转换的单位,默认为"px"
viewportWidth: 750, // 设计稿的视口宽度
unitPrecision: 2, // 单位转换后保留的精度
propList: ["*"], // 能转化为vw的属性列表
viewportUnit: "vw", // 希望使用的视口单位
fontViewportUnit: "vw", // 字体使用的视口单位
selectorBlackList: ['ignore'], // 需要忽略的CSS选择器
exclude:[/node_module/],
})
import path from 'path';
import copy from 'rollup-plugin-copy'
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import pxtovw from "postcss-px-to-viewport";
import styleImport, { VantResolve } from "vite-plugin-style-import";
const loder_pxtovw = pxtovw({
unitToConvert: "px", // 需要转换的单位,默认为"px"
viewportWidth: 750, // 设计稿的视口宽度
unitPrecision: 2, // 单位转换后保留的精度
propList: ["*"], // 能转化为vw的属性列表
viewportUnit: "vw", // 希望使用的视口单位
fontViewportUnit: "vw", // 字体使用的视口单位
selectorBlackList: ["ignore"], // 需要忽略的CSS选择器
exclude: [/node_module/],
});
import path from "path";
import copy from "rollup-plugin-copy";
export default defineConfig({
base:'./',
publicDir:'public',
devServer: {
port: 9091,
proxy: {
'/nvsthird/ptzcontrol': {
target: 'http://52.130.155.147:8888',
changeOrigin: true
}
}
},
base: "./",
publicDir: "public",
server: {
host: true,
},
devServer: {
port: 9091,
proxy: {
"/nvsthird/ptzcontrol": {
target: "http://52.130.155.147:8888",
changeOrigin: true,
},
},
},
plugins: [
vue(),
vueJsx(),
styleImport({
libs: [
{
libraryName: "vant",
esModule: true,
resolveStyle: (name) => `vant/es/${name}/style`,
},
],
}),
vue(),
vueJsx(),
styleImport({
libs: [
{
libraryName: "vant",
esModule: true,
resolveStyle: (name) => `vant/es/${name}/style`,
},
],
}),
],
css: {
postcss:{
plugins: [loder_pxtovw]
}
postcss: {
plugins: [loder_pxtovw],
},
},
resolve:{
alias:{
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@s": path.resolve(__dirname, "./src/static")
}
"@s": path.resolve(__dirname, "./src/static"),
},
},
build: {
assetsDir: "static",
// rollupOptions:{
// plugins:[copy({
// targets: [
// { src: 'public/*', dest: 'dist/static' }
// ]
// })]
// }
},
build:{
assetsDir:'static',
// rollupOptions:{
// plugins:[copy({
// targets: [
// { src: 'public/*', dest: 'dist/static' }
// ]
// })]
// }
}
})
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!