You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
YaYaRollCall/RollCallClient/src/views/RollCall.vue

632 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script setup>
import {onMounted, ref, watch, reactive} from 'vue'
import {ElMessage, ElNotification} from 'element-plus'
import {listStudent, rollCall, updatePoints} from "@/api/student.js";
// 响应式状态
const loading = ref(false)
const cards = ref([])
const canSelect = ref(true)
const isResetting = ref(false)
const dialogVisible = ref(false)
const dialogVisible2 = ref(false)
const cardResult = ref({})
const studentTable = ref([])
const avatarUrl = ref('https://api.aspark.cc')
const avatarKey = ref(0)
const pointChange = ref(0)
const pointChange2 = ref(0)
const randomEvents = ref([
{
title: "放他一马",
content: "有时候,放他一马,不是因为不在乎,而是因为明白,强求的结果只会让彼此更痛。放手的瞬间,心中隐隐作痛,却只能默默接受这无法改变的结局。(放过该同学,本次点名无效,请重新点名)"
},
{
title: "天选之子",
content: "成为天选之子,看似幸运加身,实则背负孤独。所有人羡慕你的光环,却无人懂得你走过的黑暗。被命运选中,不是因为强大,而是因为只能独自面对那些无人能解的重担。(同学化身天选之子,本次点名积分 +10 )"
}
])
const currentRandomEvent = ref({
title: '',
content: ''
})
// 洗牌
const shuffle = (array, count) => {
// 创建一个数组副本用于打乱,不改变原数组
const shuffled = [...array];
// Fisher-Yates 洗牌算法
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; // 交换位置
}
// 如果打乱后的数组长度大于等于 count返回前 count 个元素
if (shuffled.length >= count) {
return shuffled.slice(0, count);
}
// 如果数组长度不足,随机补充元素
const result = [...shuffled];
while (result.length < count) {
const randomIndex = Math.floor(Math.random() * array.length);
result.push(array[randomIndex]); // 随机从原数组中选取元素进行补充
}
return result;
};
const initCards = async () => {
const response = await rollCall()
pointChange.value = 0
cardResult.value = response.data
const totalCards = 27
const shuffledResult = shuffle(studentTable.value, totalCards)
// console.log(shuffledResult)
cards.value = shuffledResult.map(({name}) => ({name, isFlipped: false}))
avatarKey.value = Date.now()
const img = new Image()
img.src = `${avatarUrl.value}/random/${avatarKey.value}`
img.onerror = () => {
console.error('Failed to load avatar')
avatarUrl.value = 'path/to/default/avatar.png'
}
}
const selectCard = (index) => {
if (!canSelect.value || cards.value[index].isFlipped) return
cards.value[index] = {
name: cardResult.value.name,
isFlipped: true
}
canSelect.value = false
ElMessage.success(`恭喜你 ${cards.value[index].name}`)
setTimeout(() => {
cards.value.forEach((card, i) => {
if (i !== index) {
card.isFlipped = true
}
})
dialogVisible.value = true
}, 1000)
}
const handleRestart = () => {
dialogVisible.value = false
dialogVisible2.value = false
if (isResetting.value) return
isResetting.value = true
canSelect.value = false
cards.value.forEach(card => {
card.isFlipped = false
})
setTimeout(async () => {
await initCards()
canSelect.value = true
isResetting.value = false
}, 600)
}
onMounted(async () => {
loading.value = true
const response = await listStudent()
studentTable.value = response.data
await initCards()
loading.value = false
ElNotification({
title: '提示',
message: '从下方 27 张卡片中任意抽取一张吧!',
type: 'info',
})
})
const formatTooltip = (value) => {
return value > 0 ? `+${value}` : value.toString();
};
const updateSliderColor = () => {
const slider = document.querySelector('.el-slider__runway');
const sliderButton = document.querySelector('.el-slider__button');
if (!slider || !sliderButton) return;
// 设置按钮边框颜色
if (pointChange.value < 0) {
sliderButton.style.borderColor = '#f56c6c';
} else if (pointChange.value > 0) {
sliderButton.style.borderColor = '#67c23a';
} else {
sliderButton.style.borderColor = '#409EFF'; // 默认颜色当值为0时
}
// 移除旧的颜色条
const oldBars = slider.querySelectorAll('.custom-slider-bar');
oldBars.forEach(bar => bar.remove());
// 创建负值(红色)和正值(绿色)的颜色条
const negativeBar = document.createElement('div');
const positiveBar = document.createElement('div');
negativeBar.className = 'custom-slider-bar negative';
positiveBar.className = 'custom-slider-bar positive';
slider.appendChild(negativeBar);
slider.appendChild(positiveBar);
const zeroPosition = 50; // 0点在50%的位置
const currentPosition = pointChange.value * 8 * 2 + 50;
if (pointChange.value < 0) {
// 负值显示红色从0点到当前位置
negativeBar.style.left = `${currentPosition}%`;
negativeBar.style.width = `${zeroPosition - currentPosition}%`;
positiveBar.style.width = '0';
} else if (pointChange.value > 0) {
// 正值显示绿色从0点到当前位置
positiveBar.style.left = `${zeroPosition}%`;
positiveBar.style.width = `${currentPosition - zeroPosition}%`;
negativeBar.style.width = '0';
} else {
// 0值都不显示
negativeBar.style.width = '0';
positiveBar.style.width = '0';
}
};
const decrementPoints = () => {
if (pointChange.value > -5) {
pointChange.value -= 0.5;
}
};
const incrementPoints = () => {
if (pointChange.value < 5) {
pointChange.value += 0.5;
}
};
watch(pointChange, updateSliderColor);
onMounted(() => {
updateSliderColor();
});
const handlePointsChange = async () => {
if (pointChange.value === 0 && pointChange2.value === 0) {
dialogVisible.value = false
return
}
let response;
console.log(pointChange.value + ' ' + pointChange2.value)
if(pointChange2.value !== 0){
response = await updatePoints(cardResult.value.id, pointChange2.value)
pointChange2.value = 0
}else{
response = await updatePoints(cardResult.value.id, pointChange.value)
}
if (response.code === 0) {
ElMessage.error('失败,原因:' + response.msg)
}
dialogVisible.value = false
}
const luckThing = () => {
const lens = randomEvents.value.length
const randomIndex = Math.floor(Math.random() * lens);
currentRandomEvent.value = randomEvents.value[randomIndex]
if (randomIndex === 1) {
pointChange2.value = 10
}
dialogVisible2.value = true;
};
</script>
<template>
<el-card v-loading="loading">
<div class="lottery-container">
<div class="cards-grid">
<div
v-for="(card, index) in cards"
:key="index"
class="card"
:class="{ 'flipped': card.isFlipped }"
@click="selectCard(index)"
>
<div class="card-inner">
<div class="card-front"></div>
<div class="card-back">
<div class="card-content">
<!-- <div class="text-column">-->
<!-- <span class="vertical-text student-no">{{ card.no }}</span>-->
<!-- </div>-->
<div class="text-column">
<span class="vertical-text student-name">{{ card.name }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</el-card>
<el-dialog
v-model="dialogVisible"
width="400px"
:align-center="true"
class="profile-dialog"
@close="handleRestart"
>
<div class="profile-content">
<el-avatar
:size="100"
:src="avatarUrl + '/random/' + avatarKey"
class="profile-avatar"
/>
<h2 class="profile-name">{{ cardResult.no }}&nbsp;{{ cardResult.name }}</h2>
<h3 class="profile-points">当前积分:{{ cardResult.points }}</h3>
<div class="slider-demo-block">
<div class="slider-controls">
<el-button
@click="decrementPoints"
size="small"
class="slider-button decrease"
>-</el-button>
<div class="slider-wrapper">
<el-slider
v-model="pointChange"
:min="-3"
:max="3"
:step="0.5"
show-stops
:format-tooltip="formatTooltip"
/>
</div>
<el-button
@click="incrementPoints"
size="small"
class="slider-button increase"
>+</el-button>
</div>
<div class="point-change-label">
<span>积分变动:</span>
<span :class="{'positive': pointChange > 0, 'negative': pointChange < 0}">
{{ pointChange > 0 ? '+' : ''}}{{ pointChange }}
</span>
</div>
</div>
<div class="button-container">
<el-button
type="danger"
@click="handleRestart"
class="round-button"
>
<i class="iconfont yaya-xmark" style="font-size: 24px;"/>
</el-button>
<el-button
type="primary"
@click="luckThing"
class="round-button"
>
<i class="iconfont yaya-touzi" style="font-size: 24px;;" />
</el-button>
<el-button
type="success"
@click="handlePointsChange"
class="round-button"
>
<i class="iconfont yaya-duigou" style="font-size: 24px;" />
</el-button>
</div>
<p class="profile-bio">Coding the World.</p>
</div>
</el-dialog>
<el-dialog
v-model="dialogVisible2"
width="400px"
height="700px"
:align-center="true"
class="profile-dialog"
@close="handleRestart"
>
<div class="profile-content">
<!-- 显示随机事件的标题,原本头像位置替换为标题 -->
<h2 class="profile-name">{{ currentRandomEvent.title }}</h2>
<!-- 显示随机事件的内容,原来积分区域替换为内容展示 -->
<p class="point-change-label">{{ currentRandomEvent.content }}</p>
<div class="button-container">
<el-button
type="success"
@click="handlePointsChange"
class="round-button"
>
<i class="iconfont yaya-duigou" style="font-size: 24px;" />
</el-button>
</div>
</div>
</el-dialog>
</template>
<style>
body .el-dialog {
--el-dialog-border-radius: 20px;
}
</style>
<style scoped>
.lottery-container {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
box-sizing: border-box;
}
.cards-grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 10px;
width: 100%;
max-width: 1400px;
aspect-ratio: 2.7/1;
margin: 0 auto;
}
.card {
perspective: 1000px;
cursor: pointer;
aspect-ratio: 0.7/1;
}
.card-inner {
position: relative;
width: 100%;
height: 100%;
transition: transform 0.6s;
transform-style: preserve-3d;
}
.card.flipped .card-inner {
transform: rotateY(180deg);
}
.card-front,
.card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.card-front {
background-image: url('../assets/card.webp');
}
.card-back {
background-image: url('../assets/card2.webp');
border: 1px solid #ccc;
color: black;
transform: rotateY(180deg);
font-family: '隶书', '隶书-online', '微软雅黑', sans-serif;
display: flex;
justify-content: center;
align-items: center;
}
.card-content {
display: flex;
justify-content: center;
gap: 2px;
width: 90%;
height: 90%;
}
.text-column {
display: flex;
justify-content: center;
align-items: center;
}
.vertical-text {
writing-mode: vertical-rl;
text-orientation: upright;
white-space: nowrap;
line-height: 1.2;
max-height: 100%;
overflow: hidden;
}
.text-column .student-name {
font-size: clamp(1px, 3vw, 24px);
}
@media (max-width: 768px) {
.text-column .student-name {
font-size: clamp(1px, 2.5vw, 14px);
}
.card-content {
gap: 1px;
}
}
.profile-dialog :deep(.el-dialog) {
background-color: #f5f7f5;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.profile-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.profile-avatar {
margin-bottom: 20px;
}
.profile-name {
color: #2c3e50;
font-size: 1.8em;
margin-bottom: 8px;
font-weight: 600;
}
.profile-points {
color: #67c23a;
font-size: 1.4em;
margin-bottom: 25px;
font-weight: 500;
}
.profile-bio {
color: #5c6b77;
font-size: 1.1em;
margin-top: 20px;
font-family: 'Nautilus-pompilius', sans-serif;
font-style: italic;
}
.slider-demo-block {
width: 100%;
margin-bottom: 40px;
}
.slider-controls {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.slider-wrapper {
flex: 1;
margin: 0 15px;
}
.slider-button {
width: 32px;
height: 32px;
border: none;
color: white;
font-size: 18px;
cursor: pointer;
border-radius: 50%;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.slider-button.decrease {
background-color: #f56c6c;
}
.slider-button.increase {
background-color: #67c23a;
}
.slider-button:hover {
transform: scale(1.05);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.point-change-label {
text-align: center;
font-size: 16px;
color: #606266;
}
.point-change-label .positive {
color: #67c23a;
font-weight: 500;
}
.point-change-label .negative {
color: #f56c6c;
font-weight: 500;
}
:deep(.el-slider__runway) {
margin: 0;
background-color: #e4e7ed;
position: relative;
}
:deep(.custom-slider-bar) {
position: absolute;
height: 6px;
transition: all 0.3s ease;
}
:deep(.custom-slider-bar.negative) {
background-color: #f56c6c;
}
:deep(.custom-slider-bar.positive) {
background-color: #67c23a;
}
:deep(.el-slider__bar) {
display: none; /* 隐藏原始的滑块条 */
}
:deep(.el-slider__button) {
width: 20px;
height: 20px;
border: 2px solid; /* 移除固定的边框颜色 */
background-color: white;
transition: all 0.3s ease;
z-index: 3;
}
:deep(.el-slider__button:hover) {
transform: scale(1.1);
}
:deep(.el-slider__stop) {
background-color: transparent;
}
.button-container {
display: flex;
justify-content: center;
gap: 20px; /* 可根据需要调整间距 */
}
.round-button {
border-radius: 50%;
width: 50px; /* 根据需要调整大小 */
height: 50px; /* 根据需要调整大小 */
display: flex;
align-items: center;
justify-content: center;
}
</style>