feat: 新增幸运抽奖功能

main
刘隽霖 2 months ago
parent 235a275703
commit 69b7b21246

@ -21,6 +21,7 @@
"dependencies": {
"axios": "^1.7.7",
"element-plus": "^2.8.4",
"matter-js": "^0.20.0",
"mockjs": "^1.1.0",
"pinia": "^2.1.7",
"vue": "^3.4.29",

@ -14,6 +14,9 @@ importers:
element-plus:
specifier: ^2.8.4
version: 2.8.4(vue@3.5.11)
matter-js:
specifier: ^0.20.0
version: 0.20.0
mockjs:
specifier: ^1.1.0
version: 1.1.0
@ -1173,6 +1176,9 @@ packages:
magic-string@0.30.11:
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
matter-js@0.20.0:
resolution: {integrity: sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==}
memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
@ -2843,6 +2849,8 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
matter-js@0.20.0: {}
memoize-one@6.0.0: {}
merge-stream@2.0.0: {}

@ -14,6 +14,10 @@ const router = createRouter({
{
path: '/rating',
component: () => import('@/views/rating/RatingPage.vue')
},
{
path: '/ball-race',
component: () => import('@/views/roll/BallRacePage.vue')
}
]
})

@ -31,13 +31,21 @@
@change="updateScore"
/>
<!-- 如果评分为 4 分以上显示幸运抽奖提示和按钮 -->
<div v-if="currentRating >= 4 && isRatingDisabled" class="lucky-draw">
<h3>你答得太好了触发了一次幸运抽奖</h3>
<el-button class="ax_default button" @click="triggerLuckyDraw">
<span class="button-text">开始幸运抽奖</span>
</el-button>
</div>
<!-- 再抽一次按钮 -->
<el-button
v-if="isRatingDisabled"
class="ax_default button retry-button"
@click="goToRollCall"
>
<span class="button-text">再抽一次</span>
<span class="button-text">再抽一个人</span>
</el-button>
</div>
@ -88,7 +96,7 @@ const handleYesClick = () => {
// studentScore
const updateScore = (value) => {
// studentScore
studentScore.value += value
studentScore.value += value * 2
//
isRatingDisabled.value = true
@ -151,6 +159,11 @@ const updateScoreMinus = () => {
const goToRollCall = () => {
router.push('/roll-call') // /roll-call
}
//
const triggerLuckyDraw = () => {
console.log('触发幸运抽奖')
router.push('/ball-race')
}
</script>
<style scoped>

@ -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>
Loading…
Cancel
Save