Commit 5d12e41f authored by liujiangnan's avatar liujiangnan

feat

parent db2cfe8a
/* 基于视口单位的等比例缩放系统 */
/* 设计基准:1920x1080 桌面横屏 */
body { #app {
background: #fff; margin: 0 auto;
border-radius: calc(15 * 100vw / 1920);
box-shadow: 0 calc(10 * 100vw / 1920) calc(30 * 100vw / 1920) rgba(0, 0, 0, 0.2);
overflow: hidden;
} }
#test{ .question-container {
margin: 80px; position: absolute;
top: calc(10 * 100vw / 1920);
left: calc(10 * 100vw / 1920);
bottom: calc(10 * 100vw / 1920);
right: calc(10 * 100vw / 1920);
}
/* 题干部分样式 */
.question-section {
margin-bottom: calc(30 * 100vw / 1920);
padding: calc(25 * 100vw / 1920);
text-align: center;
}
.question-image {
text-align: center;
margin-bottom: calc(20 * 100vw / 1920);
}
.question-image img {
max-width: 100%;
max-height: calc(250 * 100vw / 1920);
border-radius: calc(10 * 100vw / 1920);
box-shadow: 0 calc(4 * 100vw / 1920) calc(12 * 100vw / 1920) rgba(0, 0, 0, 0.1);
}
.question-audio {
margin-bottom: calc(20 * 100vw / 1920);
display: flex;
align-items: center;
justify-content: center;
gap: calc(15 * 100vw / 1920);
}
.question-audio .audio-info {
color: #606266;
font-size: calc(14 * 100vw / 1920);
}
.question-text h3 {
margin: 0;
color: #303133;
font-size: calc(20 * 100vw / 1920);
line-height: 1.6;
font-weight: 600;
}
/* 选项部分样式 */
.options-section {
margin-bottom: calc(30 * 100vw / 1920);
display: flex;
flex-wrap: wrap;
gap: calc(15 * 100vw / 1920);
justify-content: center;
}
.option-item {
flex: 0 0 calc(300 * 100vw / 1920);
max-width: calc(300 * 100vw / 1920);
min-height: calc(200 * 100vw / 1920);
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 0;
padding: calc(20 * 100vw / 1920);
border: calc(2 * 100vw / 1920) solid #e4e7ed;
border-radius: calc(10 * 100vw / 1920);
cursor: pointer;
transition: all 0.3s ease;
background: white;
text-align: center;
}
.option-item:hover {
border-color: #409EFF;
box-shadow: 0 calc(4 * 100vw / 1920) calc(12 * 100vw / 1920) rgba(64, 158, 255, 0.15);
transform: translateY(calc(-2 * 100vw / 1920));
}
.option-item.selected {
border-color: #409EFF;
background: #f0f9ff;
}
.option-item.selected .option-selected {
color: #409EFF;
font-size: calc(24 * 100vw / 1920);
}
.option-content {
flex: 1;
display: flex;
flex-direction: column;
gap: calc(10 * 100vw / 1920);
width: 100%;
align-items: center;
}
.option-image {
text-align: center;
margin-bottom: calc(10 * 100vw / 1920);
}
.option-image img {
max-width: calc(120 * 100vw / 1920);
max-height: calc(120 * 100vw / 1920);
border-radius: calc(6 * 100vw / 1920);
object-fit: cover;
}
.option-audio {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(8 * 100vw / 1920);
margin-bottom: calc(10 * 100vw / 1920);
}
.option-audio .audio-info {
color: #606266;
font-size: calc(12 * 100vw / 1920);
text-align: center;
}
.option-text {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(8 * 100vw / 1920);
width: 100%;
}
.option-label {
font-weight: bold;
color: #409EFF;
font-size: calc(18 * 100vw / 1920);
margin-bottom: calc(5 * 100vw / 1920);
}
.option-text-content {
color: #303133;
font-size: calc(16 * 100vw / 1920);
line-height: 1.5;
text-align: center;
word-wrap: break-word;
}
.option-status {
margin-top: calc(10 * 100vw / 1920);
}
.option-selected {
color: #409EFF;
font-size: calc(24 * 100vw / 1920);
}
/* 提交按钮部分 */
.submit-section {
position: absolute;
right: calc(30 * 100vw / 1920);
bottom: calc(30 * 100vw / 1920);
}
.submit-section .el-button {
margin: 0 calc(10 * 100vw / 1920);
padding: calc(12 * 100vw / 1920) calc(30 * 100vw / 1920);
font-size: calc(16 * 100vw / 1920);
border-radius: calc(8 * 100vw / 1920);
}
/* 庆祝动画弹窗 */
.celebration-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
animation: fadeIn 0.3s ease-in-out;
}
.celebration-content {
background: white;
padding: calc(40 * 100vw / 1920);
border-radius: calc(20 * 100vw / 1920);
text-align: center;
box-shadow: 0 calc(10 * 100vw / 1920) calc(30 * 100vw / 1920) rgba(0, 0, 0, 0.3);
animation: bounceIn 0.6s ease-out;
position: relative;
z-index: 10000;
}
.celebration-icon {
font-size: calc(80 * 100vw / 1920);
margin-bottom: calc(20 * 100vw / 1920);
animation: bounce 1s infinite;
}
.celebration-text {
font-size: calc(32 * 100vw / 1920);
font-weight: bold;
color: #67c23a;
margin-bottom: calc(20 * 100vw / 1920);
}
.celebration-stars {
display: flex;
justify-content: center;
gap: calc(10 * 100vw / 1920);
}
.star {
font-size: calc(24 * 100vw / 1920);
animation: twinkle 1.5s infinite;
}
.star:nth-child(2) {
animation-delay: 0.3s;
}
.star:nth-child(3) {
animation-delay: 0.6s;
}
/* 错误动画弹窗 */
.error-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
animation: fadeIn 0.3s ease-in-out;
}
.error-content {
background: white;
padding: calc(40 * 100vw / 1920);
border-radius: calc(20 * 100vw / 1920);
text-align: center;
box-shadow: 0 calc(10 * 100vw / 1920) calc(30 * 100vw / 1920) rgba(0, 0, 0, 0.3);
animation: shake 0.5s ease-in-out;
position: relative;
z-index: 10000;
}
.error-icon {
font-size: calc(80 * 100vw / 1920);
margin-bottom: calc(20 * 100vw / 1920);
animation: shake 0.5s ease-in-out;
}
.error-text {
font-size: calc(32 * 100vw / 1920);
font-weight: bold;
color: #f56c6c;
margin-bottom: calc(20 * 100vw / 1920);
}
.correct-answer-text {
font-size: calc(18 * 100vw / 1920);
color: #606266;
background: #f5f7fa;
padding: calc(15 * 100vw / 1920);
border-radius: calc(8 * 100vw / 1920);
border-left: calc(4 * 100vw / 1920) solid #409EFF;
}
/* 彩带动画 */
.confetti-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.confetti {
position: absolute;
width: calc(10 * 100vw / 1920);
height: calc(10 * 100vw / 1920);
background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57);
animation: confettiFall 3s linear infinite;
}
.confetti:nth-child(1) { left: 10%; animation-delay: 0s; }
.confetti:nth-child(2) { left: 20%; animation-delay: 0.1s; }
.confetti:nth-child(3) { left: 30%; animation-delay: 0.2s; }
.confetti:nth-child(4) { left: 40%; animation-delay: 0.3s; }
.confetti:nth-child(5) { left: 50%; animation-delay: 0.4s; }
.confetti:nth-child(6) { left: 60%; animation-delay: 0.5s; }
.confetti:nth-child(7) { left: 70%; animation-delay: 0.6s; }
.confetti:nth-child(8) { left: 80%; animation-delay: 0.7s; }
.confetti:nth-child(9) { left: 90%; animation-delay: 0.8s; }
.confetti:nth-child(10) { left: 15%; animation-delay: 0.9s; }
.confetti:nth-child(11) { left: 25%; animation-delay: 1s; }
.confetti:nth-child(12) { left: 35%; animation-delay: 1.1s; }
.confetti:nth-child(13) { left: 45%; animation-delay: 1.2s; }
.confetti:nth-child(14) { left: 55%; animation-delay: 1.3s; }
.confetti:nth-child(15) { left: 65%; animation-delay: 1.4s; }
.confetti:nth-child(16) { left: 75%; animation-delay: 1.5s; }
.confetti:nth-child(17) { left: 85%; animation-delay: 1.6s; }
.confetti:nth-child(18) { left: 95%; animation-delay: 1.7s; }
.confetti:nth-child(19) { left: 5%; animation-delay: 1.8s; }
.confetti:nth-child(20) { left: 85%; animation-delay: 1.9s; }
/* 动画关键帧 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes bounceIn {
0% { transform: scale(0.3); opacity: 0; }
50% { transform: scale(1.05); }
70% { transform: scale(0.9); }
100% { transform: scale(1); opacity: 1; }
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-10px); }
60% { transform: translateY(-5px); }
}
@keyframes twinkle {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.2); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}
@keyframes confettiFall {
0% {
transform: translateY(-100vh) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(100vh) rotate(360deg);
opacity: 0;
}
}
/* 响应式断点 - 保持布局不变,只调整基准值 */
@media (max-width: 1200px) {
/* 中等屏幕:使用较小的基准值 */
.option-item {
flex: 0 0 calc(280 * 100vw / 1920);
max-width: calc(280 * 100vw / 1920);
}
}
@media (max-width: 768px) {
/* 小屏幕:调整基准值但保持比例 */
.question-container {
padding: calc(20 * 100vw / 1920);
}
.options-section {
flex-direction: column;
align-items: center;
}
.option-item {
flex: 0 0 auto;
max-width: 100%;
width: 100%;
min-height: auto;
}
.option-content {
flex-direction: row;
align-items: center;
text-align: left;
}
.option-image {
margin-bottom: 0;
margin-right: calc(15 * 100vw / 1920);
}
.option-audio {
flex-direction: row;
margin-bottom: 0;
margin-right: calc(15 * 100vw / 1920);
}
.option-text {
flex-direction: row;
align-items: center;
flex: 1;
}
.option-label {
margin-bottom: 0;
margin-right: calc(8 * 100vw / 1920);
}
.option-status {
margin-top: 0;
margin-left: calc(15 * 100vw / 1920);
}
.submit-section .el-button {
display: block;
width: 100%;
margin: calc(10 * 100vw / 1920) 0;
}
} }
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>h5-template-generator</title> <title>AI选择题播放</title>
<link href="./index.css" rel="stylesheet" > <link href="./index.css" rel="stylesheet" >
<script type="text/javascript" src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/jquery-1.12.3.min.js"></script> <script type="text/javascript" src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/jquery-1.12.3.min.js"></script>
<script type="text/javascript" src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script> <script type="text/javascript" src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script>
...@@ -15,7 +15,90 @@ ...@@ -15,7 +15,90 @@
<script src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/element-ui/index.js"></script> <script src="//teach.cdn.ireadabc.com/h5template/h5-static-lib/js/element-ui/index.js"></script>
</head> </head>
<body> <body>
<h1 id="test"></h1> <div id="app">
<div class="question-container" v-if="questionData">
<!-- 题干部分 -->
<div class="question-section">
<div class="question-text" v-if="questionData.question">
<h3>{{ questionData.question }}</h3>
</div>
<div class="question-image" v-if="questionData.image_url">
<img :src="questionData.image_url" alt="题干图片">
</div>
<div class="question-audio" v-if="questionData.audio_url">
<el-button size="small" type="primary" @click="toggleQuestionAudio">
<i :class="questionData.isPlaying ? 'el-icon-video-pause' : 'el-icon-video-play'"></i>
{{ questionData.isPlaying ? '暂停' : '播放' }}题目音频
</el-button>
<audio ref="questionAudio" :src="questionData.audio_url" @ended="questionAudioEnded" style="display: none;"></audio>
</div>
</div>
<!-- 选项部分 -->
<div class="options-section">
<div class="option-item"
v-for="(option, index) in questionData.options"
:key="index"
@click="toggleOption(index)"
:class="{ 'selected': selectedOptions.includes(index) }">
<div class="option-content">
<div class="option-text" v-if="option.text">
<span class="option-text-content">{{ option.text }}</span>
</div>
<div class="option-image" v-if="option.image_url">
<img :src="option.image_url" alt="选项图片">
</div>
<div class="option-audio" v-if="option.audio_url">
<el-button size="mini" type="primary" @click.stop="toggleOptionAudio(index)">
<i :class="option.isPlaying ? 'el-icon-video-pause' : 'el-icon-video-play'"></i>
{{ option.isPlaying ? '暂停' : '播放' }}
</el-button>
<audio :ref="'optionAudio' + index" :src="option.audio_url" @ended="optionAudioEnded(index)" style="display: none;"></audio>
</div>
<div class="option-text">
<span class="option-label">{{ String.fromCharCode(65 + index) }}</span>
</div>
</div>
</div>
</div>
<!-- 提交按钮 -->
<div class="submit-section">
<el-button type="success" size="large" @click="submitAnswer" :disabled="selectedOptions.length === 0">
提交答案
</el-button>
</div>
<!-- 庆祝动画弹窗 -->
<div class="celebration-modal" v-if="showCelebration" @click="closeCelebration">
<div class="celebration-content">
<div class="celebration-icon">🎉</div>
<div class="celebration-text">回答正确!</div>
<div class="celebration-stars">
<span class="star"></span>
<span class="star"></span>
<span class="star"></span>
</div>
</div>
<div class="confetti-container">
<div class="confetti" v-for="n in 20" :key="n"></div>
</div>
</div>
<!-- 错误动画弹窗 -->
<div class="error-modal" v-if="showError" @click="closeError">
<div class="error-content">
<div class="error-icon"></div>
<div class="error-text">回答错误!</div>
</div>
</div>
</div>
</div>
</body> </body>
<script src="./index.js"></script> <script src="./index.js"></script>
</html> </html>
\ No newline at end of file
...@@ -7,37 +7,174 @@ window.air.hideAirClassLoading(key,data); //页面加载完成时,必须调 ...@@ -7,37 +7,174 @@ window.air.hideAirClassLoading(key,data); //页面加载完成时,必须调
*/ */
//对应模板的名称,这个可以不写,但是最好是能写上,同时开发多个模板的情况下如果不写这个,本地缓存可能会存在混乱 //对应模板的名称,这个可以不写,但是最好是能写上,同时开发多个模板的情况下如果不写这个,本地缓存可能会存在混乱
var key = "h5_test"; var key = "AI_QUESTION_CHOSE";
new Vue({
el: '#app',
data() {
return {
questionData: null,
selectedOptions: [],
showCelebration: false,
showError: false
}
},
created() {
window.courseware.getData((data) => {
if (data) {
this.questionData = data;
// 初始化选项的播放状态
if (this.questionData.options) {
this.questionData.options.forEach(option => {
option.isPlaying = false;
});
}
// 初始化题干播放状态
if (this.questionData) {
this.questionData.isPlaying = false;
}
} else {
// 默认演示数据
this.questionData = {
question: 'What is the capital of France?',
image_url: '',
audio_url: '',
audio_name: '',
isPlaying: false,
options: [
{
text: 'London',
image_url: '',
audio_url: '',
audio_name: '',
isPlaying: false,
is_correct: false
},
{
text: 'Paris',
image_url: '',
audio_url: '',
audio_name: '',
isPlaying: false,
is_correct: true
},
{
text: 'Berlin',
image_url: '',
audio_url: '',
audio_name: '',
isPlaying: false,
is_correct: false
}
]
};
}
// 页面加载完成,隐藏遮罩层
window.air.hideAirClassLoading(key, data);
}, key);
},
methods: {
// 题干音频播放/暂停
toggleQuestionAudio() {
const audio = this.$refs.questionAudio;
if (this.questionData.isPlaying) {
audio.pause();
this.questionData.isPlaying = false;
} else {
audio.play();
this.questionData.isPlaying = true;
}
},
// 题干音频播放结束
questionAudioEnded() {
this.questionData.isPlaying = false;
},
$(function(){ // 选项音频播放/暂停
toggleOptionAudio(optionIndex) {
const audio = this.$refs['optionAudio' + optionIndex][0];
if (this.questionData.options[optionIndex].isPlaying) {
audio.pause();
this.questionData.options[optionIndex].isPlaying = false;
} else {
audio.play();
this.questionData.options[optionIndex].isPlaying = true;
}
},
// 选项音频播放结束
optionAudioEnded(optionIndex) {
this.questionData.options[optionIndex].isPlaying = false;
},
window.courseware.getData(function(data){ // 选择/取消选择选项
//数据加载完,才算页面加载完成 toggleOption(optionIndex) {
//获取数据 const index = this.selectedOptions.indexOf(optionIndex);
if(data&&data.test){ if (index > -1) {
$("#test").html("Hello "+data.test); // 如果已选择,则取消选择
} else{ this.selectedOptions.splice(index, 1);
//一般来讲,如果没有获取到数据的话,给一组默认数据,用于演示模板的展示效果,方便别人了解这个模板 } else {
$("#test").html("Hello 我是演示信息!"); // 如果未选择,则添加选择
this.selectedOptions.push(optionIndex);
} }
this.showResult = false;
},
// 提交答案
submitAnswer() {
if (this.selectedOptions.length === 0) {
return;
}
// 获取所有正确答案的索引
const correctOptions = this.questionData.options
.map((option, index) => option.is_correct ? index : -1)
.filter(index => index !== -1);
//数据渲染完成后,添加各种交互或者动画效果 // 检查选择的答案是否正确
//我这里简单做一个渐入渐出效果的例子 const isSelectionCorrect = this.selectedOptions.length === correctOptions.length &&
var flag = true; this.selectedOptions.every(selected => correctOptions.includes(selected));
setInterval(function(){
if(flag){ if (isSelectionCorrect) {
$("#test").fadeIn(500); // 回答正确,显示庆祝动画
}else{ this.showCelebration = true;
$("#test").fadeOut(500); } else {
// 回答错误,显示错误动画并重置选择
this.showError = true;
// 延迟重置选择,让用户看到错误信息
setTimeout(() => {
this.resetAnswer();
}, 2000);
} }
flag = !flag; },
},1000)
// 重新选择
resetAnswer() {
this.selectedOptions = [];
this.showCelebration = false;
this.showError = false;
},
// 关闭庆祝弹窗
closeCelebration() {
this.showCelebration = false;
},
//在页面加载、事件、动画等都初始化完成后,一定要记得调用此方法,否则模板将是不可用的 // 关闭错误弹窗
window.air.hideAirClassLoading(key,data); closeError() {
this.showError = false;
},
},key); // 获取正确答案
getCorrectAnswer() {
const correctOptions = this.questionData.options
.map((option, index) => option.is_correct ? String.fromCharCode(65 + index) + '. ' + option.text : null)
.filter(text => text !== null);
}) return correctOptions.join('');
\ No newline at end of file }
}
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment