|
|
|
@ -0,0 +1,411 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="container">
|
|
|
|
|
<!-- 左侧侧边栏 -->
|
|
|
|
|
<div class="sidebar">
|
|
|
|
|
<div class="exit-button">
|
|
|
|
|
<button @click="handleExit">退出</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 其他内容 -->
|
|
|
|
|
<!-- 右侧物理世界 -->
|
|
|
|
|
<div class="main-content">
|
|
|
|
|
<canvas ref="canvas"></canvas>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { onMounted, ref, reactive, watch } from 'vue'
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
import axios from 'axios'
|
|
|
|
|
import Matter from 'matter-js'
|
|
|
|
|
|
|
|
|
|
// 定义一个结构体保存接收到的消息
|
|
|
|
|
const messages = reactive([]) // 这里使用 reactive 来管理消息的数组
|
|
|
|
|
const stopNames = ref([
|
|
|
|
|
'何杰',
|
|
|
|
|
'马丽',
|
|
|
|
|
'王军',
|
|
|
|
|
'周强',
|
|
|
|
|
'张洋',
|
|
|
|
|
'黄娜',
|
|
|
|
|
'徐勇',
|
|
|
|
|
'杨伟',
|
|
|
|
|
'朱强',
|
|
|
|
|
'李辉',
|
|
|
|
|
'朱秀英',
|
|
|
|
|
'孙敏',
|
|
|
|
|
'郭敏',
|
|
|
|
|
'林涛',
|
|
|
|
|
'郭丽'
|
|
|
|
|
])
|
|
|
|
|
const stopIds = ref([])
|
|
|
|
|
const stopScores = ref([])
|
|
|
|
|
// 获取路由实例
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
// 退出按钮的点击事件处理函数
|
|
|
|
|
const handleExit = () => {
|
|
|
|
|
// 跳转到 /roll-call 路由
|
|
|
|
|
router.push('/roll-call')
|
|
|
|
|
}
|
|
|
|
|
// 获取 canvas 元素的引用
|
|
|
|
|
const canvas = ref(null)
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
// 使用 axios 从后端获取数据
|
|
|
|
|
axios
|
|
|
|
|
.post('http://localhost:8080/get-N-accord-score', {
|
|
|
|
|
num: 15
|
|
|
|
|
})
|
|
|
|
|
.then((response) => {
|
|
|
|
|
console.log('接收的数据:', response.data)
|
|
|
|
|
|
|
|
|
|
// 将接收的数据保存到结构体中
|
|
|
|
|
response.data.data.forEach((item) => {
|
|
|
|
|
messages.push({
|
|
|
|
|
User_id: item.User_id,
|
|
|
|
|
User_name: item.User_name,
|
|
|
|
|
User_gender: item.User_gender,
|
|
|
|
|
User_score: item.User_score
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
console.log('保存的消息:', messages)
|
|
|
|
|
stopNames.value = response.data.data.map((item) => item.User_name)
|
|
|
|
|
stopIds.value = response.data.data.map((item) => item.User_id)
|
|
|
|
|
stopScores.value = response.data.data.map((item) => item.User_score)
|
|
|
|
|
// 打印出 stopNames,确保正确更新
|
|
|
|
|
console.log('更新的 stopNames:', stopNames.value)
|
|
|
|
|
console.log('更新的 stopIds:', stopIds.value)
|
|
|
|
|
console.log('更新的 stopScores:', stopScores.value)
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
console.error('获取数据出错:', error)
|
|
|
|
|
})
|
|
|
|
|
// 初始化 Matter.js 引擎和渲染器
|
|
|
|
|
const { Engine, Render, Runner, Bodies, Composite, Events } = Matter
|
|
|
|
|
|
|
|
|
|
const myInnerWidth = 1900
|
|
|
|
|
const myInnerHeight = 891
|
|
|
|
|
|
|
|
|
|
// 创建物理引擎
|
|
|
|
|
const engine = Engine.create()
|
|
|
|
|
|
|
|
|
|
// 创建渲染器并将其绑定到 canvas
|
|
|
|
|
const render = Render.create({
|
|
|
|
|
element: canvas.value.parentNode, // 渲染到父容器中
|
|
|
|
|
canvas: canvas.value, // 绑定 canvas
|
|
|
|
|
engine: engine,
|
|
|
|
|
options: {
|
|
|
|
|
width: myInnerWidth - 250, // 减去侧边栏的宽度
|
|
|
|
|
height: myInnerHeight,
|
|
|
|
|
wireframes: false, // 关闭线框模式,使用填充
|
|
|
|
|
background: '#f4f4f4'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 创建一个地板
|
|
|
|
|
const ground = Bodies.rectangle(
|
|
|
|
|
myInnerWidth / 2,
|
|
|
|
|
myInnerHeight,
|
|
|
|
|
myInnerWidth,
|
|
|
|
|
50,
|
|
|
|
|
{
|
|
|
|
|
isStatic: true,
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: '#2ecc71'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const floor = Bodies.rectangle(myInnerWidth / 2, -30, myInnerWidth, 50, {
|
|
|
|
|
isStatic: true,
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: '#2ecc71'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 左侧墙壁
|
|
|
|
|
const leftWall = Bodies.rectangle(0, myInnerHeight / 2, 50, myInnerHeight, {
|
|
|
|
|
isStatic: true,
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: '#2c3e50'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
// 右侧墙壁
|
|
|
|
|
const rightWall = Bodies.rectangle(
|
|
|
|
|
myInnerWidth - 250,
|
|
|
|
|
myInnerHeight / 2,
|
|
|
|
|
50,
|
|
|
|
|
myInnerHeight,
|
|
|
|
|
{
|
|
|
|
|
isStatic: true,
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: '#2c3e50' // 将颜色改为与侧边栏一致
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 生成随机颜色的函数
|
|
|
|
|
function getRandomColor() {
|
|
|
|
|
const letters = '0123456789ABCDEF'
|
|
|
|
|
let color = '#'
|
|
|
|
|
for (let i = 0; i < 6; i++) {
|
|
|
|
|
color += letters[Math.floor(Math.random() * 16)]
|
|
|
|
|
}
|
|
|
|
|
return color
|
|
|
|
|
}
|
|
|
|
|
// 监听 stopNames 和 userIds user_score 的变化,生成小球
|
|
|
|
|
const balls = []
|
|
|
|
|
watch([stopNames, stopIds, stopScores], ([newNames, newIds, newScores]) => {
|
|
|
|
|
// 清空之前的球
|
|
|
|
|
balls.length = 0
|
|
|
|
|
// 创建和 stopNames 数量一致的小球
|
|
|
|
|
newNames.forEach((name, i) => {
|
|
|
|
|
const ball = Bodies.circle(Math.random() * (myInnerWidth - 250), 0, 25, {
|
|
|
|
|
restitution: 1.1, // 设置反弹系数
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: getRandomColor() // 使用随机颜色
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
ball.name = name // 给每个小球设置一个名字
|
|
|
|
|
ball.user_id = newIds[i] // 给每个小球设置一个 user_id
|
|
|
|
|
ball.user_score = newScores[i] // 给每个小球设置一个 user_score
|
|
|
|
|
balls.push(ball)
|
|
|
|
|
})
|
|
|
|
|
// 将小球添加到世界
|
|
|
|
|
Composite.add(engine.world, balls)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 创建密集的圆形障碍物
|
|
|
|
|
const obstacles = []
|
|
|
|
|
const cols = 10 // 障碍物的列数
|
|
|
|
|
const rows = 8 // 障碍物的行数
|
|
|
|
|
const spacingX = (myInnerWidth - 150) / (cols + 1) // X 轴的间距
|
|
|
|
|
const spacingY = myInnerHeight / 1 / (rows + 1) // 增加Y轴的间距
|
|
|
|
|
for (let row = 1; row <= rows; row++) {
|
|
|
|
|
// 为偶数行添加偏移量
|
|
|
|
|
const offsetX = row % 2 === 0 ? spacingX / 2 : 0
|
|
|
|
|
|
|
|
|
|
// 如果是偶数行,左侧增加一个额外的圆形障碍物
|
|
|
|
|
if (row % 2 === 0) {
|
|
|
|
|
const extraLeftObstacle = Bodies.circle(
|
|
|
|
|
spacingX / 2 - 50, // X 坐标,靠近左侧
|
|
|
|
|
row * spacingY + 30, // Y 坐标
|
|
|
|
|
30, // 障碍物的半径
|
|
|
|
|
{
|
|
|
|
|
isStatic: true,
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: '#2c3e50' // 设置障碍物的颜色
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
obstacles.push(extraLeftObstacle) // 将额外障碍物加入数组
|
|
|
|
|
}
|
|
|
|
|
for (let col = 1; col <= cols; col++) {
|
|
|
|
|
const obstacle = Bodies.circle(
|
|
|
|
|
col * spacingX + offsetX - 50, // X 坐标,根据行号决定是否添加偏移量
|
|
|
|
|
row * spacingY + 30, // Y 坐标,距离顶部 200px
|
|
|
|
|
30, // 障碍物的半径
|
|
|
|
|
{
|
|
|
|
|
isStatic: true,
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: '#2c3e50' // 设置障碍物的颜色
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
obstacles.push(obstacle)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建区域并给每个区域编号
|
|
|
|
|
const areas = []
|
|
|
|
|
const separatorWidth = 15 // 隔板的宽度
|
|
|
|
|
const areaNumbers = [] // 用于存储每个区域的随机数字
|
|
|
|
|
for (let col = 1; col <= cols; col++) {
|
|
|
|
|
let randomNum
|
|
|
|
|
do {
|
|
|
|
|
randomNum = Math.floor(Math.random() * 11) - 5 // 生成 -5 到 1 的随机数
|
|
|
|
|
} while (randomNum === 0) // 如果是 0,重新生成
|
|
|
|
|
const formattedNum = randomNum > 0 ? `+${randomNum}` : `${randomNum}` // 生成 -5 到 5 的随机数字,并将正数添加 + 号
|
|
|
|
|
areaNumbers.push(formattedNum)
|
|
|
|
|
|
|
|
|
|
const area = Bodies.rectangle(
|
|
|
|
|
col * spacingX - 50, // X 位置
|
|
|
|
|
myInnerHeight - 40, // Y 位置,高于地板位置
|
|
|
|
|
spacingX - separatorWidth, // 区域的宽度
|
|
|
|
|
20, // 区域的高度
|
|
|
|
|
{
|
|
|
|
|
isStatic: true,
|
|
|
|
|
isSensor: true, // 将区域设置为传感器,不影响物体运动
|
|
|
|
|
label: `Area ${col}`, // 给区域添加编号
|
|
|
|
|
render: {
|
|
|
|
|
fillStyle: 'rgba(52, 73, 94, 0.3)' // 区域颜色设置为透明度较高
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
area.number = formattedNum // 给每个区域添加一个随机数字
|
|
|
|
|
area.score = randomNum
|
|
|
|
|
areas.push(area)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将小球、地板、墙壁和障碍物加入到物理世界
|
|
|
|
|
Composite.add(engine.world, [
|
|
|
|
|
...balls,
|
|
|
|
|
ground,
|
|
|
|
|
floor,
|
|
|
|
|
leftWall,
|
|
|
|
|
rightWall,
|
|
|
|
|
...obstacles,
|
|
|
|
|
...areas
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// 监听小球进入区域事件
|
|
|
|
|
Events.on(engine, 'collisionStart', (event) => {
|
|
|
|
|
const pairs = event.pairs
|
|
|
|
|
pairs.forEach((pair) => {
|
|
|
|
|
const { bodyA, bodyB } = pair
|
|
|
|
|
// 检查小球和区域是否发生碰撞
|
|
|
|
|
if (bodyA.isSensor && balls.includes(bodyB)) {
|
|
|
|
|
let newscore = bodyA.score + bodyB.user_score
|
|
|
|
|
console.log(
|
|
|
|
|
`Ball entered ${bodyA.label}, Score: ${bodyA.score}, Ball Name: ${bodyB.name}, Ball Id: ${bodyB.user_id}, Ball Score: ${bodyB.user_score}, newScore: ${newscore}`
|
|
|
|
|
)
|
|
|
|
|
const data = JSON.stringify({
|
|
|
|
|
user_id: bodyB.user_id,
|
|
|
|
|
user_score: newscore
|
|
|
|
|
})
|
|
|
|
|
// 构建 config 对象
|
|
|
|
|
var config = {
|
|
|
|
|
method: 'post',
|
|
|
|
|
url: 'http://localhost:8080/change-score',
|
|
|
|
|
data: data
|
|
|
|
|
}
|
|
|
|
|
// 发起 POST 请求,传递数据给后端
|
|
|
|
|
axios(config)
|
|
|
|
|
.then((response) => {
|
|
|
|
|
console.log('数据成功发送到后端:', response.data)
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
console.error('发送数据到后端时出错:', error)
|
|
|
|
|
})
|
|
|
|
|
const sidebar = document.querySelector('.sidebar')
|
|
|
|
|
const entry = document.createElement('div')
|
|
|
|
|
entry.textContent = `${bodyB.name} 分数:${bodyA.number}`
|
|
|
|
|
sidebar.appendChild(entry)
|
|
|
|
|
Composite.remove(engine.world, bodyB) // 从世界中移除小球
|
|
|
|
|
} else if (bodyB.isSensor && balls.includes(bodyA)) {
|
|
|
|
|
console.log(
|
|
|
|
|
`Ball entered ${bodyB.label}, Score: ${bodyB.score}, Ball Name: ${bodyA.name}`
|
|
|
|
|
)
|
|
|
|
|
const sidebar = document.querySelector('.sidebar')
|
|
|
|
|
const entry = document.createElement('div')
|
|
|
|
|
entry.textContent = `${bodyA.name} 分数:${bodyB.number}`
|
|
|
|
|
sidebar.appendChild(entry)
|
|
|
|
|
Composite.remove(engine.world, bodyA) // 从世界中移除小球
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 自定义渲染函数,用于显示小球名字
|
|
|
|
|
Matter.Events.on(render, 'afterRender', () => {
|
|
|
|
|
const context = render.context
|
|
|
|
|
context.font = '20px Arial'
|
|
|
|
|
context.fillStyle = '#000'
|
|
|
|
|
|
|
|
|
|
// 显示每个小球的名字
|
|
|
|
|
balls.forEach((ball) => {
|
|
|
|
|
const { position } = ball
|
|
|
|
|
context.fillText(ball.name, position.x - 20, position.y - 30) // 在小球上方显示名字
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 显示每个区域的随机数字
|
|
|
|
|
areas.forEach((area) => {
|
|
|
|
|
const { position } = area
|
|
|
|
|
context.font = 'bold 24px Arial'
|
|
|
|
|
context.fillStyle = area.number > 0 ? 'green' : 'red'
|
|
|
|
|
context.fillText(area.number, position.x - 10, position.y - 10) // 在区域上方显示数字
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 运行引擎
|
|
|
|
|
Engine.run(engine)
|
|
|
|
|
|
|
|
|
|
// 运行渲染器
|
|
|
|
|
Render.run(render)
|
|
|
|
|
|
|
|
|
|
// 创建一个运行器来驱动模拟
|
|
|
|
|
const runner = Runner.create()
|
|
|
|
|
Runner.run(runner, engine)
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* 页面总体布局:使用 Flexbox 布局 */
|
|
|
|
|
.container {
|
|
|
|
|
display: flex;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 左侧边栏样式 */
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 240px;
|
|
|
|
|
background-color: #2c3e50;
|
|
|
|
|
color: #ecf0f1;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar h2 {
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar ul {
|
|
|
|
|
padding: 0;
|
|
|
|
|
list-style-type: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar ul li {
|
|
|
|
|
margin: 15px 0;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar ul li:hover {
|
|
|
|
|
color: #1abc9c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exit-button {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exit-button button {
|
|
|
|
|
background-color: #e74c3c;
|
|
|
|
|
color: #ecf0f1;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 10px 20px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exit-button button:hover {
|
|
|
|
|
background-color: #c0392b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 右侧主内容区样式 */
|
|
|
|
|
.main-content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
position: relative;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* canvas 样式,用于控制显示 */
|
|
|
|
|
canvas {
|
|
|
|
|
background-color: #f4f4f4;
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
</style>
|