Vue3纯前端同源跨窗口通信移动AGV小车

发布于:2025-09-11 ⋅ 阅读:(16) ⋅ 点赞:(0)

在这里插入图片描述
1.右侧控制页面

<template>
  <div class="point-command">
    <h3>AGV小车模拟仿真</h3>
    <div class="point-container">
      <el-button
        v-for="n in 22"
        :key="n"
        :class="{ 'active-point': activePoint === n }"
        type="primary"
        circle
        @click="handlePointClick(n)"
        >{{ n }}</el-button
      >
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

const channel = ref(null)
const activePoint = ref(null)

const handlePointClick = (pointNumber) => {
  // 发送消息
  channel.value.postMessage({
    type: "POINT_SELECTED",
    pointId: pointNumber,
    timestamp: Date.now(),
  })

  // 按钮动画效果
  activePoint.value = pointNumber
  setTimeout(() => {
    activePoint.value = null
  }, 300)
}

onMounted(() => {
  channel.value = new BroadcastChannel("agv-channel")
})

onBeforeUnmount(() => {
  channel.value?.close()
})
</script>

<style scoped>
.point-command {
  padding: 30px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  max-width: 800px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

h3 {
  color: #2c3e50;
  font-size: 24px;
  margin-bottom: 25px;
  font-weight: 600;
}

.point-container {
  width: 100%;
  background: #f8f9fa;
  border-radius: 8px;
  padding: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}

.el-button.is-circle {
  width: 60px;
  height: 60px;
  font-size: 20px;
  font-weight: 600;
  transition: all 0.3s ease;
  margin: 0;
}

.el-button.is-circle:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
}

/* 添加按钮激活效果 */
.active-point {
  transform: scale(1.15);
  box-shadow: 0 0 15px rgba(64, 158, 255, 0.4);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

2.左侧小车移动页面

<template>
  <div class="home-page">
    <div class="path-container">
      <div class="center-line"></div>
      <div 
        v-for="point in points" 
        :key="point.id"
        class="point"
        :style="{ left: point.x + '%', top: point.y + '%' }"
      >
        <span class="point-number">{{ point.id }}</span>
      </div>
      <div 
        class="moving-box"
        :style="{ left: currentPosition.x + '%', top: currentPosition.y + '%' }"
      ></div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

const channel = ref(null)
const points = ref([
  // 上边框
  { id: 1, x: 5, y: 0 },
  { id: 2, x: 15, y: 0 },
  { id: 3, x: 25, y: 0 },
  { id: 4, x: 35, y: 0 },
  { id: 5, x: 45, y: 0 },
  // 中间线
  { id: 6, x: 50, y: 10 },
  { id: 7, x: 50, y: 25 },
  { id: 8, x: 50, y: 40 },
  { id: 9, x: 50, y: 55 },
  { id: 10, x: 50, y: 70 },
  { id: 11, x: 50, y: 85 },
  // 右边框
  { id: 12, x: 100, y: 15 },
  { id: 13, x: 100, y: 30 },
  { id: 14, x: 100, y: 45 },
  { id: 15, x: 100, y: 60 },
  { id: 16, x: 100, y: 75 },
  { id: 17, x: 100, y: 90 },
  // 下边框
  { id: 18, x: 85, y: 100 },
  { id: 19, x: 70, y: 100 },
  { id: 20, x: 55, y: 100 },
  { id: 21, x: 40, y: 100 },
  { id: 22, x: 25, y: 100 }
])
const currentPosition = ref({
  x: 5,
  y: 0
})

const handleChannelMessage = (event) => {
  if (event.data.type === "POINT_SELECTED") {
    moveToPoint(event.data.pointId)
  }
}

const moveToPoint = (pointId) => {
  const targetPoint = points.value.find(p => p.id === pointId)
  if (targetPoint) {
    moveAlongPath(targetPoint)
  }
}

// 计算并执行路径移动 - 沿着边线和中心线移动
const moveAlongPath = (targetPoint) => {
  const current = { ...currentPosition.value }
  const target = { ...targetPoint }
  
  // 如果已经在目标位置,直接返回
  if (current.x === target.x && current.y === target.y) {
    return
  }
  
  // 计算路径
  const path = calculatePath(current, target)
  
  // 执行路径移动
  executePath(path)
}

// 计算沿边线的路径
const calculatePath = (start, end) => {
  const path = []
  
  // 定义关键路径点
  const pathNodes = {
    // 上边框节点
    topBorder: (x) => ({ x, y: 0 }),
    // 右边框节点  
    rightBorder: (y) => ({ x: 100, y }),
    // 下边框节点
    bottomBorder: (x) => ({ x, y: 100 }),
    // 左边框节点
    leftBorder: (y) => ({ x: 0, y }),
    // 中心线节点
    centerLine: (y) => ({ x: 50, y })
  }
  
  // 判断点在哪条线上
  const getLineType = (point) => {
    if (point.y === 0) return 'top'
    if (point.x === 100) return 'right'
    if (point.y === 100) return 'bottom'
    if (point.x === 0) return 'left'
    if (point.x === 50) return 'center'
    return 'unknown'
  }
  
  const startLine = getLineType(start)
  const endLine = getLineType(end)
  
  // 如果在同一条线上,直接移动
  if (startLine === endLine) {
    path.push(end)
    return path
  }
  
  // 不同线之间的移动策略
  if (startLine === 'top') {
    if (endLine === 'center') {
      // 从上边框到中心线:先到(50,0)再到目标
      path.push({ x: 50, y: 0 })
      path.push(end)
    } else if (endLine === 'right') {
      // 从上边框到右边框:先到(100,0)再到目标
      path.push({ x: 100, y: 0 })
      path.push(end)
    } else if (endLine === 'bottom') {
      // 从上边框到下边框:通过中心线
      path.push({ x: 50, y: 0 })
      path.push({ x: 50, y: 100 })
      path.push(end)
    }
  } else if (startLine === 'center') {
    if (endLine === 'top') {
      // 从中心线到上边框:先到(50,0)再到目标
      path.push({ x: 50, y: 0 })
      path.push(end)
    } else if (endLine === 'right') {
      // 从中心线到右边框:找最近的转折点
      if (start.y <= 50) {
        // 上半部分:通过上边框
        path.push({ x: 50, y: 0 })
        path.push({ x: 100, y: 0 })
        path.push(end)
      } else {
        // 下半部分:通过下边框
        path.push({ x: 50, y: 100 })
        path.push({ x: 100, y: 100 })
        path.push(end)
      }
    } else if (endLine === 'bottom') {
      // 从中心线到下边框:先到(50,100)再到目标
      path.push({ x: 50, y: 100 })
      path.push(end)
    }
  } else if (startLine === 'right') {
    if (endLine === 'top') {
      // 从右边框到上边框:先到(100,0)再到目标
      path.push({ x: 100, y: 0 })
      path.push(end)
    } else if (endLine === 'center') {
      // 从右边框到中心线:找最近的路径
      if (start.y <= 50) {
        // 上半部分:通过上边框
        path.push({ x: 100, y: 0 })
        path.push({ x: 50, y: 0 })
        path.push(end)
      } else {
        // 下半部分:通过下边框
        path.push({ x: 100, y: 100 })
        path.push({ x: 50, y: 100 })
        path.push(end)
      }
    } else if (endLine === 'bottom') {
      // 从右边框到下边框:先到(100,100)再到目标
      path.push({ x: 100, y: 100 })
      path.push(end)
    }
  } else if (startLine === 'bottom') {
    if (endLine === 'center') {
      // 从下边框到中心线:先到(50,100)再到目标
      path.push({ x: 50, y: 100 })
      path.push(end)
    } else if (endLine === 'right') {
      // 从下边框到右边框:先到(100,100)再到目标
      path.push({ x: 100, y: 100 })
      path.push(end)
    } else if (endLine === 'top') {
      // 从下边框到上边框:通过中心线
      path.push({ x: 50, y: 100 })
      path.push({ x: 50, y: 0 })
      path.push(end)
    }
  }
  
  return path
}

// 执行路径移动
const executePath = (path) => {
  if (path.length === 0) return
  
  let currentIndex = 0
  
  const moveToNext = () => {
    if (currentIndex < path.length) {
      currentPosition.value = { ...path[currentIndex] }
      currentIndex++
      
      // 如果还有下一个点,延迟后继续移动
      if (currentIndex < path.length) {
        setTimeout(moveToNext, 600) // 0.6秒间隔
      }
    }
  }
  
  moveToNext()
}

onMounted(() => {
  channel.value = new BroadcastChannel("agv-channel")
  channel.value.addEventListener("message", handleChannelMessage)
})

onBeforeUnmount(() => {
  channel.value?.close()
})
</script>

<style scoped>
.home-page {
  padding: 20px;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.path-container {
  position: relative;
  width: 800px;
  height: 600px;
  background: #f5f5f5;
  border-radius: 8px;
  border: 2px solid #409eff;
}

.center-line {
  position: absolute;
  top: 0;
  left: 50%;
  width: 2px;
  height: 100%;
  background: #409eff;
  opacity: 0.5;
}

.point {
  position: absolute;
  width: 24px;
  height: 24px;
  background: #409eff;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  transition: all 0.3s ease;
}

.point:hover {
  transform: translate(-50%, -50%) scale(1.2);
  box-shadow: 0 0 10px #409eff;
}

.point-number {
  color: white;
  font-size: 12px;
  font-weight: bold;
}

.moving-box {
  position: absolute;
  width: 30px;
  height: 30px;
  background: #67c23a;
  border-radius: 4px;
  transform: translate(-50%, -50%);
  transition: all 0.5s ease;
  z-index: 1;
}
</style>

网站公告

今日签到

点亮在社区的每一天
去签到