整体代码完善
This commit is contained in:
parent
c51c2437fa
commit
260dc6b942
|
|
@ -2,29 +2,50 @@
|
|||
* 缓存对象管理
|
||||
* 防止对象过多
|
||||
*/
|
||||
import StorageS from 'storages-js';
|
||||
import StorageS from 'storages-js'
|
||||
|
||||
/**
|
||||
* 所有实例
|
||||
* 公共管理
|
||||
* */
|
||||
const allStorage = {};
|
||||
const allStorage = {}
|
||||
function createS(key, value) {
|
||||
if (!allStorage[key]) {
|
||||
allStorage[key] = new StorageS(key, value, { modelName: 'local' });
|
||||
allStorage[key] = new StorageS(key, value, { modelName: 'local' })
|
||||
}
|
||||
return allStorage[key];
|
||||
return allStorage[key]
|
||||
}
|
||||
|
||||
/** 保存用户的基本信息 */
|
||||
const userStorage = (value) => {
|
||||
return createS('user-container', value);
|
||||
};
|
||||
return createS('user-container', value)
|
||||
}
|
||||
const fullScreenStorage = (value) => {
|
||||
return createS('full-screen-container', value);
|
||||
};
|
||||
return createS('full-screen-container', value)
|
||||
}
|
||||
|
||||
/** 保存公共的 token 信息 */
|
||||
const userTokenStorage = (value) => {
|
||||
return createS('token-container', value)
|
||||
}
|
||||
/** 保存机器人的信息 */
|
||||
const userRobotStorage = (value) => {
|
||||
return createS('robot-container', value)
|
||||
}
|
||||
/** 保存地图信息 */
|
||||
const userMapStorage = (value) => {
|
||||
return createS('map-container', value)
|
||||
}
|
||||
/** 保存机器人实时点位信息 */
|
||||
const userRobotPointsStorage = (value) => {
|
||||
return createS('robot-points-container', value)
|
||||
}
|
||||
|
||||
export default {
|
||||
userStorage,
|
||||
fullScreenStorage,
|
||||
};
|
||||
userTokenStorage,
|
||||
userRobotStorage,
|
||||
userMapStorage,
|
||||
userRobotPointsStorage,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,3 +8,8 @@ export const getTokenApi = (data) => {
|
|||
export const getDeviceInfoApi = (data) => {
|
||||
return service.post('/api/robot/sbdUser/getDeviceList', data)
|
||||
}
|
||||
|
||||
// 操作机器人行动接口
|
||||
export const handleRobotActionApi = (data) => {
|
||||
return service.post('/api/robot/instruct/sedXml', data)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,6 @@ const onHandleCloseModal = () => {
|
|||
|
||||
.modal-content {
|
||||
flex: 1;
|
||||
// height: calc(100% - 40px);
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,17 +2,18 @@
|
|||
<!-- 视频播放器 Flv格式视频 -->
|
||||
<video
|
||||
:id="videoId"
|
||||
class="video-player"
|
||||
autoplay
|
||||
controls
|
||||
muted
|
||||
@timeupdate="progress($event)"
|
||||
style="width: 100%; height: 100%; border-radius: 12px"
|
||||
style="border-radius: 12px"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import flv from 'flv.js'
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { ref, watch, nextTick, onBeforeUnmount } from 'vue'
|
||||
|
||||
let flvPlayerList = []
|
||||
let replayCount = 0
|
||||
|
|
@ -105,11 +106,15 @@ const progress = (e) => {
|
|||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopvideo()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => dfp.cameraNode,
|
||||
() => {
|
||||
errorMsg.value = ''
|
||||
console.log(dfp.cameraNode, 'dfp.cameraNode')
|
||||
|
||||
if (dfp.cameraNode.steamURL) {
|
||||
playUrl.value =
|
||||
dfp.cameraNode.steamURL +
|
||||
|
|
@ -119,9 +124,6 @@ watch(
|
|||
dfp.cameraNode.idx +
|
||||
'&stream=' +
|
||||
dfp.cameraNode.stream
|
||||
|
||||
// console.log(dfp.cameraNode.steamURL, 'playUrl.value')
|
||||
console.log(playUrl.value, 'playUrl.value')
|
||||
playvideo()
|
||||
} else {
|
||||
stopvideo()
|
||||
|
|
@ -133,10 +135,10 @@ watch(
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
#video {
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
import { getTokenApi } from '@/api/home'
|
||||
|
||||
export function useScale(appRef) {}
|
||||
|
|
@ -4,10 +4,13 @@
|
|||
*/
|
||||
import axios from 'axios'
|
||||
import { userDataStore } from '@/store/user'
|
||||
// import { useMessage } from 'naive-ui'
|
||||
import router from '@/router'
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
const { message } = createDiscreteApi(['message'])
|
||||
|
||||
let baseApiURL = import.meta.env.VITE_APP_baseApiURL //api原始链接
|
||||
const timeout = 13000 //api请求超时时间
|
||||
const timeout = 10000 //api请求超时时间
|
||||
|
||||
export const service = axios.create({
|
||||
//可创建多个 axios实例
|
||||
|
|
@ -66,8 +69,9 @@ service.interceptors.response.use(
|
|||
return Promise.reject(data)
|
||||
}
|
||||
},
|
||||
() => {
|
||||
(error) => {
|
||||
//数据请求发生错误
|
||||
message.error('请求发生错误,请稍后再试')
|
||||
return Promise.reject({
|
||||
msg: '请求发生错误,请稍后再试',
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/** 机器人数据 */
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import allStorage from '@/action/storageManage'
|
||||
|
||||
export const useRobotDataStore = defineStore('useRobotDataStore', {
|
||||
state: () => {
|
||||
const userTokenStorage = allStorage.userTokenStorage() // 获取缓存
|
||||
const userStorage = allStorage.userRobotStorage() // 获取缓存
|
||||
const userMapStorage = allStorage.userMapStorage() // 获取缓存
|
||||
const userRobotPointsStorage = allStorage.userRobotPointsStorage() // 获取缓存
|
||||
|
||||
let tokenInfo = userTokenStorage.value
|
||||
if (typeof tokenInfo !== 'object') {
|
||||
tokenInfo = {}
|
||||
}
|
||||
let robotInfo = userStorage.value
|
||||
if (typeof robotInfo !== 'object') {
|
||||
robotInfo = {}
|
||||
}
|
||||
let mapInfo = userMapStorage.value
|
||||
if (typeof mapInfo !== 'object') {
|
||||
mapInfo = {}
|
||||
}
|
||||
let robotPoints = userRobotPointsStorage.value
|
||||
if (typeof robotPoints !== 'object') {
|
||||
robotPoints = {}
|
||||
}
|
||||
|
||||
return {
|
||||
tokenInfo: tokenInfo || {}, // 设备token
|
||||
robotInfo: robotInfo || {}, // 机器人信息
|
||||
mapInfo: mapInfo || {}, // 地图信息
|
||||
robotPoints: {}, // 机器人点位信息
|
||||
}
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
setTokenInfo(value) {
|
||||
this.tokenInfo = value || {}
|
||||
/** 存入缓存 */
|
||||
const tokenStorage = allStorage.userTokenStorage()
|
||||
tokenStorage.value = value
|
||||
},
|
||||
setRobotInfo(value) {
|
||||
this.robotInfo = value || {}
|
||||
/** 存入缓存 */
|
||||
const robotStorage = allStorage.userRobotStorage()
|
||||
robotStorage.value = value
|
||||
},
|
||||
setMapInfo(value) {
|
||||
this.mapInfo = value || {}
|
||||
/** 存入缓存 */
|
||||
const mapStorage = allStorage.userMapStorage()
|
||||
mapStorage.value = value
|
||||
},
|
||||
setRobotPoints(value) {
|
||||
this.robotPoints = value || {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -85,4 +85,16 @@
|
|||
|
||||
.n-form-item .n-form-item-label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.n-base-select-menu .n-base-select-option.n-base-select-option--selected.n-base-select-option--pending::before {
|
||||
background-color: #072148;
|
||||
}
|
||||
|
||||
.n-base-select-menu .n-base-select-option.n-base-select-option--pending::before {
|
||||
background-color: #2f5789;
|
||||
}
|
||||
|
||||
.n-base-selection:not(.n-base-selection--disabled).n-base-selection--active .n-base-selection-label {
|
||||
background-color: #1d3861
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import { initLoginApi } from '@/utils/initLogin'
|
||||
import { getDeviceInfoApi, handleRobotActionApi } from '@/api/home'
|
||||
import { useRobotDataStore } from '@/store/robot'
|
||||
const robotData = useRobotDataStore()
|
||||
|
||||
// 获取机器人token
|
||||
export const getRobotTokenFn = async () => {
|
||||
if (!robotData.tokenInfo.deviceToken) {
|
||||
const loginParams = {
|
||||
address: '112.31.70.193',
|
||||
port: '9988',
|
||||
user: 'bns3',
|
||||
password: 'Bns@admin**',
|
||||
epid: 'system',
|
||||
bfix: 1,
|
||||
}
|
||||
const res = await initLoginApi(loginParams)
|
||||
robotData.setTokenInfo({
|
||||
deviceToken: res.data.token,
|
||||
})
|
||||
return res.data.token
|
||||
} else {
|
||||
return robotData.tokenInfo.deviceToken
|
||||
}
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
export const getRobotDeviceListFn = async () => {
|
||||
if (!robotData.robotInfo.puId) {
|
||||
const { data: res } = await getDeviceInfoApi()
|
||||
if (res?.data?.length > 0) {
|
||||
const deviceInfo = res.data[0]
|
||||
robotData.setRobotInfo({
|
||||
devName: deviceInfo.devName,
|
||||
puId: deviceInfo.puId,
|
||||
})
|
||||
|
||||
return {
|
||||
devName: deviceInfo.devName,
|
||||
puId: deviceInfo.puId,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return robotData.robotInfo
|
||||
}
|
||||
}
|
||||
|
||||
// 获取地图信息
|
||||
export const getRobotMapInfoFn = async (puId) => {
|
||||
if (!robotData.mapInfo.mapBase64) {
|
||||
const { data: res } = await handleRobotActionApi({
|
||||
puId,
|
||||
type: 4,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
const { Data, Height, Width } = res.data
|
||||
const mapInfo = {
|
||||
mapBase64: 'data:image/png;base64,' + Data,
|
||||
mapWidth: Width,
|
||||
mapHeight: Height,
|
||||
}
|
||||
robotData.setMapInfo(mapInfo)
|
||||
return mapInfo
|
||||
}
|
||||
} else {
|
||||
return robotData.mapInfo
|
||||
}
|
||||
}
|
||||
// 获取机器人实时定位信息
|
||||
export const getRobotPointsInfoFn = async (puId) => {
|
||||
if (!robotData.robotPoints.Robot_x && !robotData.robotPoints.Robot_y) {
|
||||
const { data: res } = await handleRobotActionApi({
|
||||
puId,
|
||||
type: 2,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
const pointsInfo = {
|
||||
Robot_x: res?.data?.Robot_x,
|
||||
Robot_y: res?.data?.Robot_y,
|
||||
}
|
||||
robotData.setRobotPoints(pointsInfo)
|
||||
return pointsInfo
|
||||
}
|
||||
} else {
|
||||
return robotData.robotPoints
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,15 @@
|
|||
<!-- 中一 ---- 自巡检视角 -->
|
||||
<div class="center-one child-container">
|
||||
<div class="view-title" v-if="!fullScreenVisible">自巡检视角</div>
|
||||
<div class="video-container">
|
||||
<!-- <video controls style="width: 100%; height: 100%; border-radius: 12px" autoplay>
|
||||
<source src="@/assets/video/测试.mp4" type="video/mp4" autoplay />
|
||||
</video> -->
|
||||
<FlvPlayer :cameraNode="cameraNode_2" :videoId="props.videoId" />
|
||||
<div class="video-container" ref="videoContainerRef">
|
||||
<FlvPlayer :cameraNode="cameraNode_2" :videoId="props.videoId" ref="flvPlayerRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import FlvPlayer from '@/components/FlvPlayer/index.vue'
|
||||
import { nextTick } from 'vue'
|
||||
const props = defineProps({
|
||||
cameraNode_2: {
|
||||
type: Object,
|
||||
|
|
@ -20,13 +18,33 @@ const props = defineProps({
|
|||
},
|
||||
videoId: {
|
||||
type: String,
|
||||
default: 'video-5',
|
||||
default: '',
|
||||
},
|
||||
fullScreenVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const videoContainerRef = ref(null)
|
||||
const flvPlayerRef = ref(null)
|
||||
|
||||
watch(
|
||||
() => props.fullScreenVisible,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
nextTick(() => {
|
||||
console.log(
|
||||
flvPlayerRef.value,
|
||||
'newValnewValnewValnewValnewValnewValnewValnewValnewValnewValnewVal',
|
||||
)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -50,6 +68,7 @@ const props = defineProps({
|
|||
}
|
||||
|
||||
> .video-container {
|
||||
width: 100%;
|
||||
flex: 1; /* 新增:占据剩余空间 */
|
||||
min-height: 0; /* 新增:允许内容压缩 */
|
||||
position: relative; /* 新增:为video定位提供基准 */
|
||||
|
|
@ -59,8 +78,8 @@ const props = defineProps({
|
|||
position: absolute; /* 新增:绝对定位填满容器 */
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
object-fit: cover; /* 保持视频比例 */
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,19 @@
|
|||
<div class="more-btn" @click="onHandleMore">更多</div>
|
||||
</n-flex>
|
||||
|
||||
<div class="marquee-outer">
|
||||
<div class="marquee-outer" ref="marqueeOuterRef">
|
||||
<div class="marquee-container" ref="marqueeContainer">
|
||||
<div class="marquee-track" :style="{ width: trackWidth }">
|
||||
<div class="marquee-item" v-for="item in items" :key="item.id">
|
||||
<div
|
||||
class="marquee-item"
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:style="{ width: imgWidth + 'px' }"
|
||||
>
|
||||
<n-image
|
||||
width="100%"
|
||||
:width="imgWidth"
|
||||
:height="imgHeight"
|
||||
:src="item.image"
|
||||
object-fit="cover"
|
||||
class="item-image"
|
||||
/>
|
||||
<div class="marquee-item-title">途径点{{ item.id }}</div>
|
||||
|
|
@ -23,9 +28,16 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- 克隆元素实现无缝循环 -->
|
||||
<div class="marquee-item" v-for="item in items" :key="`clone-${item.id}`">
|
||||
<div
|
||||
class="marquee-item"
|
||||
v-for="item in items"
|
||||
:key="`clone-${item.id}`"
|
||||
v-if="items.length > 4"
|
||||
:style="{ width: imgWidth + 'px' }"
|
||||
>
|
||||
<n-image
|
||||
width="100%"
|
||||
:width="imgWidth"
|
||||
:height="imgHeight"
|
||||
:src="item.image"
|
||||
object-fit="cover"
|
||||
class="item-image"
|
||||
|
|
@ -84,12 +96,18 @@
|
|||
</n-form>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="table-container">
|
||||
<div class="table-container" ref="tableContainerRef">
|
||||
<!-- 分页 -->
|
||||
|
||||
<div v-for="item in 7" :key="item" class="table-item">
|
||||
<div
|
||||
v-for="item in 7"
|
||||
:key="item"
|
||||
class="table-item"
|
||||
:style="{ width: tableItemWidth + 'px' }"
|
||||
>
|
||||
<n-image
|
||||
width="100%"
|
||||
:width="tableItemWidth"
|
||||
height="170"
|
||||
object-fit="cover"
|
||||
class="item-image"
|
||||
src="https://img1.baidu.com/it/u=3294211986,2810142789&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500"
|
||||
|
|
@ -114,10 +132,12 @@
|
|||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup scoped>
|
||||
import TitleBackground from '@/components/TitleBackground/index.vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
import imgSrc from '@/assets/demo.png'
|
||||
|
||||
const morePanelVisible = ref(false)
|
||||
const searchForm = ref({})
|
||||
const marqueeContainer = ref(null)
|
||||
|
|
@ -126,7 +146,11 @@ const imageHeight = ref('0px')
|
|||
const itemMargin = 10 // 右边距10px
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const itemCount = ref(100)
|
||||
const imgHeight = ref(0)
|
||||
const imgWidth = ref(0)
|
||||
const tableItemWidth = ref(0)
|
||||
const marqueeOuterRef = ref(null)
|
||||
const tableContainerRef = ref(null)
|
||||
|
||||
// 模拟数据
|
||||
const items = ref([
|
||||
|
|
@ -134,7 +158,7 @@ const items = ref([
|
|||
id: 1,
|
||||
time: '14:23:56',
|
||||
status: 'arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
image: imgSrc,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
|
|
@ -143,44 +167,26 @@ const items = ref([
|
|||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
time: '16:45:33',
|
||||
id: 1,
|
||||
time: '14:23:56',
|
||||
status: 'arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
image: imgSrc,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
time: '17:30:11',
|
||||
id: 2,
|
||||
time: '15:10:22',
|
||||
status: 'not-arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
time: '18:15:44',
|
||||
id: 1,
|
||||
time: '14:23:56',
|
||||
status: 'arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
image: imgSrc,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
time: '19:20:05',
|
||||
status: 'not-arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
time: '19:20:05',
|
||||
status: 'not-arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
time: '19:20:05',
|
||||
status: 'not-arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
time: '19:20:05',
|
||||
id: 2,
|
||||
time: '15:10:22',
|
||||
status: 'not-arrived',
|
||||
image: 'https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg',
|
||||
},
|
||||
|
|
@ -215,11 +221,25 @@ const setupMarquee = () => {
|
|||
// 更多按钮点击
|
||||
const onHandleMore = () => {
|
||||
morePanelVisible.value = true
|
||||
|
||||
nextTick(() => {
|
||||
// setupMarquee()
|
||||
|
||||
const tableContainer = tableContainerRef.value
|
||||
|
||||
tableItemWidth.value = (tableContainer.clientWidth - 40) / 4
|
||||
console.log(tableContainer.clientWidth, 'tableContainer')
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupMarquee()
|
||||
window.addEventListener('resize', setupMarquee)
|
||||
nextTick(() => {
|
||||
const container = marqueeContainer.value
|
||||
imgWidth.value = (container.clientWidth - 40) / 4
|
||||
imgHeight.value = `${container.clientHeight * 0.68}`
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
@ -242,6 +262,7 @@ onUnmounted(() => {
|
|||
|
||||
.marquee-outer {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
|
@ -281,13 +302,13 @@ onUnmounted(() => {
|
|||
flex-shrink: 0;
|
||||
|
||||
/* 确保Naive UI的n-image组件完全填充 */
|
||||
:deep(.n-image) {
|
||||
.n-image {
|
||||
width: 100%;
|
||||
display: block;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: v-bind(imageHeight);
|
||||
height: v-bind(imgHeight);
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
|
@ -346,7 +367,7 @@ onUnmounted(() => {
|
|||
|
||||
.more-panel-card {
|
||||
width: 80%;
|
||||
height: 80vh;
|
||||
// height: 80vh;
|
||||
background: url('@/assets/home-imgs/modal-bg.png') no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
|
@ -358,15 +379,15 @@ onUnmounted(() => {
|
|||
}
|
||||
|
||||
.table-container {
|
||||
height: 75%;
|
||||
// height: 75%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
color: #fff;
|
||||
|
||||
.table-item {
|
||||
flex-shrink: 0;
|
||||
width: calc((100% - 30px) / 4);
|
||||
height: calc((100% - 20px) / 2);
|
||||
// width: calc((100% - 30px) / 4);
|
||||
// height: calc((100% - 20px) / 2);
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 5px;
|
||||
|
|
@ -376,23 +397,23 @@ onUnmounted(() => {
|
|||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
|
||||
.item-image {
|
||||
// width: 100%;
|
||||
height: 150px;
|
||||
flex-shrink: 0;
|
||||
// .item-image {
|
||||
// // width: 100%;
|
||||
// height: 150px;
|
||||
// flex-shrink: 0;
|
||||
|
||||
/* 确保Naive UI的n-image组件完全填充 */
|
||||
:deep(.n-image) {
|
||||
// width: 100%;
|
||||
display: block;
|
||||
// /* 确保Naive UI的n-image组件完全填充 */
|
||||
// :deep(.n-image) {
|
||||
// // width: 100%;
|
||||
// display: block;
|
||||
|
||||
img {
|
||||
// width: 100%;
|
||||
// height: v-bind(imageHeight);
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
// img {
|
||||
// // width: 100%;
|
||||
// // height: v-bind(imageHeight);
|
||||
// object-fit: cover;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
.table-item-title {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -3,25 +3,25 @@
|
|||
<div class="control-deck child-container">
|
||||
<!-- 第一行 -->
|
||||
<n-grid :cols="10">
|
||||
<n-grid-item :span="4">
|
||||
<n-grid-item :span="5">
|
||||
<div class="row-1-item">摄像头</div>
|
||||
</n-grid-item>
|
||||
<n-grid-item :span="3">
|
||||
<n-grid-item :span="3" v-if="false">
|
||||
<div class="row-1-item">升降杆</div>
|
||||
</n-grid-item>
|
||||
<n-grid-item :span="3">
|
||||
<n-grid-item :span="5">
|
||||
<div class="row-1-item">底盘</div>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
<!-- 第二行 -->
|
||||
<n-grid :cols="10">
|
||||
<n-grid-item :span="4">
|
||||
<n-grid-item :span="5">
|
||||
<div class="row-2-item">
|
||||
<div>速度</div>
|
||||
<div>30</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
<n-grid-item :span="3">
|
||||
<n-grid-item :span="3" v-if="false">
|
||||
<div class="row-2-item">
|
||||
<div>高度</div>
|
||||
<div>
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
<n-grid-item :span="3">
|
||||
<n-grid-item :span="5">
|
||||
<div class="row-2-item">
|
||||
<div>速度</div>
|
||||
<div>30</div>
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
</n-grid>
|
||||
<!-- 第三行 -->
|
||||
<n-grid :cols="10">
|
||||
<n-grid-item :span="4">
|
||||
<n-grid-item :span="5">
|
||||
<div class="row-3-item-1">
|
||||
<!-- 上下左右控制按钮 -->
|
||||
<img
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
<n-grid-item :span="3">
|
||||
<n-grid-item :span="3" v-if="false">
|
||||
<div class="row-3-item-2" ref="container">
|
||||
<!-- 上下滑动调节 -->
|
||||
<span
|
||||
|
|
@ -109,19 +109,40 @@
|
|||
/>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
<n-grid-item :span="3">
|
||||
<n-grid-item :span="5">
|
||||
<div class="row-3-item-3">
|
||||
<!-- 上下左右控制按钮 -->
|
||||
<img class="arrow-top" src="@/assets/home-imgs/control-2-arrow.png" alt="" />
|
||||
<img class="arrow-right" src="@/assets/home-imgs/control-2-arrow.png" alt="" />
|
||||
<img class="arrow-bottom" src="@/assets/home-imgs/control-2-arrow.png" alt="" />
|
||||
<img class="arrow-left" src="@/assets/home-imgs/control-2-arrow.png" alt="" />
|
||||
<img
|
||||
@click="handleChangeRobot('5')"
|
||||
class="arrow-top"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
@click="handleChangeRobot('8')"
|
||||
class="arrow-right"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
@click="handleChangeRobot('6')"
|
||||
class="arrow-bottom"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
@click="handleChangeRobot('7')"
|
||||
class="arrow-left"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div class="row-3-item-1-center">
|
||||
<img
|
||||
class="center-icon"
|
||||
src="@/assets/home-imgs/control-2-start.png"
|
||||
alt=""
|
||||
@click="handleChangeRobot('9')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -240,7 +261,7 @@
|
|||
<!-- 第六行 -->
|
||||
<n-grid :cols="10" class="row-6">
|
||||
<n-grid-item :span="4">
|
||||
<div class="row-6-item">回桩充电</div>
|
||||
<div class="row-6-item" @click="handleChangeRobot(10)">回桩充电</div>
|
||||
</n-grid-item>
|
||||
<n-grid-item :span="3">
|
||||
<div class="row-6-item-1">
|
||||
|
|
@ -255,7 +276,13 @@
|
|||
import { ref, onMounted } from 'vue'
|
||||
|
||||
import { changeDeviceCameraApi, stopDeviceCameraApi } from '@/utils/initLogin'
|
||||
import { handleRobotActionApi } from '@/api/home'
|
||||
import { useRobotDataStore } from '@/store/robot'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
const robotData = useRobotDataStore()
|
||||
const message = useMessage()
|
||||
const upDownHeight = ref(30)
|
||||
const container = ref(null)
|
||||
const draggable = ref(null)
|
||||
|
|
@ -320,10 +347,10 @@ const stopDrag = () => {
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
const containerHeight = container.value.offsetHeight
|
||||
const draggableHeight = draggable.value.offsetHeight
|
||||
const maxTop = containerHeight - draggableHeight - 20
|
||||
currentTop.value = maxTop * (1 - upDownHeight.value / 100)
|
||||
// const containerHeight = container.value.offsetHeight
|
||||
// const draggableHeight = draggable.value.offsetHeight
|
||||
// const maxTop = containerHeight - draggableHeight - 20
|
||||
// currentTop.value = maxTop * (1 - upDownHeight.value / 100)
|
||||
})
|
||||
|
||||
const handleChange1 = (e) => {
|
||||
|
|
@ -345,18 +372,26 @@ const formatTooltip = (value) => {
|
|||
return `${value}%`
|
||||
}
|
||||
|
||||
const handleChangeCamera = async (direction) => {
|
||||
// 操控机器人上方的摄像头
|
||||
const handleChangeCamera = debounce(async (motion) => {
|
||||
if (!robotData.robotInfo?.puId) {
|
||||
message.error('当前机器人未连接', {
|
||||
duration: 1000,
|
||||
})
|
||||
return
|
||||
}
|
||||
// console.log(direction)
|
||||
const res = await changeDeviceCameraApi({
|
||||
token: localStorage.getItem('token'),
|
||||
puid: '201115200268437643',
|
||||
idx: 1,
|
||||
motion: direction,
|
||||
motion,
|
||||
})
|
||||
|
||||
console.log(res, '调整位置---')
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
// 停止摄像头方向转动
|
||||
const handleStopCamera = async () => {
|
||||
const res = await stopDeviceCameraApi({
|
||||
token: localStorage.getItem('token'),
|
||||
|
|
@ -366,6 +401,32 @@ const handleStopCamera = async () => {
|
|||
|
||||
console.log(res, '停止位置---')
|
||||
}
|
||||
|
||||
// 操控机器人
|
||||
const handleChangeRobot = debounce(async (type) => {
|
||||
// type 5 前进 6 后退 7 左转 8 右转
|
||||
if (!robotData.robotInfo?.puId) {
|
||||
message.error('当前机器人未连接', {
|
||||
duration: 1000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
puId: robotData.robotInfo?.puId,
|
||||
type,
|
||||
}
|
||||
|
||||
console.log(params, 'params---')
|
||||
|
||||
const res = await handleRobotActionApi(params)
|
||||
console.log(res, '操控机器人---')
|
||||
}, 1000)
|
||||
|
||||
// 停止机器人
|
||||
const handleStopRobot = async () => {
|
||||
console.log('停止机器人')
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.control-deck {
|
||||
|
|
|
|||
|
|
@ -31,36 +31,46 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预置位配置 -->
|
||||
<!-- 预置位配置弹框 -->
|
||||
<n-modal v-model:show="presetSettingVisible">
|
||||
<PresetSetting
|
||||
v-if="showModal === 1"
|
||||
@onHandleCloseModal="onHandleCloseModal"
|
||||
@onHandleAddMarker="onHandleAddMarker"
|
||||
:presetSettingVisible="presetSettingVisible"
|
||||
/>
|
||||
<InspectionTask
|
||||
v-if="showModal === 2"
|
||||
@onHandleCloseModal="onHandleCloseModal"
|
||||
@onHandleAddInspectionTask="onHandleAddInspectionTask"
|
||||
/>
|
||||
<div>
|
||||
<PresetSetting
|
||||
:markerInfoNew="markerInfoNew"
|
||||
v-if="presetSettingVisible"
|
||||
@onHandleAddMarker="onHandleAddMarker"
|
||||
@onHandleCloseModal="onHandleCloseSettingModal"
|
||||
/>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- 新增巡视任务 -->
|
||||
<n-modal v-model:show="addInspectionTaskVisible">
|
||||
<AddOrEditForm
|
||||
v-if="addInspectionTaskVisible"
|
||||
@onHandleCloseModalInner="onHandleCloseModalInner"
|
||||
/>
|
||||
</n-modal>
|
||||
|
||||
<!-- 新增预置点位 -->
|
||||
<n-modal v-model:show="addMarkerVisible">
|
||||
<AddOrEditMarkerForm
|
||||
v-if="addMarkerVisible"
|
||||
:markerInfo="markerInfo"
|
||||
@onHandleCloseAddModal="onHandleCloseAddModal"
|
||||
/>
|
||||
<div>
|
||||
<AddOrEditMarkerForm
|
||||
v-if="addMarkerVisible"
|
||||
:markerInfo="markerInfo"
|
||||
@onHandleConfirm="onHandleConfirm"
|
||||
@onHandleCloseAddMarkerModal="onHandleCloseAddMarkerModal"
|
||||
/>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<n-modal v-model:show="inspectionTaskVisible">
|
||||
<div>
|
||||
<InspectionTask
|
||||
v-if="inspectionTaskVisible"
|
||||
@onHandleCloseModal="onHandleCloseInspectionModal"
|
||||
@onHandleAddInspectionTask="onHandleAddInspectionTask"
|
||||
/>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<n-modal v-model:show="addInspectionTaskVisible">
|
||||
<div>
|
||||
<AddOrEditForm
|
||||
v-if="addInspectionTaskVisible"
|
||||
@onHandleCloseModalInner="onHandleCloseModalInner"
|
||||
/>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
|
|
@ -72,11 +82,12 @@ import AddOrEditForm from './modal-content/add-or-edit-form.vue'
|
|||
import AddOrEditMarkerForm from './modal-content/add-or-edit-marker-form.vue'
|
||||
|
||||
import FlvPlayer from '@/components/FlvPlayer/index.vue'
|
||||
const presetSettingVisible = ref(false)
|
||||
const addInspectionTaskVisible = ref(false)
|
||||
const addMarkerVisible = ref(false)
|
||||
const showModal = ref(1)
|
||||
const presetSettingVisible = ref(false) // 预置位配置
|
||||
const addMarkerVisible = ref(false) // 新增预置点位
|
||||
const inspectionTaskVisible = ref(false) // 巡视任务
|
||||
const addInspectionTaskVisible = ref(false) // 新增巡视任务
|
||||
const markerInfo = ref({}) // 新增预置点位信息
|
||||
const markerInfoNew = ref({}) // 新增预置点位信息 修改后的数据
|
||||
|
||||
const props = defineProps({
|
||||
fullScreenVisible: {
|
||||
|
|
@ -103,70 +114,80 @@ const emits = defineEmits([
|
|||
'onHandleChangeView',
|
||||
])
|
||||
|
||||
// 预置位配置
|
||||
// 上方按钮组 ----------------------------
|
||||
|
||||
// 预置位配置 1
|
||||
const onHandlePresetSetting = () => {
|
||||
showModal.value = 1
|
||||
presetSettingVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭预置位配置
|
||||
const onHandleCloseModal = () => {
|
||||
showModal.value = null
|
||||
presetSettingVisible.value = false
|
||||
}
|
||||
|
||||
// 巡视任务
|
||||
// 巡视任务 2
|
||||
const onHandleInspectionTask = () => {
|
||||
showModal.value = 2
|
||||
presetSettingVisible.value = true
|
||||
inspectionTaskVisible.value = true
|
||||
}
|
||||
|
||||
// 新增巡视任务
|
||||
const onHandleAddInspectionTask = () => {
|
||||
addInspectionTaskVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭新增或修改巡视任务
|
||||
const onHandleCloseModalInner = () => {
|
||||
addInspectionTaskVisible.value = false
|
||||
}
|
||||
|
||||
// 新增预置点位
|
||||
const onHandleAddMarker = (info) => {
|
||||
markerInfo.value = info
|
||||
addMarkerVisible.value = true
|
||||
}
|
||||
// 关闭新增预置点位
|
||||
const onHandleCloseAddModal = () => {
|
||||
addMarkerVisible.value = false
|
||||
}
|
||||
|
||||
// 打开操作面板
|
||||
// 操作面板 3
|
||||
const onHandleOperationPanel = () => {
|
||||
emits('onHandleOperationPanel', true)
|
||||
}
|
||||
|
||||
// 全屏控制
|
||||
// 全屏控制 4
|
||||
const onHandleOpenFullScreen = () => {
|
||||
emits('onHandleFullScreenToggle', true)
|
||||
}
|
||||
|
||||
// 退出全屏
|
||||
// 退出全屏 5
|
||||
const onHandleBackFullScreen = () => {
|
||||
emits('onHandleFullScreenToggle', false)
|
||||
}
|
||||
|
||||
// 视角切换
|
||||
// 视角切换 6
|
||||
const onHandleChangeView = () => {
|
||||
emits('onHandleChangeView')
|
||||
}
|
||||
|
||||
// 关闭预置位配置弹框
|
||||
const onHandleCloseSettingModal = () => {
|
||||
presetSettingVisible.value = false
|
||||
}
|
||||
|
||||
// 新增预置点位 确认
|
||||
const onHandleConfirm = (info) => {
|
||||
markerInfoNew.value = info
|
||||
addMarkerVisible.value = false
|
||||
}
|
||||
|
||||
// 新增巡视任务 打开新增巡视任务弹框
|
||||
const onHandleAddInspectionTask = () => {
|
||||
addInspectionTaskVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭巡视任务弹框
|
||||
const onHandleCloseInspectionModal = () => {
|
||||
inspectionTaskVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭新增或修改巡视任务弹框
|
||||
const onHandleCloseModalInner = () => {
|
||||
addInspectionTaskVisible.value = false
|
||||
}
|
||||
|
||||
// 新增预置点位 打开新增预置点位弹框
|
||||
const onHandleAddMarker = (info) => {
|
||||
markerInfo.value = info
|
||||
addMarkerVisible.value = true
|
||||
}
|
||||
// 关闭新增预置点位弹框
|
||||
const onHandleCloseAddMarkerModal = () => {
|
||||
addMarkerVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.left-one {
|
||||
display: flex; /* 新增 */
|
||||
flex-direction: column; /* 新增 */
|
||||
height: 100%; /* 确保容器有高度 */
|
||||
// height: 100%; /* 确保容器有高度 */
|
||||
|
||||
.btns {
|
||||
// width: 90px;
|
||||
|
|
|
|||
|
|
@ -5,20 +5,17 @@
|
|||
|
||||
<div class="map-container" ref="mapContainerRef">
|
||||
<svg ref="svgMapRef" class="svg-map-container" :width="svgWidth" :height="svgHeight">
|
||||
<!-- 图片,宽高 100% 跟随 SVG -->
|
||||
<image
|
||||
href="@/assets/demo.png"
|
||||
:href="mapInfo.mapBase64"
|
||||
width="100%"
|
||||
height="100%"
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
|
||||
<!-- 标记的点位 -->
|
||||
<circle
|
||||
v-for="(point, index) in devicePoints"
|
||||
:key="index"
|
||||
:cx="point.x * scale + offsetX / scale"
|
||||
:cy="point.y * scale + offsetY / scale"
|
||||
:cx="point.x * xScale"
|
||||
:cy="point.y * yScale"
|
||||
r="8"
|
||||
fill="red"
|
||||
/>
|
||||
|
|
@ -28,23 +25,54 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { ref, nextTick, onMounted } from 'vue'
|
||||
import TitleBackground from '@/components/TitleBackground/index.vue'
|
||||
const mapTitle = ref('巡检地图')
|
||||
const svgWidth = ref(0)
|
||||
const svgHeight = ref(0)
|
||||
const devicePoints = ref([{ x: 100, y: 100 }])
|
||||
const scale = ref(1)
|
||||
const offsetX = ref(0)
|
||||
const offsetY = ref(0)
|
||||
import { getRobotDeviceListFn, getRobotMapInfoFn, getRobotPointsInfoFn } from '@/utils/getRobotInfo'
|
||||
|
||||
const mapTitle = ref('巡检地图') // 地图标题
|
||||
const svgMapRef = ref(null) // 地图svg引用
|
||||
const svgWidth = ref(0) // 地图svg宽度
|
||||
const svgHeight = ref(0) // 地图svg高度
|
||||
const xScale = ref(1) // 地图x轴缩放比例
|
||||
const yScale = ref(1) // 地图y轴缩放比例
|
||||
const devicePoints = ref([]) // 机器人点位
|
||||
const mapInfo = ref({}) // 地图信息
|
||||
// 获取map-container的宽高赋值给svg
|
||||
const mapContainerRef = ref(null)
|
||||
onMounted(() => {
|
||||
// 计算宽高比
|
||||
const calculateScale = (x, y) => {
|
||||
const widthRatio = svgWidth.value / x
|
||||
const heightRatio = svgHeight.value / y
|
||||
xScale.value = widthRatio
|
||||
yScale.value = heightRatio
|
||||
}
|
||||
|
||||
// 标记点位
|
||||
const markPoints = (Robot_x, Robot_y) => {
|
||||
console.log(Robot_x, Robot_y, 'Robot_x, Robot_y')
|
||||
|
||||
const svgRect = svgMapRef.value.getBoundingClientRect()
|
||||
const clientY = svgHeight.value - yScale.value * Robot_y + svgRect.top
|
||||
const logicalY1 = (clientY - svgRect.top) / yScale.value
|
||||
devicePoints.value.push({ x: Robot_x, y: logicalY1 })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
nextTick(() => {
|
||||
svgWidth.value = mapContainerRef.value.clientWidth
|
||||
svgHeight.value = mapContainerRef.value.clientHeight
|
||||
})
|
||||
|
||||
// 获取设备信息
|
||||
const deviceInfo = await getRobotDeviceListFn()
|
||||
// 获取地图信息
|
||||
mapInfo.value = await getRobotMapInfoFn(deviceInfo?.puId)
|
||||
// 计算缩放比例
|
||||
calculateScale(mapInfo.value?.mapWidth, mapInfo.value?.mapHeight)
|
||||
// 获取机器人点位
|
||||
const pointsInfo = await getRobotPointsInfoFn(deviceInfo?.puId)
|
||||
// 标记点位
|
||||
markPoints(pointsInfo?.Robot_x, pointsInfo?.Robot_y)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -56,17 +84,12 @@ onMounted(() => {
|
|||
.map-container {
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
background-color: #000;
|
||||
// background-color: #000;
|
||||
position: relative;
|
||||
|
||||
.svg-map-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
// overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<!-- 新增或编辑弹框 -->
|
||||
<DialogModal
|
||||
:width="`80%`"
|
||||
:width="`80vw`"
|
||||
:modalTitle="addOrEditTitle"
|
||||
@onHandleCloseModal="onHandleCloseModalInner"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,108 +2,159 @@
|
|||
<DialogModal
|
||||
@onHandleCloseModal="onHandleCloseAddModal"
|
||||
:modalTitle="`${markerParams.type}预置点位`"
|
||||
:width="`50%`"
|
||||
:width="`120vh`"
|
||||
:height="`90vh`"
|
||||
>
|
||||
<n-form
|
||||
ref="addOrEditFormRef"
|
||||
size="small"
|
||||
label-placement="left"
|
||||
style="margin-top: 10px"
|
||||
label-width="140"
|
||||
>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="点位序号:">
|
||||
<span style="color: #fff">
|
||||
{{ markerParams.markerIndex + 1 }}
|
||||
</span>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="点位名称:" prop="markerName">
|
||||
<n-input
|
||||
v-model:value="markerParams.markerName"
|
||||
placeholder="点位名称"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<div style="display: flex; flex-direction: column; height: 100%">
|
||||
<n-form
|
||||
ref="addOrEditFormRef"
|
||||
size="small"
|
||||
label-placement="left"
|
||||
style="margin-top: 10px"
|
||||
label-width="140"
|
||||
>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="点位序号:">
|
||||
<span style="color: #fff">
|
||||
{{ markerParams.markerIndex + 1 }}
|
||||
</span>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="点位名称:" prop="markerName">
|
||||
<n-input
|
||||
v-model:value="markerParams.markerName"
|
||||
placeholder="点位名称"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="位置:"> </n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="9">
|
||||
<n-form-item label="x:" prop="markerX">
|
||||
<n-input
|
||||
v-model:value="markerParams.markerX"
|
||||
placeholder="x坐标"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="9">
|
||||
<n-form-item label="y:" prop="markerY">
|
||||
<n-input
|
||||
v-model:value="markerParams.markerY"
|
||||
placeholder="y坐标"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="角度:" prop="markerAngle">
|
||||
<n-input
|
||||
v-model:value="markerParams.markerAngle"
|
||||
placeholder="角度"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="6">
|
||||
<n-form-item label="位置:"> </n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="9">
|
||||
<n-form-item label="x:" prop="xCount">
|
||||
<n-input
|
||||
type="text"
|
||||
clearable
|
||||
placeholder="x坐标"
|
||||
:allow-input="onlyAllowNumber"
|
||||
v-model:value.trim="markerParams.xCount"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi :span="9">
|
||||
<n-form-item label="y:" prop="yCount">
|
||||
<n-input
|
||||
clearable
|
||||
type="text"
|
||||
placeholder="y坐标"
|
||||
:allow-input="onlyAllowNumber"
|
||||
v-model:value.trim="markerParams.yCount"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="角度:" prop="markerAngle">
|
||||
<n-input
|
||||
v-model:value="markerParams.markerAngle"
|
||||
placeholder="角度"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="摄像头预置位:" prop="markerPreset">
|
||||
<n-input
|
||||
v-model:value="markerParams.markerPreset"
|
||||
placeholder="摄像头预置位"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-form-item label="摄像头预置位:" prop="markerPreset">
|
||||
<!-- <n-input
|
||||
v-model:value="markerParams.markerPreset"
|
||||
placeholder="摄像头预置位"
|
||||
clearable
|
||||
/> -->
|
||||
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-flex justify="end">
|
||||
<n-button type="info" style="margin-right: 10px"> 确认 </n-button>
|
||||
<n-button type="info"> 取消 </n-button>
|
||||
</n-flex>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
<n-select
|
||||
:options="presetOptions"
|
||||
placeholder="请选择摄像头预置位"
|
||||
v-model:value="markerParams.markerPreset"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-grid x-gap="24" :cols="24">
|
||||
<n-gi :span="24">
|
||||
<n-flex justify="end">
|
||||
<n-button
|
||||
type="info"
|
||||
style="margin-right: 10px"
|
||||
@click="onHandleConfirm"
|
||||
>
|
||||
确认
|
||||
</n-button>
|
||||
<n-button color="#6E90A9" @click="onHandleCloseAddModal">
|
||||
取消
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
|
||||
<n-flex justify="space-between" style="flex: 1; margin-top: 10px">
|
||||
<div class="left-box" style="width: 49%">
|
||||
<!-- 放置video -->
|
||||
<FlvPlayer />
|
||||
</div>
|
||||
<div class="right-box" style="width: 46%">
|
||||
<!-- 放置操控面板 -->
|
||||
<ControlDeck :deviceToken="deviceToken" :deviceInfo="deviceInfo" />
|
||||
</div>
|
||||
</n-flex>
|
||||
</div>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { watch } from 'vue'
|
||||
import FlvPlayer from '@/components/FlvPlayer/index.vue'
|
||||
import ControlDeck from './control-deck.vue'
|
||||
import { watch, ref, onMounted } from 'vue'
|
||||
import { getRobotTokenFn, getRobotDeviceListFn } from '@/utils/getRobotInfo'
|
||||
|
||||
const emits = defineEmits(['onHandleCloseAddModal'])
|
||||
const deviceToken = ref('')
|
||||
const deviceInfo = ref({})
|
||||
|
||||
const emits = defineEmits(['onHandleCloseAddMarkerModal', 'onHandleConfirm'])
|
||||
const presetOptions = ref([
|
||||
{
|
||||
label: '正前',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '正右',
|
||||
value: 2,
|
||||
},
|
||||
])
|
||||
const markerParams = ref({
|
||||
type: '新增', // 新增/修改
|
||||
markerIndex: '', // 点位序号
|
||||
markerX: '', // x坐标
|
||||
markerY: '', // y坐标
|
||||
markerY1: '', // y1坐标
|
||||
markerName: '', // 点位名称
|
||||
markerAngle: '', // 角度
|
||||
markerPreset: '', // 摄像头预置位
|
||||
xCount: '', // 像素坐标 真实数据
|
||||
yCount: '', // 像素坐标 真实数据
|
||||
})
|
||||
const props = defineProps({
|
||||
markerInfo: {
|
||||
|
|
@ -112,22 +163,37 @@ const props = defineProps({
|
|||
},
|
||||
})
|
||||
const onHandleCloseAddModal = () => {
|
||||
emits('onHandleCloseAddModal')
|
||||
emits('onHandleCloseAddMarkerModal')
|
||||
}
|
||||
|
||||
// 确认按钮
|
||||
const onHandleConfirm = () => {
|
||||
emits('onHandleConfirm', markerParams.value)
|
||||
}
|
||||
|
||||
const onlyAllowNumber = (value) => !value || /^\d+$/.test(value)
|
||||
|
||||
watch(
|
||||
() => props.markerInfo,
|
||||
(newVal) => {
|
||||
markerParams.value.type = newVal?.type
|
||||
markerParams.value.markerIndex = newVal?.markerIndex
|
||||
markerParams.value.markerX = Math.floor(newVal?.markerX)
|
||||
markerParams.value.markerY = Math.floor(newVal?.markerY)
|
||||
markerParams.value.markerX = Math.ceil(newVal?.markerX).toString()
|
||||
markerParams.value.markerY = Math.ceil(newVal?.markerY).toString()
|
||||
markerParams.value.markerY1 = newVal?.markerY1
|
||||
markerParams.value.markerName = newVal?.markerName
|
||||
markerParams.value.markerAngle = newVal?.markerAngle
|
||||
markerParams.value.markerPreset = newVal?.markerPreset
|
||||
markerParams.value.xCount = Math.ceil(newVal?.xCount).toString()
|
||||
markerParams.value.yCount = Math.ceil(newVal?.yCount).toString()
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
deviceToken.value = await getRobotTokenFn()
|
||||
deviceInfo.value = await getRobotDeviceListFn()
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,352 @@
|
|||
<template>
|
||||
<!-- 机器人操控面板 -->
|
||||
<div class="control-deck">
|
||||
<!-- 第一行 -->
|
||||
<n-grid :cols="24">
|
||||
<n-grid-item :span="12">
|
||||
<div class="row-3-item-1">
|
||||
<!-- 上下左右控制按钮 -->
|
||||
<img
|
||||
class="arrow-top hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnUp')"
|
||||
/>
|
||||
<img
|
||||
class="arrow-right hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnRight')"
|
||||
/>
|
||||
<img
|
||||
class="arrow-bottom hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnDown')"
|
||||
/>
|
||||
<img
|
||||
class="arrow-left hand-direction"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
@click="handleChangeCamera('TurnLeft')"
|
||||
/>
|
||||
|
||||
<!-- 中间的按钮 -->
|
||||
<div class="row-3-item-1-center" @click="handleStopCamera">
|
||||
<img
|
||||
alt=""
|
||||
class="center-icon"
|
||||
:src="isStopLeft ? startImg : stopImg"
|
||||
@click="handleChangeRobot('9')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 描述文字 -->
|
||||
<div class="text-top description-text">正前</div>
|
||||
<div class="text-bottom description-text">正后</div>
|
||||
<div class="text-left description-text">正左</div>
|
||||
<div class="text-right description-text">正右</div>
|
||||
|
||||
<!-- 增加减少按钮 -->
|
||||
<div class="add-reduce-btn">
|
||||
<span>缩放</span>
|
||||
<img
|
||||
style="margin-left: 20px; transform: rotate(-90deg)"
|
||||
src="@/assets/home-imgs/control-2-add.png"
|
||||
@click="handleChangeCamera('ZoomIn')"
|
||||
/>
|
||||
<img
|
||||
style="margin-left: 50px; transform: rotate(-90deg)"
|
||||
src="@/assets/home-imgs/control-2-reduce.png"
|
||||
@click="handleChangeCamera('ZoomOut')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item :span="12">
|
||||
<div class="row-3-item-3">
|
||||
<!-- 上下左右控制按钮 -->
|
||||
<img
|
||||
@click="handleChangeRobot('5')"
|
||||
class="arrow-top"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
@click="handleChangeRobot('8')"
|
||||
class="arrow-right"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
@click="handleChangeRobot('6')"
|
||||
class="arrow-bottom"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
@click="handleChangeRobot('7')"
|
||||
class="arrow-left"
|
||||
src="@/assets/home-imgs/control-2-arrow.png"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div class="row-3-item-1-center">
|
||||
<img
|
||||
class="center-icon"
|
||||
:src="isStopRight ? startImg : stopImg"
|
||||
@click="handleChangeRobot('9')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { changeDeviceCameraApi, stopDeviceCameraApi } from '@/utils/initLogin'
|
||||
import { handleRobotActionApi } from '@/api/home'
|
||||
import { useRobotDataStore } from '@/store/robot'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { debounce } from 'lodash'
|
||||
import stopImg from '@/assets/home-imgs/control-2-stop.png'
|
||||
import startImg from '@/assets/home-imgs/control-2-start.png'
|
||||
const robotData = useRobotDataStore()
|
||||
const message = useMessage()
|
||||
const isStopLeft = ref(false)
|
||||
const isStopRight = ref(false)
|
||||
|
||||
const props = defineProps({
|
||||
deviceToken: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
deviceInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
// 操控机器人上方的摄像头
|
||||
const handleChangeCamera = debounce(async (motion) => {
|
||||
if (!robotData.robotInfo?.puId) {
|
||||
message.error('当前机器人未连接', {
|
||||
duration: 1000,
|
||||
})
|
||||
return
|
||||
}
|
||||
const res = await changeDeviceCameraApi({
|
||||
token: props.deviceToken,
|
||||
puid: props.deviceInfo.puId,
|
||||
idx: 1,
|
||||
motion,
|
||||
})
|
||||
|
||||
console.log(res, '调整上方的摄像头位置---')
|
||||
}, 1000)
|
||||
|
||||
// 停止摄像头方向转动
|
||||
const handleStopCamera = async () => {
|
||||
const res = await stopDeviceCameraApi({
|
||||
token: props.deviceToken,
|
||||
puid: props.deviceInfo.puId,
|
||||
idx: 1,
|
||||
})
|
||||
|
||||
console.log(res, '停止位置---')
|
||||
}
|
||||
|
||||
// 操控机器人
|
||||
const handleChangeRobot = debounce(async (type) => {
|
||||
// type 5 前进 6 后退 7 左转 8 右转 9 停止
|
||||
if (!robotData.robotInfo?.puId) {
|
||||
message.error('当前机器人未连接', {
|
||||
duration: 1000,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
puId: robotData.robotInfo?.puId,
|
||||
type,
|
||||
}
|
||||
const { data: res } = await handleRobotActionApi(params)
|
||||
if (res.code == 200) {
|
||||
if (type == '9') {
|
||||
isStopRight.value = false
|
||||
} else {
|
||||
isStopRight.value = true
|
||||
}
|
||||
}
|
||||
}, 600)
|
||||
|
||||
onMounted(async () => {})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.control-deck {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 112, 190, 0.3);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.row-3-item-1 {
|
||||
margin-right: 20px !important;
|
||||
}
|
||||
.row-3-item-1,
|
||||
.row-3-item-3 {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 40px auto 0;
|
||||
position: relative;
|
||||
background: url('@/assets/home-imgs/control-2-round.png') no-repeat center center;
|
||||
background-size: 100% 100%;
|
||||
|
||||
> img {
|
||||
width: 24px;
|
||||
height: 26px;
|
||||
cursor: pointer;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.arrow-top {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(-90deg);
|
||||
}
|
||||
.arrow-right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 8px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.arrow-bottom {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(90deg);
|
||||
}
|
||||
.arrow-left {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 8px;
|
||||
transform: translateY(-50%) rotate(-180deg);
|
||||
}
|
||||
|
||||
.row-3-item-1-center {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
border: 2px solid #7fc0ff;
|
||||
border-radius: 4px;
|
||||
background-color: #2f78bf;
|
||||
cursor: pointer;
|
||||
|
||||
> img {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
object-fit: contain;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 描述文字
|
||||
.description-text {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.text-top {
|
||||
left: 50%;
|
||||
top: -40px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.text-bottom {
|
||||
left: 50%;
|
||||
bottom: -40px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.text-top,
|
||||
.text-bottom {
|
||||
width: 36px;
|
||||
height: 22px;
|
||||
border: 1px solid #7fc0ff;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
left: -40px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.text-right {
|
||||
right: -40px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.text-left,
|
||||
.text-right {
|
||||
height: 36px;
|
||||
width: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #7fc0ff;
|
||||
border-radius: 4px;
|
||||
// 使文字竖向展示 并垂直居中
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: upright;
|
||||
}
|
||||
|
||||
.add-reduce-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -100px;
|
||||
// height: 100px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
// 反转90度
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
color: #7fc0ff;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
height: 5%;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row-3-item-3 {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
margin: 38px auto 0 !important;
|
||||
|
||||
> img {
|
||||
width: 24px;
|
||||
height: 26px;
|
||||
cursor: pointer;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
@onHandleCloseModal="onHandleCloseModal"
|
||||
:modalTitle="modalTitle"
|
||||
:height="`90vh`"
|
||||
:width="`90%`"
|
||||
:width="`90vw`"
|
||||
>
|
||||
<!-- 巡视任务-->
|
||||
<div class="inspection-task-container" ref="inspectionTaskContainer">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<DialogModal
|
||||
@onHandleCloseModal="onHandleCloseModal"
|
||||
:modalTitle="modalTitle"
|
||||
:width="`70%`"
|
||||
:width="`90vw`"
|
||||
style="position: relative"
|
||||
>
|
||||
<!-- 平面图操作区域 -->
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
class="plane-map-container"
|
||||
ref="planeMapContainer"
|
||||
:class="{ 'can-drag': canDrag() }"
|
||||
:style="{ height: INITIAL_HEIGHT + 'px' }"
|
||||
@mousemove="handleMouseMove"
|
||||
>
|
||||
<svg
|
||||
|
|
@ -18,15 +19,15 @@
|
|||
class="svg-map-container"
|
||||
:width="svgWidth"
|
||||
:height="svgHeight"
|
||||
@wheel="handleWheel"
|
||||
@wheel.passive="handleWheel"
|
||||
@click="handleMapClick"
|
||||
:style="{ transform: `translate(${offsetX}px, ${offsetY}px)` }"
|
||||
>
|
||||
<!-- 图片,宽高 100% 跟随 SVG -->
|
||||
<image
|
||||
href="@/assets/demo.png"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:href="mapInfo.mapBase64"
|
||||
preserveAspectRatio="none"
|
||||
/>
|
||||
|
||||
|
|
@ -34,8 +35,8 @@
|
|||
<circle
|
||||
v-for="(point, index) in devicePoints"
|
||||
:key="index"
|
||||
:cx="point.x * scale + offsetX / scale"
|
||||
:cy="point.y1 * scale + offsetY / scale"
|
||||
:cx="point.markerX * scale + offsetX / scale"
|
||||
:cy="point.markerY1 * scale + offsetY / scale"
|
||||
r="8"
|
||||
fill="red"
|
||||
@click="handlePointClick(point, index)"
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
|
||||
<div v-if="showTooltip && mousePosition" class="coord-tooltip" :style="tooltipStyle">
|
||||
<!-- 数据向下取整 -->
|
||||
X: {{ Math.floor(mousePosition.x) }}, Y: {{ Math.floor(mousePosition.y) }}
|
||||
X: {{ Math.floor(mousePosition.x) * 2 }}, Y: {{ Math.floor(mousePosition.y) * 2 }}
|
||||
</div>
|
||||
|
||||
<div v-if="showContextMenu" class="context-menu" :style="contextMenuStyle" @click.stop>
|
||||
|
|
@ -77,46 +78,34 @@
|
|||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { refThrottled, useMouseInElement } from '@vueuse/core'
|
||||
import { ref, onMounted, nextTick, watch, onBeforeUnmount } from 'vue'
|
||||
import { NIcon, useMessage } from 'naive-ui'
|
||||
import { ref, onMounted, watch, onBeforeUnmount } from 'vue'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import { throttle } from 'lodash'
|
||||
import { TrashSharp, AddCircleOutline, RemoveCircleOutline } from '@vicons/ionicons5'
|
||||
import demoImg from '@/assets/demo.png'
|
||||
import { AddCircleOutline, RemoveCircleOutline } from '@vicons/ionicons5'
|
||||
import { getRobotDeviceListFn, getRobotMapInfoFn } from '@/utils/getRobotInfo'
|
||||
|
||||
const itemRefs = ref([])
|
||||
|
||||
const setItemRef = (el, index) => {
|
||||
if (el) {
|
||||
itemRefs.value[index] = el
|
||||
}
|
||||
}
|
||||
|
||||
const emits = defineEmits(['onHandleCloseModal', 'onHandleAddMarker'])
|
||||
const modalTitle = ref('预置位配置')
|
||||
const mousePosition = ref(null)
|
||||
const devicePoints = ref([])
|
||||
const isPointClickStatus = ref(true)
|
||||
const showAddModal = ref(false)
|
||||
|
||||
// 右键菜单相关状态
|
||||
const showContextMenu = ref(false)
|
||||
const contextMenuStyle = ref({})
|
||||
const selectedPointIndex = ref(-1)
|
||||
|
||||
// 新增:容器尺寸跟踪
|
||||
const containerSize = ref({ width: 0, height: 0 })
|
||||
const planeMapContainer = ref(null)
|
||||
const svgMapRef = ref(null)
|
||||
const tooltipStyle = ref({})
|
||||
const modalTitle = ref('预置位配置') // 模态框标题
|
||||
const mousePosition = ref(null) // 鼠标位置
|
||||
const devicePoints = ref([]) // 设备点位
|
||||
const isPointClickStatus = ref(true) // 是否点击点位
|
||||
const mapInfo = ref({}) // 地图信息
|
||||
|
||||
// SVG 初始尺寸
|
||||
const INITIAL_WIDTH = 800
|
||||
const INITIAL_HEIGHT = 500
|
||||
const INITIAL_WIDTH = ref(0)
|
||||
const INITIAL_HEIGHT = ref(0)
|
||||
|
||||
// SVG 动态宽高
|
||||
const svgWidth = ref(INITIAL_WIDTH)
|
||||
const svgHeight = ref(INITIAL_HEIGHT)
|
||||
const svgWidth = ref(INITIAL_WIDTH.value)
|
||||
const svgHeight = ref(INITIAL_HEIGHT.value)
|
||||
|
||||
// 拖拽相关状态
|
||||
const isDragging = ref(false) // 是否拖拽
|
||||
const offsetX = ref(0) // 偏移量X
|
||||
const offsetY = ref(0) // 偏移量Y
|
||||
const startX = ref(0) // 起始X
|
||||
const startY = ref(0) // 起始Y
|
||||
const maxOffsetX1 = ref(0) // 最大偏移量X
|
||||
const maxOffsetY1 = ref(0) // 最大偏移量Y
|
||||
|
||||
// 缩放比例
|
||||
const scale = ref(1.0)
|
||||
|
|
@ -126,6 +115,34 @@ const MIN_SCALE = 0.5 // 最小缩放比例
|
|||
const MAX_SCALE = 3 // 最大缩放比例
|
||||
const SCALE_STEP = 0.1 // 每次滚轮的缩放步长
|
||||
|
||||
// 右键菜单相关状态
|
||||
const showContextMenu = ref(false) // 是否显示右键菜单
|
||||
const contextMenuStyle = ref({}) // 右键菜单样式
|
||||
const selectedPointIndex = ref(-1) // 选中的点位索引
|
||||
|
||||
// 容器尺寸跟踪
|
||||
const planeMapContainer = ref(null) // 容器引用
|
||||
const svgMapRef = ref(null) // svg引用
|
||||
const tooltipStyle = ref({}) // 提示框样式
|
||||
|
||||
const itemRefs = ref([]) // 标记点位引用
|
||||
const emits = defineEmits(['onHandleCloseModal', 'onHandleAddMarker']) // 定义 emits
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
markerInfoNew: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
|
||||
// 设置标记点位引用
|
||||
const setItemRef = (el, index) => {
|
||||
if (el) {
|
||||
itemRefs.value[index] = el
|
||||
}
|
||||
}
|
||||
|
||||
// 缩放时调整偏移量(以鼠标位置为中心)
|
||||
const adjustOffsetOnZoom = (e, newScale) => {
|
||||
const container = planeMapContainer.value
|
||||
|
|
@ -146,6 +163,7 @@ const adjustOffsetOnZoom = (e, newScale) => {
|
|||
enforceBoundaries() // 确保修正后不越界
|
||||
}
|
||||
|
||||
// 缩放
|
||||
const zoom = (direction, e = { clientX: 0, clientY: 0 }) => {
|
||||
const delta = direction === 'in' ? SCALE_STEP : -SCALE_STEP
|
||||
const newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale.value + delta))
|
||||
|
|
@ -158,19 +176,25 @@ const zoom = (direction, e = { clientX: 0, clientY: 0 }) => {
|
|||
}
|
||||
}
|
||||
|
||||
// 修改原有方法
|
||||
// 放大
|
||||
const zoomIn = (e) => zoom('in', e)
|
||||
|
||||
// 缩小
|
||||
const zoomOut = (e) => zoom('out', e)
|
||||
|
||||
// 滚轮缩放
|
||||
const handleWheel = (e) => {
|
||||
e.preventDefault()
|
||||
zoom(e.deltaY > 0 ? 'out' : 'in', e)
|
||||
requestAnimationFrame(() => {
|
||||
e.preventDefault() // 延迟阻止默认行为
|
||||
zoom(e.deltaY > 0 ? 'out' : 'in', e)
|
||||
})
|
||||
}
|
||||
|
||||
// 更新 SVG 尺寸
|
||||
const updateSvgSize = () => {
|
||||
const oldScale = scale.value
|
||||
svgWidth.value = INITIAL_WIDTH * scale.value
|
||||
svgHeight.value = INITIAL_HEIGHT * scale.value
|
||||
svgWidth.value = INITIAL_WIDTH.value * scale.value
|
||||
svgHeight.value = INITIAL_HEIGHT.value * scale.value
|
||||
|
||||
// 按比例修正偏移量(保持视觉连续性)
|
||||
if (oldScale !== 0) {
|
||||
|
|
@ -182,6 +206,7 @@ const updateSvgSize = () => {
|
|||
enforceBoundaries()
|
||||
}
|
||||
|
||||
// 确保修正后不越界
|
||||
const enforceBoundaries = () => {
|
||||
const { maxOffsetX, maxOffsetY } = getMaxOffset()
|
||||
|
||||
|
|
@ -198,13 +223,6 @@ const enforceBoundaries = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 拖拽相关状态
|
||||
const isDragging = ref(false)
|
||||
const offsetX = ref(0)
|
||||
const offsetY = ref(0)
|
||||
const startX = ref(0)
|
||||
const startY = ref(0)
|
||||
|
||||
// 开始拖拽
|
||||
const startDrag = (e) => {
|
||||
if (!canDrag()) return // 如果不需要拖拽,直接返回
|
||||
|
|
@ -215,9 +233,6 @@ const startDrag = (e) => {
|
|||
document.addEventListener('mouseup', stopDrag)
|
||||
}
|
||||
|
||||
const maxOffsetX1 = ref(0)
|
||||
const maxOffsetY1 = ref(0)
|
||||
|
||||
// 处理拖拽
|
||||
const handleDrag = (e) => {
|
||||
if (!isDragging.value) return
|
||||
|
|
@ -256,13 +271,7 @@ const stopDrag = () => {
|
|||
document.removeEventListener('mouseup', stopDrag)
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
presetSettingVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 是否可以拖拽
|
||||
const canDrag = () => {
|
||||
const parentWidth = planeMapContainer.value?.clientWidth || 0
|
||||
const parentHeight = planeMapContainer.value?.clientHeight || 0
|
||||
|
|
@ -279,18 +288,8 @@ const getMaxOffset = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 获取逻辑坐标
|
||||
const getLogicalPosition = (clientX, clientY) => {
|
||||
// const svgRect = svgMapRef.value.getBoundingClientRect()
|
||||
|
||||
// // 计算相对于SVG内容的逻辑坐标(考虑缩放和偏移)
|
||||
// const logicalX = (clientX - svgRect.left - offsetX.value) / scale.value
|
||||
// const logicalY = (clientY - svgRect.top - offsetY.value) / scale.value
|
||||
|
||||
// return {
|
||||
// x: Math.max(0, Math.min(INITIAL_WIDTH, logicalX)),
|
||||
// y: Math.max(0, Math.min(INITIAL_HEIGHT, logicalY)),
|
||||
// }
|
||||
|
||||
const svgRect = svgMapRef.value.getBoundingClientRect()
|
||||
|
||||
// 计算相对于SVG内容的逻辑坐标(考虑缩放和偏移)
|
||||
|
|
@ -302,9 +301,9 @@ const getLogicalPosition = (clientX, clientY) => {
|
|||
const logicalY1 = (clientY - svgRect.top - offsetY.value) / scale.value
|
||||
|
||||
return {
|
||||
x: Math.max(0, Math.min(INITIAL_WIDTH, logicalX)),
|
||||
y: Math.max(0, Math.min(INITIAL_HEIGHT, logicalY)),
|
||||
y1: Math.max(0, Math.min(INITIAL_HEIGHT, logicalY1)),
|
||||
x: Math.max(0, Math.min(INITIAL_WIDTH.value, logicalX)),
|
||||
y: Math.max(0, Math.min(INITIAL_HEIGHT.value, logicalY)),
|
||||
y1: Math.max(0, Math.min(INITIAL_HEIGHT.value, logicalY1)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -407,9 +406,15 @@ const handleModifyPoint = () => {
|
|||
// 这里可以添加修改逻辑,比如弹出对话框
|
||||
const markerInfo = {
|
||||
type: '修改',
|
||||
xCount: point.xCount,
|
||||
yCount: point.yCount,
|
||||
markerIndex: selectedPointIndex.value,
|
||||
markerX: point.x,
|
||||
markerY: point.y,
|
||||
markerX: point.markerX,
|
||||
markerY: point.markerY,
|
||||
markerY1: point.markerY1,
|
||||
markerName: point.markerName,
|
||||
markerAngle: point.markerAngle,
|
||||
markerPreset: point.markerPreset,
|
||||
}
|
||||
emits('onHandleAddMarker', markerInfo)
|
||||
}
|
||||
|
|
@ -424,23 +429,19 @@ const handleDeletePoint = () => {
|
|||
closeContextMenu()
|
||||
}
|
||||
|
||||
// 监听点击事件以关闭菜单
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutsideMenu)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleClickOutsideMenu)
|
||||
})
|
||||
|
||||
// 添加设备点位
|
||||
const addDevicePoint = (x, y, y1) => {
|
||||
if (isPointClickStatus.value) return
|
||||
devicePoints.value.push({
|
||||
x: x, // 直接存储逻辑坐标
|
||||
y: y, // 直接存储逻辑坐标
|
||||
y1: y1, // 直接存储逻辑坐标
|
||||
xCount: x * 2, // 像素坐标 真实数据
|
||||
yCount: y * 2, // 像素坐标 真实数据
|
||||
markerX: x, // 直接存储逻辑坐标
|
||||
markerY: y, // 直接存储逻辑坐标
|
||||
markerY1: y1, // 直接存储逻辑坐标
|
||||
id: Date.now(),
|
||||
markerName: '',
|
||||
markerAngle: '', // 角度
|
||||
markerPreset: '', // 摄像头预置位
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -448,9 +449,15 @@ const addDevicePoint = (x, y, y1) => {
|
|||
const handlePointClick = (point, index) => {
|
||||
const markerInfo = {
|
||||
type: '新增',
|
||||
xCount: point.xCount, // 像素坐标 真实数据
|
||||
yCount: point.yCount, // 像素坐标 真实数据
|
||||
markerIndex: index,
|
||||
markerX: point.x,
|
||||
markerY: point.y,
|
||||
markerX: point.markerX,
|
||||
markerY: point.markerY,
|
||||
markerY1: point.markerY1,
|
||||
markerName: point.markerName,
|
||||
markerAngle: point.markerAngle,
|
||||
markerPreset: point.markerPreset,
|
||||
}
|
||||
emits('onHandleAddMarker', markerInfo)
|
||||
}
|
||||
|
|
@ -460,16 +467,65 @@ const onHandleCloseModal = () => {
|
|||
emits('onHandleCloseModal')
|
||||
}
|
||||
|
||||
// 关闭新增弹框
|
||||
const onHandleCloseAddModal = () => {
|
||||
showAddModal.value = false
|
||||
}
|
||||
// 监听点击事件以关闭菜单
|
||||
onMounted(async () => {
|
||||
document.addEventListener('click', handleClickOutsideMenu)
|
||||
const svg = svgMapRef.value
|
||||
svg.addEventListener('wheel', handleWheel, { passive: false }) // 明确声明
|
||||
|
||||
const deviceInfo = await getRobotDeviceListFn()
|
||||
// 获取地图信息
|
||||
mapInfo.value = await getRobotMapInfoFn(deviceInfo?.puId)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleClickOutsideMenu)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.markerInfoNew,
|
||||
(newVal) => {
|
||||
// 判断是否为空对象
|
||||
if (Object.keys(newVal).length > 0) {
|
||||
const svgRect = svgMapRef.value.getBoundingClientRect()
|
||||
const clientY =
|
||||
svgHeight.value - scale.value * (newVal.yCount / 2) + svgRect.top + offsetY.value
|
||||
const logicalY1 = (clientY - svgRect.top - offsetY.value) / scale.value
|
||||
devicePoints.value[newVal.markerIndex].markerX = newVal.xCount / 2
|
||||
devicePoints.value[newVal.markerIndex].markerY = newVal.markerY
|
||||
devicePoints.value[newVal.markerIndex].markerY1 = logicalY1
|
||||
devicePoints.value[newVal.markerIndex].markerName = newVal.markerName
|
||||
devicePoints.value[newVal.markerIndex].markerAngle = newVal.markerAngle
|
||||
devicePoints.value[newVal.markerIndex].markerPreset = newVal.markerPreset
|
||||
devicePoints.value[newVal.markerIndex].xCount = newVal.xCount
|
||||
devicePoints.value[newVal.markerIndex].yCount = newVal.yCount
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
// 监听地图信息
|
||||
() => mapInfo.value,
|
||||
(newVal) => {
|
||||
if (newVal.mapWidth && newVal.mapHeight) {
|
||||
INITIAL_WIDTH.value = Math.ceil(newVal.mapWidth / 2)
|
||||
INITIAL_HEIGHT.value = Math.ceil(newVal.mapHeight / 2)
|
||||
svgWidth.value = INITIAL_WIDTH.value
|
||||
svgHeight.value = INITIAL_HEIGHT.value
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plane-map-container {
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
// height: 80vh;
|
||||
max-height: 85vh;
|
||||
overflow: auto;
|
||||
// 优化横向和竖向滚动条样式
|
||||
&::-webkit-scrollbar {
|
||||
|
|
|
|||
|
|
@ -1,258 +0,0 @@
|
|||
<template>
|
||||
<!-- 预置位配置 -->
|
||||
<DialogModal @onHandleCloseModal="onHandleCloseModal" :modalTitle="modalTitle" :height="`90vh`">
|
||||
<!-- 平面图操作区域 -->
|
||||
<div class="plane-map-container" ref="planeMapContainer">
|
||||
<svg
|
||||
ref="svgMap"
|
||||
class="floor-plan"
|
||||
@click="handleMapClick"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
:viewBox="`${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`"
|
||||
>
|
||||
<!-- 平面图背景 -->
|
||||
<image
|
||||
v-if="imgLoaded"
|
||||
:href="demoImg"
|
||||
:width="viewBox.width"
|
||||
:height="viewBox.height"
|
||||
preserveAspectRatio="xMidYMid slice"
|
||||
style="object-fit: cover"
|
||||
/>
|
||||
|
||||
<!-- 动态渲染标记点 -->
|
||||
<circle
|
||||
v-for="(point, index) in points"
|
||||
:key="index"
|
||||
:cx="point.x"
|
||||
:cy="point.y"
|
||||
r="8"
|
||||
fill="red"
|
||||
@click.stop="selectPoint(index)"
|
||||
@mousedown="startDrag(index, $event)"
|
||||
/>
|
||||
|
||||
<!-- 动态渲染连线 -->
|
||||
<line
|
||||
v-for="(line, index) in lines"
|
||||
:key="'line-' + index"
|
||||
:x1="line.start.x"
|
||||
:y1="line.start.y"
|
||||
:x2="line.end.x"
|
||||
:y2="line.end.y"
|
||||
stroke="blue"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="map-control-container">
|
||||
<n-button type="info" @click="handleZoomIn">放大</n-button>
|
||||
<n-button type="info" @click="handleZoomOut">缩小</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { refThrottled, useMouseInElement } from '@vueuse/core'
|
||||
import { ref, onMounted, nextTick, watch } from 'vue'
|
||||
// import { CashOutline as CashIcon } from '@vicons/ionicons5'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import demoImg from '@/assets/demo.png'
|
||||
|
||||
const emits = defineEmits(['onHandleCloseModal'])
|
||||
const modalTitle = ref('预置位配置')
|
||||
const svgMap = refThrottled(null)
|
||||
const points = ref([])
|
||||
const lines = ref([])
|
||||
const imgLoaded = ref(false)
|
||||
const imgNaturalSize = ref({ width: 0, height: 0 })
|
||||
|
||||
const props = defineProps({
|
||||
presetSettingVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 新增:响应式 viewBox 控制
|
||||
const viewBox = ref({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
|
||||
// 新增:容器尺寸跟踪
|
||||
const containerSize = ref({ width: 0, height: 0 })
|
||||
const planeMapContainer = ref(null)
|
||||
|
||||
// 关闭模态框
|
||||
const onHandleCloseModal = () => {
|
||||
emits('onHandleCloseModal')
|
||||
}
|
||||
|
||||
// 拖拽标记点
|
||||
const startDrag = (index, e) => {
|
||||
e.preventDefault()
|
||||
const { x: startX, y: startY } = points.value[index]
|
||||
const onMove = (moveEvent) => {
|
||||
const rect = svgMap.value.getBoundingClientRect()
|
||||
points.value[index] = {
|
||||
x: moveEvent.clientX - rect.left,
|
||||
y: moveEvent.clientY - rect.top,
|
||||
}
|
||||
}
|
||||
const onUp = () => {
|
||||
window.removeEventListener('mousemove', onMove)
|
||||
window.removeEventListener('mouseup', onUp)
|
||||
}
|
||||
window.addEventListener('mousemove', onMove)
|
||||
window.addEventListener('mouseup', onUp)
|
||||
}
|
||||
|
||||
// 连线示例(假设连接第一个和第二个点)
|
||||
const connectPoints = () => {
|
||||
if (points.value.length >= 2) {
|
||||
lines.value.push({
|
||||
start: points.value[0],
|
||||
end: points.value[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 监听容器尺寸变化
|
||||
nextTick(() => {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
if (entries[0]) {
|
||||
containerSize.value = {
|
||||
width: entries[0].contentRect.width,
|
||||
height: entries[0].contentRect.height,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (planeMapContainer.value) {
|
||||
resizeObserver.observe(planeMapContainer.value)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 修改点击事件处理(考虑缩放后的坐标)
|
||||
const handleMapClick = (e) => {
|
||||
if (!svgMap.value) return
|
||||
|
||||
const pt = svgMap.value.createSVGPoint()
|
||||
pt.x = e.clientX
|
||||
pt.y = e.clientY
|
||||
const svgPt = pt.matrixTransform(svgMap.value.getScreenCTM().inverse())
|
||||
|
||||
points.value.push({
|
||||
x: svgPt.x,
|
||||
y: svgPt.y,
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.presetSettingVisible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
loadImage()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
const initMapSize = () => {
|
||||
if (!planeMapContainer.value) return
|
||||
|
||||
containerSize.value = {
|
||||
width: planeMapContainer.value.clientWidth,
|
||||
height: planeMapContainer.value.clientHeight,
|
||||
}
|
||||
|
||||
// 初始化 viewBox(根据您的业务逻辑调整)
|
||||
viewBox.value.width = containerSize.value.width
|
||||
viewBox.value.height = containerSize.value.height
|
||||
}
|
||||
|
||||
// 加载图片并获取真实尺寸
|
||||
const loadImage = () => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
imgNaturalSize.value = {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
}
|
||||
imgLoaded.value = true
|
||||
adjustViewBox()
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.error('图片加载失败,请检查路径:', demoImg)
|
||||
}
|
||||
img.src = demoImg // 使用导入的图片路径
|
||||
}
|
||||
|
||||
// 调整viewBox
|
||||
const adjustViewBox = () => {
|
||||
if (!planeMapContainer.value || !imgNaturalSize.value.width) return
|
||||
|
||||
const container = planeMapContainer.value
|
||||
const containerRatio = container.clientWidth / container.clientHeight
|
||||
const imgRatio = imgNaturalSize.value.width / imgNaturalSize.value.height
|
||||
|
||||
if (containerRatio > imgRatio) {
|
||||
// 容器更宽,按高度适配
|
||||
viewBox.value = {
|
||||
x: (imgNaturalSize.value.width - imgNaturalSize.value.height * containerRatio) / 2,
|
||||
y: 0,
|
||||
width: imgNaturalSize.value.height * containerRatio,
|
||||
height: imgNaturalSize.value.height,
|
||||
}
|
||||
} else {
|
||||
// 容器更高,按宽度适配
|
||||
viewBox.value = {
|
||||
x: 0,
|
||||
y: (imgNaturalSize.value.height - imgNaturalSize.value.width / containerRatio) / 2,
|
||||
width: imgNaturalSize.value.width,
|
||||
height: imgNaturalSize.value.width / containerRatio,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 放大
|
||||
const handleZoomIn = () => {
|
||||
viewBox.value.width = viewBox.value.width * 1.1
|
||||
viewBox.value.height = viewBox.value.height * 1.1
|
||||
}
|
||||
|
||||
// 缩小
|
||||
const handleZoomOut = () => {
|
||||
viewBox.value.width = viewBox.value.width * 0.9
|
||||
viewBox.value.height = viewBox.value.height * 0.9
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plane-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.map-control-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
<template>
|
||||
<!-- 预置位配置 -->
|
||||
<DialogModal @onHandleCloseModal="onHandleCloseModal" :modalTitle="modalTitle" :height="`90vh`">
|
||||
<!-- 平面图操作区域 -->
|
||||
<div class="plane-map-container"></div>
|
||||
</DialogModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DialogModal from '@/components/DialogModal/index.vue'
|
||||
import { refThrottled, useMouseInElement } from '@vueuse/core'
|
||||
import { ref, onMounted, nextTick, watch } from 'vue'
|
||||
// import { CashOutline as CashIcon } from '@vicons/ionicons5'
|
||||
import { NIcon } from 'naive-ui'
|
||||
import demoImg from '@/assets/demo.png'
|
||||
|
||||
const emits = defineEmits(['onHandleCloseModal'])
|
||||
const modalTitle = ref('预置位配置')
|
||||
const svgMap = refThrottled(null)
|
||||
const points = ref([])
|
||||
const lines = ref([])
|
||||
const imgLoaded = ref(false)
|
||||
const imgNaturalSize = ref({ width: 0, height: 0 })
|
||||
|
||||
const props = defineProps({
|
||||
presetSettingVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 新增:响应式 viewBox 控制
|
||||
const viewBox = ref({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
|
||||
// 新增:容器尺寸跟踪
|
||||
const containerSize = ref({ width: 0, height: 0 })
|
||||
const planeMapContainer = ref(null)
|
||||
|
||||
// 关闭模态框
|
||||
const onHandleCloseModal = () => {
|
||||
emits('onHandleCloseModal')
|
||||
}
|
||||
|
||||
// 拖拽标记点
|
||||
const startDrag = (index, e) => {
|
||||
e.preventDefault()
|
||||
const { x: startX, y: startY } = points.value[index]
|
||||
const onMove = (moveEvent) => {
|
||||
const rect = svgMap.value.getBoundingClientRect()
|
||||
points.value[index] = {
|
||||
x: moveEvent.clientX - rect.left,
|
||||
y: moveEvent.clientY - rect.top,
|
||||
}
|
||||
}
|
||||
const onUp = () => {
|
||||
window.removeEventListener('mousemove', onMove)
|
||||
window.removeEventListener('mouseup', onUp)
|
||||
}
|
||||
window.addEventListener('mousemove', onMove)
|
||||
window.addEventListener('mouseup', onUp)
|
||||
}
|
||||
|
||||
// 连线示例(假设连接第一个和第二个点)
|
||||
const connectPoints = () => {
|
||||
if (points.value.length >= 2) {
|
||||
lines.value.push({
|
||||
start: points.value[0],
|
||||
end: points.value[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 监听容器尺寸变化
|
||||
nextTick(() => {
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
if (entries[0]) {
|
||||
containerSize.value = {
|
||||
width: entries[0].contentRect.width,
|
||||
height: entries[0].contentRect.height,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (planeMapContainer.value) {
|
||||
resizeObserver.observe(planeMapContainer.value)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 修改点击事件处理(考虑缩放后的坐标)
|
||||
const handleMapClick = (e) => {
|
||||
if (!svgMap.value) return
|
||||
|
||||
const pt = svgMap.value.createSVGPoint()
|
||||
pt.x = e.clientX
|
||||
pt.y = e.clientY
|
||||
const svgPt = pt.matrixTransform(svgMap.value.getScreenCTM().inverse())
|
||||
|
||||
points.value.push({
|
||||
x: svgPt.x,
|
||||
y: svgPt.y,
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.presetSettingVisible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
loadImage()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
const initMapSize = () => {
|
||||
if (!planeMapContainer.value) return
|
||||
|
||||
containerSize.value = {
|
||||
width: planeMapContainer.value.clientWidth,
|
||||
height: planeMapContainer.value.clientHeight,
|
||||
}
|
||||
|
||||
// 初始化 viewBox(根据您的业务逻辑调整)
|
||||
viewBox.value.width = containerSize.value.width
|
||||
viewBox.value.height = containerSize.value.height
|
||||
}
|
||||
|
||||
// 加载图片并获取真实尺寸
|
||||
const loadImage = () => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
imgNaturalSize.value = {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
}
|
||||
imgLoaded.value = true
|
||||
adjustViewBox()
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.error('图片加载失败,请检查路径:', demoImg)
|
||||
}
|
||||
img.src = demoImg // 使用导入的图片路径
|
||||
}
|
||||
|
||||
// 调整viewBox
|
||||
const adjustViewBox = () => {
|
||||
if (!planeMapContainer.value || !imgNaturalSize.value.width) return
|
||||
|
||||
const container = planeMapContainer.value
|
||||
const containerRatio = container.clientWidth / container.clientHeight
|
||||
const imgRatio = imgNaturalSize.value.width / imgNaturalSize.value.height
|
||||
|
||||
if (containerRatio > imgRatio) {
|
||||
// 容器更宽,按高度适配
|
||||
viewBox.value = {
|
||||
x: (imgNaturalSize.value.width - imgNaturalSize.value.height * containerRatio) / 2,
|
||||
y: 0,
|
||||
width: imgNaturalSize.value.height * containerRatio,
|
||||
height: imgNaturalSize.value.height,
|
||||
}
|
||||
} else {
|
||||
// 容器更高,按宽度适配
|
||||
viewBox.value = {
|
||||
x: 0,
|
||||
y: (imgNaturalSize.value.height - imgNaturalSize.value.width / containerRatio) / 2,
|
||||
width: imgNaturalSize.value.width,
|
||||
height: imgNaturalSize.value.width / containerRatio,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleZoomIn = () => {
|
||||
console.log('放大')
|
||||
viewBox.value.width = viewBox.value.width * 1.1
|
||||
viewBox.value.height = viewBox.value.height * 1.1
|
||||
}
|
||||
|
||||
const handleZoomOut = () => {
|
||||
console.log('缩小')
|
||||
viewBox.value.width = viewBox.value.width * 0.9
|
||||
viewBox.value.height = viewBox.value.height * 0.9
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plane-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background-color: pink;
|
||||
|
||||
// .map-control-container {
|
||||
// position: absolute;
|
||||
// top: 10px;
|
||||
// right: 10px;
|
||||
// z-index: 1000;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// gap: 10px;
|
||||
// }
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="viewport-container">
|
||||
<!-- 非全屏状态 -->
|
||||
<div ref="appRef" class="app-container">
|
||||
<!-- 机器人首页 -->
|
||||
|
|
@ -9,52 +9,52 @@
|
|||
<!-- 左一 -->
|
||||
<div
|
||||
:style="{
|
||||
gridColumn: fullScreenVisible ? '1 / 9' : '1 / 4',
|
||||
gridRow: fullScreenVisible ? '1 / 13' : '1 / 7',
|
||||
gridColumn: fullScreenVisibleNew ? '1 / 9' : '1 / 4',
|
||||
gridRow: fullScreenVisibleNew ? '1 / 13' : '1 / 7',
|
||||
}"
|
||||
>
|
||||
<LeftOne
|
||||
:videoId="'video-1'"
|
||||
:cameraNode="cameraNode_1"
|
||||
:fullScreenVisible="fullScreenVisible"
|
||||
:fullScreenVisible="fullScreenVisibleNew"
|
||||
@onHandleChangeView="onHandleChangeView"
|
||||
@onHandleOperationPanel="onHandleOperationPanel"
|
||||
@onHandleFullScreenToggle="onHandleFullScreenToggleL"
|
||||
@onHandleFullScreenToggle="onHandleFullScreenToggle"
|
||||
/>
|
||||
</div>
|
||||
<!-- 左二 -->
|
||||
<div class="robot-2" v-if="!fullScreenVisible">
|
||||
<div class="robot-2" v-if="!fullScreenVisibleNew">
|
||||
<LeftTwo />
|
||||
</div>
|
||||
|
||||
<!-- 中一 -->
|
||||
<div
|
||||
:style="{
|
||||
gridColumn: fullScreenVisible ? '9 / 13' : '4 / 10',
|
||||
gridRow: fullScreenVisible ? '1 / 6' : '1 / 8',
|
||||
gridColumn: fullScreenVisibleNew ? '9 / 13' : '4 / 10',
|
||||
gridRow: fullScreenVisibleNew ? '1 / 6' : '1 / 8',
|
||||
}"
|
||||
>
|
||||
<CenterOne
|
||||
:videoId="'video-2'"
|
||||
:cameraNode_2="cameraNode_2"
|
||||
:fullScreenVisible="fullScreenVisible"
|
||||
:fullScreenVisible="fullScreenVisibleNew"
|
||||
/>
|
||||
</div>
|
||||
<!-- 中二 -->
|
||||
<div class="robot-4" v-if="!fullScreenVisible">
|
||||
<div class="robot-4" v-if="!fullScreenVisibleNew">
|
||||
<CenterTwo />
|
||||
</div>
|
||||
|
||||
<!-- 右一 -->
|
||||
<div class="robot-5" v-if="!fullScreenVisible">
|
||||
<div class="robot-5" v-if="!fullScreenVisibleNew">
|
||||
<RightOne />
|
||||
</div>
|
||||
<!-- 右二 -->
|
||||
<div class="robot-6" v-if="!fullScreenVisible">
|
||||
<div class="robot-6" v-if="!fullScreenVisibleNew">
|
||||
<RightTwo />
|
||||
</div>
|
||||
|
||||
<div class="robot-7" v-if="fullScreenVisible">
|
||||
<div class="robot-7" v-if="fullScreenVisibleNew">
|
||||
<ControlDeck />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -77,28 +77,28 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { initLoginApi, getDeviceUrlApi } from '@/utils/initLogin'
|
||||
import { getTokenApi, getDeviceInfoApi } from '@/api/home'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getDeviceUrlApi } from '@/utils/initLogin'
|
||||
import { getTokenApi } from '@/api/home'
|
||||
import { useScale } from '@/hooks/useScale' // 引入自定义 Hook
|
||||
import { userDataStore } from '@/store/user'
|
||||
import LeftOne from './components/left-one.vue'
|
||||
import LeftTwo from './components/left-two.vue'
|
||||
import CenterOne from './components/center-one.vue'
|
||||
import CenterTwo from './components/center-two.vue'
|
||||
import RightOne from './components/right-one.vue'
|
||||
import RightTwo from './components/right-two/index.vue'
|
||||
import ControlDeck from './components/control-deck.vue'
|
||||
import { userDataStore } from '@/store/user' // 用户数据
|
||||
import LeftOne from './components/left-one.vue' // 左一
|
||||
import LeftTwo from './components/left-two.vue' // 左二
|
||||
import CenterOne from './components/center-one.vue' // 中一
|
||||
import CenterTwo from './components/center-two.vue' // 中二
|
||||
import RightOne from './components/right-one.vue' // 右一
|
||||
import RightTwo from './components/right-two/index.vue' // 右二
|
||||
import ControlDeck from './components/control-deck.vue' // 控制台
|
||||
|
||||
import { getRobotTokenFn, getRobotDeviceListFn } from '@/utils/getRobotInfo' // 获取机器人信息
|
||||
|
||||
const userData = userDataStore()
|
||||
const appRef = ref(null) // 获取 DOM 引用
|
||||
const fullScreenVisible = ref(false) // 全屏状态
|
||||
|
||||
// 使用 useScale Hook(直接调用,无需在 setup() 里)
|
||||
const fullScreenVisibleNew = ref(false) // 全屏状态
|
||||
const { baseWidth, baseHeight, scale } = useScale(appRef)
|
||||
|
||||
const cameraNode_1 = ref({
|
||||
puid: '201115200268437643',
|
||||
puid: '',
|
||||
idx: 0,
|
||||
stream: 0,
|
||||
name: '',
|
||||
|
|
@ -107,7 +107,7 @@ const cameraNode_1 = ref({
|
|||
steamURL: '',
|
||||
})
|
||||
const cameraNode_2 = ref({
|
||||
puid: '201115200268437643',
|
||||
puid: '',
|
||||
idx: 1,
|
||||
stream: 0,
|
||||
name: '',
|
||||
|
|
@ -123,8 +123,8 @@ const onHandleOperationPanel = (visible) => {
|
|||
operationPanelVisible.value = visible
|
||||
}
|
||||
// 全屏控制
|
||||
const onHandleFullScreenToggleL = (visible) => {
|
||||
fullScreenVisible.value = visible
|
||||
const onHandleFullScreenToggle = (visible) => {
|
||||
fullScreenVisibleNew.value = visible
|
||||
}
|
||||
// 视角切换
|
||||
const onHandleChangeView = () => {
|
||||
|
|
@ -136,43 +136,17 @@ const onHandleChangeView = () => {
|
|||
cameraNode_2.value.steamURL = temp
|
||||
}
|
||||
|
||||
const loginParams = ref({
|
||||
// - 登录平台IP
|
||||
address: '60.168.132.97',
|
||||
port: '49988',
|
||||
// - 登录平台用户名
|
||||
user: 'admin',
|
||||
// - 登录平台密码
|
||||
password: 'Aa123456',
|
||||
// - 登录平台企业ID
|
||||
epid: 'YDJFXK',
|
||||
// - 登录平台是否通过网闸模式
|
||||
bfix: 0,
|
||||
})
|
||||
|
||||
// 初始化登录
|
||||
const initLoginData = async () => {
|
||||
initLoginApi(loginParams.value).then((res) => {
|
||||
console.log(res, 'res设备---')
|
||||
|
||||
// 存储token 平台设备使用
|
||||
userData.setDeviceToken(res?.data?.token)
|
||||
|
||||
// getDeviceData()
|
||||
})
|
||||
}
|
||||
|
||||
// 获取视频地址
|
||||
const getDeviceData = async (puId) => {
|
||||
// // 获取视频地址
|
||||
const getDeviceData = async (puId, deviceToken) => {
|
||||
const { data: res } = await getDeviceUrlApi({
|
||||
token: userData?.deviceToken,
|
||||
token: deviceToken,
|
||||
puid: puId,
|
||||
idx: 0,
|
||||
stream: 0,
|
||||
type: 'HTTP-FLV',
|
||||
})
|
||||
const { data: res2 } = await getDeviceUrlApi({
|
||||
token: userData?.deviceToken,
|
||||
token: deviceToken,
|
||||
puid: puId,
|
||||
idx: 1,
|
||||
stream: 0,
|
||||
|
|
@ -183,40 +157,27 @@ const getDeviceData = async (puId) => {
|
|||
cameraNode_2.value.steamURL = res2?.streamUrl
|
||||
}
|
||||
|
||||
initLoginData()
|
||||
|
||||
// 获取token
|
||||
const getTokenData = async () => {
|
||||
const { data: res } = await getTokenApi({
|
||||
getTokenApi({
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
})
|
||||
// console.log(res, 'token')
|
||||
|
||||
// 存储token 内部使用
|
||||
userData.setUserInfo({
|
||||
token: res?.token,
|
||||
}).then((res) => {
|
||||
// 存储token 内部使用
|
||||
userData.setUserInfo({
|
||||
token: res?.data?.token,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getTokenData()
|
||||
getTokenData() // 获取token
|
||||
|
||||
// 获取设备列表
|
||||
const getDeviceInfoData = async () => {
|
||||
const { data: res } = await getDeviceInfoApi()
|
||||
console.log(res, 'deviceInfo')
|
||||
onMounted(async () => {
|
||||
const deviceToken = await getRobotTokenFn() // 获取设备token
|
||||
const deviceInfo = await getRobotDeviceListFn() // 获取设备信息
|
||||
|
||||
// 设备获取成功
|
||||
if (res?.data && res?.data?.length > 0) {
|
||||
const deviceInfo = res.data[0]
|
||||
cameraNode_1.value.name = deviceInfo.devName
|
||||
cameraNode_2.value.name = deviceInfo.devName
|
||||
|
||||
getDeviceData(deviceInfo.puId)
|
||||
}
|
||||
}
|
||||
|
||||
getDeviceInfoData()
|
||||
getDeviceData(deviceInfo.puId, deviceToken) // 获取Url
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
Loading…
Reference in New Issue