From 5672f352d0ba114e2ae96c8cefad6c74ae6d2934 Mon Sep 17 00:00:00 2001 From: unknown <280848880@qq.com> Date: 星期一, 23 十月 2023 10:38:28 +0800 Subject: [PATCH] 苏州-web:智能安全帽提交 --- web/src/views/SecureManage/SmartHelmet/TrackBack.vue | 741 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 738 insertions(+), 3 deletions(-) diff --git a/web/src/views/SecureManage/SmartHelmet/TrackBack.vue b/web/src/views/SecureManage/SmartHelmet/TrackBack.vue index 1939462..a943fa9 100644 --- a/web/src/views/SecureManage/SmartHelmet/TrackBack.vue +++ b/web/src/views/SecureManage/SmartHelmet/TrackBack.vue @@ -1,3 +1,738 @@ -<template> - <div>轨迹回放</div> -</template> \ No newline at end of file +<template> + <!-- 安全管理 ==> 智能安全帽 => 轨迹回放 --> + <div class="main"> + <!-- table --> + <div class="main_left"> + <div class="main_left_search"> + <div class="search_item"> + <span>姓名:</span> + <el-input size="mini" v-model="queryInfo.userName" placeholder="请输入姓名" clearable></el-input> + </div> + <div class="search_item"> + <span>设备编号:</span> + <el-input size="mini" v-model="queryInfo.deviceNum" placeholder="请输入设备编号" clearable></el-input> + <el-button icon="el-icon-search" @click="queryTable">查询</el-button> + </div> + </div> + <div class="main_left_table"> + <cpnTable :table-loading="loading" :table-data="dataList" :table-columns="tableColumns" + :row-click="rowClick"> + </cpnTable> + </div> + </div> + <!-- chart --> + <div class="main_right_echart" v-show="!isShowMap"> + <div class="echart_options"> + <span>日期:</span> + <el-date-picker class="elDatePicker" type="daterange" v-model="chart.date" value-format="yyyy-MM-dd" + start-placeholder="开始日期" end-placeholder="结束日期" clearable></el-date-picker> + <el-button icon="el-icon-search" @click="queryEchart">查询</el-button> + <el-button style="float:right" @click="watchMap">查看</el-button> + </div> + <div ref="echart_bar" class="echart_bar"></div> + <div ref="echart_drop" class="echart_drop"></div> + </div> + <!-- map --> + <div class="main_right_map" v-show="isShowMap"> + <div class="map_option"> + <span>日期:</span> + <el-date-picker class="elDatePicker" type="daterange" v-model="chart.date" value-format="yyyy-MM-dd" + start-placeholder="开始日期" end-placeholder="结束日期" clearable></el-date-picker> + <el-button icon="el-icon-search" @click="queryEchart">查询</el-button> + <el-button style="float:right" @click="hideMap">返回</el-button> + </div> + <div class="map_view"> + <div class="map_view_main"> + <div id="container"></div> + <div id="trackDetail" v-show="isShowTrackDetail"> + <h2>位置信息统计</h2> + <div class="ul_box"> + <ul> + <li :class="{ active: index === maps.trackListIndex }" + v-for="(val, index) in maps.trackLists " :key="index" @click="trackListClick(index)"> + {{ val }} + </li> + </ul> + </div> + </div> + </div> + <div class="map_view_btn"> + <el-button @click="startAnimation">开始动画</el-button> + <el-button @click="pauseAnimation">停止动画</el-button> + <el-button @click="resumeAnimation">继续动画</el-button> + <el-button @click="stopAnimation">停止动画</el-button> + <el-button @click="showTrackDetail">轨迹详情</el-button> + </div> + </div> + </div> + </div> +</template> + +<script> +import AMapLoader from '@amap/amap-jsapi-loader'; +import cpnTable from '@/components/element/Table' +import icon from '@/assets/markerIcon.png' +import 'echarts-liquidfill' // 水滴图形 +export default { + data() { + return { + loading: false, + isShowMap: false, // 是否查看轨迹地图 + isShowTrackDetail: false, // 是否渲染轨迹详情 + queryInfo: { + pageNum: 1, + pageSize: 9999, + userName: '', + deviceNum: '', + }, + dataList: [], + tableColumns: [], + userId: '', // 用户id + // 图表数据 + chart: { + date: [new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).format(), new Date().format()], // 轨迹图表日期.默认近一个月 + totaltime: '', // 安全帽在线总时长 + }, + maps: { // 地图数据 + date: '', + trackLists: [], // 轨迹详情 + trackListIndex: '', + }, + } + }, + components: { + cpnTable + }, + created() { + // 添加密钥,获取高德地址使用 + window._AMapSecurityConfig = { + securityJsCode: "aa90b00e27025d1f57286675cc8b232e", + } + // 存储全局变量.(不响应式) + this.maps.times = [] + this.maps.lineArr = [] + this.maps.AMap = null + this.maps.map = null + this.maps.mapMarker = null + this.chart.chartInstanceBar = null + this.chart.chartInstanceDrop = null + this.$http = this.$api.Safety.SmartHelmet.trackBack // 接口api路径 + + this.setTableColumn() + this.getLists() + }, + mounted() { + window.addEventListener("resize", this.screenAdapter) + }, + beforeDestroy() { + this.clearMap() + this.clearChart() + window.removeEventListener("resize", this.screenAdapter) + }, + methods: { + setTableColumn() { + this.tableColumns = [ + {selection: true}, + {name: "用户ID", key: "userId"}, + {name: "姓名", key: "userName"}, + {name: "设备编号", key: "deviceNum"}, + ] + }, + getLists() { + this.loading = true + this.$api.Safety.SmartHelmet.getLists(this.queryInfo).then(res => { + if (res.statusMsg === 'ok') { + this.dataList = res.data.list + } + this.loading = false + }) + }, + // 获取用户在线时长 + async getOnlineTime() { + let out = {} + const params = { + userId: this.userId, + // strTime: this.chart.date?.[0], + // endTime: this.chart.date?.[1], + strTime: '2023-06-01', + endTime: '2023-06-31', + } + const {data, statusMsg} = await this.$http.getOnlineTime(params) + if (statusMsg === 'ok') { + out = { + list: data.tHelmetTrajectory, + total: data.total, + } + } + return out + }, + // 获取用户轨迹 + async getTrackLists() { + let out = { + positions: [], // 经纬度 + times: [], // 时间 + } + const params = { + userId: this.userId, + // strTime: this.chart.date?.[0], + // endTime: this.chart.date?.[1], + strTime: '2023-06-01', + endTime: '2023-06-31', + } + const {data, statusMsg} = await this.$http.getTrackLists(params) + if (statusMsg === 'ok') { + data.forEach(item => { + out.positions.push([+item.ypoint, +item.xpoint]) + out.times.push(item.smTime) + }) + } + return out + }, + // 初始化图表 + initChart() { + this.chart.chartInstanceBar = this.$echarts.init(this.$refs.echart_bar) + this.chart.chartInstanceDrop = this.$echarts.init(this.$refs.echart_drop) + this.renderBaseChart() + }, + // 渲染chart基础配置 + renderBaseChart() { + const baseBarOptions = { + animationDuration: 1500, + tooltip: { + trigger: "axis", + }, + legend: { + itemWidth: 35, + itemHeight: 12, + x: 'right', + y: 'top', + textStyle: { + color: "#fff", + }, + }, + grid: { + top: "10%", + right: "0", + left: "2%", + bottom: "14", + containLabel: true, + }, + xAxis: [{ + type: 'category', + axisLine: { + lineStyle: { + width: 2, + color: "#B7E4F7", + }, + }, + axisTick: { + show: false, + }, + }], + yAxis: [{ + type: 'value', + axisLabel: { + formatter: "{value}", + color: "#CAD3E0", + }, + splitLine: { + lineStyle: { + width: 2, + type: "dashed", + color: "#5A7E9D", + }, + }, + }], + series: [{ + type: 'bar', + name: "在线时长", + barWidth: 14, + color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [ + {offset: 0, color: "#C15B3C"}, + {offset: 1, color: "#FACD91"}, + ]), + }], + } + const baseDropOptions = { + series: [{ + type: 'liquidFill', + data: [0], + radius: '70%', //图表的大小 值是圆的直径 + shape: 'circle', //水填充图的形状 circle默认圆形 rect圆角矩形 triangle三角形 diamond菱形 pin水滴状 arrow箭头状 还可以是svg的path + center: ['50%', '50%'], //图表相对于盒子的位置 + outline: { + show: false + }, + backgroundStyle: { // 背景配置 + color: 'transparent', //背景颜色 + borderWidth: '2',//边框大小 + borderColor: 'rgba(17, 94, 176, 0.4)', // 边框颜色 + }, + itemStyle: { + color: new this.$echarts.graphic.LinearGradient( + 0, 0, 0, 1, //4个参数用于配置渐变色的起止位置, 这4个参数依次对应右/下/左/上四个方位. 而0 0 0 1则代表渐变色从正上方开始 + [ + {offset: 0, color: '#2790F4'}, + {offset: 1, color: '#165190'} + ] //数组, 用于配置颜色的渐变过程. 每一项为一个对象, 包含offset和color两个参数. offset的范围是0 ~ 1, 用于表示位置 + ) + }, + }] + } + this.chart.chartInstanceBar.setOption(baseBarOptions) + this.chart.chartInstanceDrop.setOption(baseDropOptions) + }, + // 渲染chart内容数据(x,y,series) + async renderChart() { + const {list, total} = await this.getOnlineTime() + const x = [] // x轴数据 + const y = [] // y轴数据 + list.forEach(item => { + x.push(item.ctime) + y.push(item.ltime) + }) + const baseBarOptions = { + xAxis: { + index: 0, + data: x + }, + series: { + index: 0, + data: y + } + } + const baseDropOptions = { + series: { + index: 0, + data: [0.3], + label: { + color: '#fff', + fontSize: 28, + formatter: () => this.handleTime(total), + } + } + } + this.chart.chartInstanceBar.setOption(baseBarOptions) + this.chart.chartInstanceDrop.setOption(baseDropOptions) + }, + // 处理秒=>天时分 + handleTime(num) { + let second = num * 60 + let day = 0 + let hour = 0 + let minutes = 0 + if (second < 86400) { + day = 0; + if (second < 3600) { + hour = 0; + if (second < 60) { + minutes = 0; + } else { + minutes = Math.floor(second / 60); + } + } else { + hour = Math.floor(second / 3600); + if (second - hour * 3600 < 60) { + minutes = 0; + } else { + minutes = Math.floor((second - hour * 3600) / 60); + } + } + } else { + day = Math.floor(second / 86400); + if (second - 86400 * day < 3600) { + hour = 0; + minutes = Math.floor((second - 86400 * day) / 60); + } else { + hour = Math.floor((second - 86400 * day) / 3600); + if (second - 86400 * day - 3600 * hour < 60) { + minutes = 0; + } else { + minutes = Math.floor((second - 86400 * day - 3600 * hour) / 60); + } + } + } + day = day ? day + "天" : '' + hour = hour ? hour + "小时" : '' + minutes = minutes && minutes + "分" + return day + hour + minutes + }, + rowClick(row) { + this.userId = row.userId + if (!this.chart.chartInstanceBar) { + this.initChart() + } + this.hideMap() + this.renderChart() + }, + queryTable() { + this.getLists() + }, + queryEchart() { + this.renderChart() + }, + queryMap() { + + }, + // chart 查看按钮 + watchMap() { + if (!this.userId) { + this.$message.warning('请先选择用户'); + return + } + this.renderMap() + }, + async renderMap() { + this.isShowMap = !this.isShowMap + let {positions, times} = await this.getTrackLists() + this.maps.times = times + this.maps.lineArr = positions + AMapLoader.load({ + key: "1dc3895771d98745fa27bbcc3280464f", // 申请好的Web端开发者Key,首次调用 load 时必填 + version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15 + }).then(AMap => { + this.maps.AMap = AMap + AMap.plugin('AMap.MoveAnimation', () => { + // 创建地图 + this.maps.map = new AMap.Map("container", { + resizeEnable: true, + viewMode: "2D", // 是否为3D地图模式 + zoom: 17, // 初始化地图级别 + }) + if (positions.length) { + // 创建一个 (小人) Icon + var personIcon = new AMap.Icon({ + image: icon, // 图标的取图地址 + imageSize: new AMap.Size(40, 40), // 图标所用图片大小 + }) + // 创建marker实例 + let mapMarker = this.maps.mapMarker = new AMap.Marker({ + map: this.maps.map, + position: positions[0], + icon: personIcon, + zIndex: 10, + offset: new AMap.Pixel(-20, -36), + }) + // marker提示框 + let infoWindow = new AMap.InfoWindow({offset: new AMap.Pixel(0, -30)}); + let markerMouseover = async (e) => { + let index = e.target.content + let adress = await this.getAdress(positions[index]) + let time = times[index] + let el = ` + <p><t>地址:</t><span>${adress}</span></p> + <p><t>时间:</t><span>${time}</span></p> + <p><t>经纬度:</t><span>${positions[index][0]}, ${positions[index][1]}</span></p> + ` + infoWindow.setContent(el) + infoWindow.open(this.maps.map, e.target.getPosition()) + } + // 给折线图添加拐点和绑定事件 + for (var i = 0; i < positions.length; i += 1) { + let center = positions[i] + let icon = new AMap.Icon({ + size: new AMap.Size(6, 11), // 图标尺寸 + image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', // Icon的图像 + imageSize: new AMap.Size(6, 11) + }) + let marker = new AMap.Marker({ + map: this.maps.map, + position: center, + icon: icon, + offset: new AMap.Pixel(-3, -10) + }) + marker.content = i + marker.on("mouseover", markerMouseover) + } + // 绘制轨迹 + new AMap.Polyline({ + map: this.maps.map, + path: positions, + // showDir: true, + strokeColor: "#28F", //线颜色 + strokeWeight: 3, //线宽 + }) + // 已通过轨迹 + let passedPolyline = new AMap.Polyline({ + map: this.maps.map, + strokeColor: "red", //线颜色 + strokeWeight: 3, //线宽 + }) + mapMarker.on('moving', (e) => { + passedPolyline.setPath(e.passedPath); + this.maps.map.setCenter(e.target.getPosition(), true) + }) + this.maps.map.setFitView() + } + }) + }) + }, + // 获取地址 + getAdress(lnglats) { + return new Promise(resolve => { + const AMap = this.maps.AMap + AMap.plugin('AMap.Geocoder', () => { + const geocoder = new AMap.Geocoder({ + radius: 1000, + extensions: "all" + }) + geocoder.getAddress(lnglats, (status, result) => { + let address = '' + if (status === 'complete' && result.info === 'OK') { + address = result.regeocodes ? result.regeocodes : result.regeocode.formattedAddress + } + resolve(address) + }) + }) + }) + }, + hideMap() { + this.clearMap() + this.isShowMap = false + this.maps.trackLists = [] + this.maps.trackListIndex = '' + }, + startAnimation() { + this.maps.mapMarker?.moveAlong(this.maps.lineArr, { + // 每一段的时长 + duration: 500,//可根据实际采集时间间隔设置 + // JSAPI2.0 是否延道路自动设置角度在 moveAlong 里设置 + autoRotation: true, + }) + }, + pauseAnimation() { + this.maps.mapMarker?.pauseMove() + }, + resumeAnimation() { + this.maps.mapMarker?.resumeMove() + }, + stopAnimation() { + this.maps.mapMarker?.stopMove() + }, + // 显示轨迹详情 + async showTrackDetail() { + this.isShowTrackDetail = !this.isShowTrackDetail + if (this.maps.trackLists.length) { + return + } + if (this.maps.times.length && this.maps.lineArr.length) { + let tracks = [] + let adress = await this.getAdress([...this.maps.lineArr]) + adress.forEach((item, index) => { + tracks.push(`位置: ${this.maps.times[index]}(${item.formattedAddress})`) + }) + this.maps.trackLists = tracks + } + }, + trackListClick(index) { + this.maps.trackListIndex = index + }, + screenAdapter() { + this.chart.chartInstanceBar?.resize() + this.chart.chartInstanceDrop?.resize() + }, + clearMap() { + this.maps.mapMarker?.stopMove() + this.maps.map?.destroy() + }, + clearChart() { + this.chart.chartInstanceBar?.clear() + this.chart.chartInstanceDrop?.clear() + }, + } +} +</script> + +<style lang="scss" scoped> +@import '@/style/layout-main.scss'; + +::v-deep .amap-info-content { + flex-direction: row; + width: 220px; + padding: 14px; + line-height: 20px; + border-radius: 5px; + background: #0D1846; + + p { + display: flex; + width: 100%; + } + + t { + width: 40px; + } + + span { + flex: 1; + color: #39B5FE; + } +} + +::v-deep .bottom-center .amap-info-sharp { + border-top: 8px solid #0D1846; +} + +.active { + color: #39B5FE; +} + +#container { + min-height: 400px; + flex: 1.6; +} + +#trackDetail { + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + + h2 { + text-indent: 20px; + } + + .ul_box { + overflow-y: auto; + flex: 1; + + ul { + margin: 0; + padding: 0 16px; + } + + li { + padding: 10px 0; + font-size: 15px; + line-height: 24px; + list-style: none; + border-bottom: 1px dashed #fff; + cursor: pointer; + } + } +} + +.main { + display: flex; + flex-direction: row; + color: #EBEBEB; + + .main_left { + display: flex; + flex-direction: column; + width: 380px; + padding: 14px; + margin-right: 18px; + border: 1px solid #39B5FE; + border-radius: 10px; + box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset; + box-sizing: content-box; + + .search_item { + span { + display: inline-block; + min-width: 54px; + margin-right: 6px; + text-align: right; + } + + .el-input { + width: 220px; + margin: 0 28px 16px 0; + } + } + + ::v-deep.main_left_table { + overflow: hidden; + + .el-table__row { + cursor: pointer + } + + .el-table { + display: flex; + flex-direction: column; + + .el-table__body-wrapper { + overflow-y: auto; + flex: 1; + } + } + } + } + + .main_right_echart { + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + padding: 20px; + border: 1px solid #39B5FE; + background-color: rgba(0, 52, 121, 0.3); + border-radius: 10px; + box-shadow: rgba(0, 145, 255, 0.7) 0 0 18px inset; + box-sizing: content-box; + + .echart_options { + span { + display: inline-block; + margin-right: 6px; + text-align: right; + } + + .elDatePicker { + width: 260px; + margin: 0 10px 14px 0; + } + } + + .echart_bar { + flex: 1; + } + + .echart_drop { + flex: 1.1; + } + } + + .main_right_map { + display: flex; + flex-direction: column; + position: absolute; + right: 0; + left: 430px; + top: 0; + bottom: 0; + padding: 20px; + + .map_option { + span { + display: inline-block; + margin-right: 6px; + } + + .elDatePicker { + width: 260px; + margin: 0 10px 14px 0; + } + } + + .map_view { + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + + .map_view_main { + overflow: hidden; + display: flex; + flex: 1; + } + + .map_view_btn { + margin-top: 10px; + } + } + } +} +</style> \ No newline at end of file -- Gitblit v1.9.3