Skip to content

Commit 8a25764

Browse files
committed
feat: implement completion modal in QAController with enhanced visual effects and user interaction
1 parent 21c7913 commit 8a25764

1 file changed

Lines changed: 225 additions & 14 deletions

File tree

scripts/WenTianPavilion/index.js

Lines changed: 225 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,8 @@ class QAController {
490490
if (this.optionsContainer) {
491491
Array.from(this.optionsContainer.children).forEach(child => {
492492
if (child.dataset.id === question.answer) {
493+
// 正确答案
493494
child.style.backgroundColor = '#e0ffe0';
494-
// 添加正确图标
495495
const rightIcon = document.createElement('img');
496496
rightIcon.src = '../assets/images/WenTianPavilion/right.svg';
497497
rightIcon.alt = 'correct';
@@ -500,9 +500,9 @@ class QAController {
500500
rightIcon.style.marginLeft = '1rem';
501501
rightIcon.style.verticalAlign = 'middle';
502502
child.appendChild(rightIcon);
503-
} else if (child.classList.contains('selected') && !correct) {
503+
} else if (child.classList.contains('selected')) {
504+
// 用户选择的错误答案
504505
child.style.backgroundColor = '#ffe0e0';
505-
// 添加错误图标
506506
const errorIcon = document.createElement('img');
507507
errorIcon.src = '../assets/images/WenTianPavilion/error.svg';
508508
errorIcon.alt = 'error';
@@ -511,6 +511,10 @@ class QAController {
511511
errorIcon.style.marginLeft = '1rem';
512512
errorIcon.style.verticalAlign = 'middle';
513513
child.appendChild(errorIcon);
514+
} else {
515+
// 其他未选择的选项,设置为灰色背景表示未选择
516+
child.style.backgroundColor = 'rgba(128, 128, 128, 0.2)';
517+
child.style.opacity = '0.6';
514518
}
515519
});
516520
}
@@ -656,8 +660,16 @@ class QAController {
656660
piece.classList.add('puzzle-piece');
657661
piece.setAttribute('draggable', 'true');
658662
piece.dataset.index = originalIndex; // 使用原始索引作为正确位置
659-
piece.style.width = '8rem';
660-
piece.style.height = '8rem';
663+
piece.style.cssText = `
664+
width: 8rem;
665+
height: 8rem;
666+
object-fit: cover;
667+
border-radius: 0.5rem;
668+
border: 0.2rem solid rgba(122, 147, 255, 0.3);
669+
cursor: move;
670+
transition: all 0.3s ease;
671+
background-color: rgba(0, 0, 0, 0.2);
672+
`;
661673
piece.title = `碎片 ${originalIndex + 1}`;
662674
piecesContainer.appendChild(piece);
663675

@@ -670,11 +682,22 @@ class QAController {
670682
const target = document.createElement('div');
671683
target.classList.add('puzzle-target');
672684
target.dataset.index = index;
673-
target.style.width = '8rem';
674-
target.style.height = '8rem';
685+
target.style.cssText = `
686+
width: 8rem;
687+
height: 8rem;
688+
border: 0.2rem dashed rgba(122, 147, 255, 0.3);
689+
border-radius: 0.5rem;
690+
display: flex;
691+
align-items: center;
692+
justify-content: center;
693+
background-color: rgba(122, 147, 255, 0.05);
694+
transition: all 0.3s ease;
695+
position: relative;
696+
font-size: 1.4rem;
697+
color: rgba(122, 147, 255, 0.7);
698+
font-family: 'fys', sans-serif;
699+
`;
675700
target.textContent = `位置 ${index + 1}`;
676-
target.style.fontSize = '1.4rem';
677-
target.style.color = 'rgba(122, 147, 255, 0.7)';
678701
targetsContainer.appendChild(target);
679702

680703
// 添加拖拽事件
@@ -688,6 +711,7 @@ class QAController {
688711
handleDragStart(e) {
689712
e.dataTransfer.setData('text/plain', e.target.dataset.index);
690713
e.target.style.opacity = '0.5';
714+
e.target.style.transform = 'scale(0.95)';
691715
}
692716

693717
handleDragOver(e) {
@@ -713,8 +737,9 @@ class QAController {
713737

714738
if (!piece) return;
715739

716-
// 恢复透明度
740+
// 恢复透明度和缩放
717741
piece.style.opacity = '1';
742+
piece.style.transform = 'scale(1)';
718743

719744
// 检查是否已有碎片在目标位置
720745
const existingPiece = e.target.querySelector('.puzzle-piece');
@@ -726,16 +751,30 @@ class QAController {
726751

727752
if (pieceIndex === targetIndex) {
728753
// 正确放置
754+
e.target.innerHTML = ''; // 清空内容
729755
e.target.appendChild(piece);
730-
e.target.textContent = ''; // 清除位置提示文字
731756
e.target.classList.add('puzzle-completed');
757+
758+
// 调整碎片在目标位置的样式
759+
piece.style.width = '100%';
760+
piece.style.height = '100%';
761+
piece.style.objectFit = 'cover';
762+
piece.style.borderRadius = '0.5rem';
763+
732764
this.checkPuzzleCompletion();
733765
} else {
734-
// 错误放置,直接移回碎片容器
766+
// 错误放置,显示错误动画后移回碎片容器
767+
e.target.innerHTML = ''; // 清空内容
735768
e.target.appendChild(piece);
736-
e.target.textContent = ''; // 清除位置提示文字
769+
770+
// 错误动画
771+
piece.style.filter = 'sepia(1) hue-rotate(-50deg) saturate(2)';
737772
setTimeout(() => {
738773
const piecesContainer = document.querySelector('.puzzle-pieces');
774+
piece.style.width = '8rem';
775+
piece.style.height = '8rem';
776+
piece.style.objectFit = 'cover';
777+
piece.style.filter = 'none';
739778
piecesContainer.appendChild(piece);
740779
e.target.textContent = `位置 ${parseInt(targetIndex) + 1}`;
741780
e.target.classList.remove('puzzle-completed');
@@ -763,7 +802,7 @@ class QAController {
763802
if (correctCount === totalCount) {
764803
// 拼图完成
765804
setTimeout(() => {
766-
alert('🎉 恭喜!拼图完成!\n\n你已经成功解开了宇宙的奥秘,收集了所有的知识碎片!');
805+
this.showCompletionModal();
767806
// 可以添加完成后的效果
768807
const puzzleContainer = document.querySelector('.puzzle-targets');
769808
if (puzzleContainer) {
@@ -789,6 +828,178 @@ class QAController {
789828
}
790829
}
791830

831+
showCompletionModal() {
832+
// 创建弹窗覆盖层
833+
const modalOverlay = document.createElement('div');
834+
modalOverlay.className = 'completion-modal-overlay';
835+
modalOverlay.style.cssText = `
836+
position: fixed;
837+
top: 0;
838+
left: 0;
839+
width: 100vw;
840+
height: 100vh;
841+
background: rgba(0, 0, 0, 0.3);
842+
display: flex;
843+
align-items: center;
844+
justify-content: center;
845+
z-index: 10000;
846+
backdrop-filter: blur(1rem);
847+
`;
848+
849+
// 创建弹窗内容
850+
const modalContent = document.createElement('div');
851+
modalContent.className = 'completion-modal-content';
852+
modalContent.style.cssText = `
853+
background: linear-gradient(135deg, rgba(30, 0, 50, 0.95), rgba(70, 20, 100, 0.95));
854+
border-radius: 2rem;
855+
padding: 4rem;
856+
text-align: center;
857+
max-width: 60rem;
858+
position: relative;
859+
transform: scale(0.8);
860+
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
861+
box-shadow: 0 2rem 4rem rgba(122, 147, 255, 0.3);
862+
`;
863+
864+
// 创建标题
865+
const title = document.createElement('h2');
866+
title.textContent = '恭喜!拼图完成!';
867+
title.style.cssText = `
868+
font-size: 4rem;
869+
font-family: 'fys', sans-serif;
870+
background: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(191, 151, 202, 1), rgba(127, 48, 150, 1));
871+
background-clip: text;
872+
color: transparent;
873+
margin-bottom: 2rem;
874+
`;
875+
876+
// 创建图片容器
877+
const imageContainer = document.createElement('div');
878+
imageContainer.style.cssText = `
879+
margin: 3rem 0;
880+
perspective: 150rem;
881+
display: flex;
882+
justify-content: center;
883+
align-items: center;
884+
`;
885+
886+
// 创建奖杯图片
887+
const trophyImage = document.createElement('img');
888+
trophyImage.src = '../assets/images/WenTianPavilion/all.webp';
889+
trophyImage.alt = 'completion trophy';
890+
trophyImage.style.cssText = `
891+
width: 20rem;
892+
height: 20rem;
893+
object-fit: contain;
894+
transition: transform 0.2s ease-out;
895+
transform-style: preserve-3d;
896+
cursor: pointer;
897+
filter: drop-shadow(0 1rem 2rem rgba(122, 147, 255, 0.4));
898+
`;
899+
900+
// 添加3D旋转交互
901+
imageContainer.addEventListener('mousemove', (e) => {
902+
const rect = imageContainer.getBoundingClientRect();
903+
const centerX = rect.left + rect.width / 2;
904+
const centerY = rect.top + rect.height / 2;
905+
906+
// 计算鼠标相对于容器中心的位置(-1到1之间)
907+
const mouseX = (e.clientX - centerX) / (rect.width / 2);
908+
const mouseY = (e.clientY - centerY) / (rect.height / 2);
909+
910+
// 计算旋转角度,限制在合理范围内
911+
const rotateY = mouseX * 15; // 左右旋转最大15度
912+
const rotateX = -mouseY * 10; // 上下旋转最大10度
913+
914+
trophyImage.style.transform = `
915+
perspective(150rem)
916+
rotateX(${rotateX}deg)
917+
rotateY(${rotateY}deg)
918+
scale(1.05)
919+
translateZ(2rem)
920+
`;
921+
});
922+
923+
imageContainer.addEventListener('mouseleave', () => {
924+
trophyImage.style.transform = `
925+
perspective(150rem)
926+
rotateX(0deg)
927+
rotateY(0deg)
928+
scale(1)
929+
translateZ(0rem)
930+
`;
931+
});
932+
933+
// 创建描述文字
934+
const description = document.createElement('p');
935+
description.textContent = '你已经成功解开了宇宙的奥秘,收集了所有的知识碎片!';
936+
description.style.cssText = `
937+
font-size: 2.4rem;
938+
font-family: 'fys', sans-serif;
939+
color: rgba(255, 255, 255, 0.9);
940+
margin: 2rem 0;
941+
line-height: 1.5;
942+
`;
943+
944+
// 创建关闭按钮
945+
const closeButton = document.createElement('button');
946+
closeButton.textContent = '完成';
947+
closeButton.style.cssText = `
948+
background: linear-gradient(135deg, rgba(122, 147, 255, 1), rgba(127, 48, 150, 1));
949+
border: none;
950+
border-radius: 1rem;
951+
color: white;
952+
font-size: 2.4rem;
953+
font-family: 'fys', sans-serif;
954+
padding: 1rem 2rem;
955+
cursor: pointer;
956+
transition: all 0.3s ease;
957+
margin-top: 2rem;
958+
`;
959+
960+
closeButton.addEventListener('mouseover', () => {
961+
closeButton.style.transform = 'scale(1.05)';
962+
closeButton.style.boxShadow = '0 1rem 2rem rgba(122, 147, 255, 0.4)';
963+
});
964+
965+
closeButton.addEventListener('mouseout', () => {
966+
closeButton.style.transform = 'scale(1)';
967+
closeButton.style.boxShadow = 'none';
968+
});
969+
970+
closeButton.addEventListener('click', () => {
971+
modalOverlay.style.opacity = '0';
972+
modalContent.style.transform = 'scale(0.8)';
973+
setTimeout(() => {
974+
document.body.removeChild(modalOverlay);
975+
}, 300);
976+
});
977+
978+
// 组装弹窗
979+
imageContainer.appendChild(trophyImage);
980+
modalContent.appendChild(title);
981+
modalContent.appendChild(imageContainer);
982+
modalContent.appendChild(description);
983+
modalContent.appendChild(closeButton);
984+
modalOverlay.appendChild(modalContent);
985+
986+
// 添加到页面并显示动画
987+
document.body.appendChild(modalOverlay);
988+
989+
// 触发进入动画
990+
setTimeout(() => {
991+
modalOverlay.style.opacity = '1';
992+
modalContent.style.transform = 'scale(1)';
993+
}, 50);
994+
995+
// 点击背景关闭弹窗
996+
modalOverlay.addEventListener('click', (e) => {
997+
if (e.target === modalOverlay) {
998+
closeButton.click();
999+
}
1000+
});
1001+
}
1002+
7921003
showHideController(hiddenControllers, showControllers) {
7931004
// 立即隐藏不需要的元素
7941005
hiddenControllers.forEach(controller => {

0 commit comments

Comments
 (0)